JSON with Ruby on Rails on Google AppEngine
- John Wang
Getting Started
Here's a list of what you'll need to get started with the demo:
- a Google AppEngine account
- Ruby 1.8.6
- Rails 2.3.5
- Ruby on Rails AppEngine Gem
Instructions
The first steps that you'll want to follow are from John Woodell's Gist on Rails 2.3.5 on App Engine with DataMapper.
As you'll notice, the last couple of steps generate a RESTful resource called Contact and also it's associated DataMapper Model.
Adding REST
Now in order to add JSON to the mix, we'll need a couple more things:
- JSON gem
- dm-serializer gem
We only have to make a few very small changes to the demo app that you got from the Gist to get JSON integrated. They're listed below, and also at this Gist.
Gemfile changes: adding the json-jruby and dm-serializer to the gems for bundler
[sourcecode language="ruby"]
Critical default settings:
disable_system_gems disable_rubygems bundle_path '.gems/bundler_gems'
List gems to bundle here:
gem 'rails_dm_datastore' gem 'rails', "2.3.5" gem "json-jruby" gem "dm-serializer" [/sourcecode]
config.ru changes: adding require 'dm-serializer' and 'json'
[sourcecode language="ruby"] require 'appengine-rack' require 'cgi' require 'json' require 'dm-core' require 'dm-serializer'
AppEngine::Rack.configure_app(
:application => 'iphone-json',
:precompilation_enabled => true,
:sessions_enabled => true,
:version => "1")
AppEngine::Rack.app.resource_files.exclude :rails_excludes ENV['RAILS_ENV'] = AppEngine::Rack.environment
deferred_dispatcher = AppEngine::Rack::DeferredDispatcher.new(
:require => File.expand_path('../config/environment', __FILE__),
:dispatch => 'ActionController::Dispatcher', :isolate => true)
map '/admin' do use AppEngine::Rack::AdminRequired run deferred_dispatcher end
map '/user' do use AppEngine::Rack::LoginRequired run deferred_dispatcher end
map '/' do run deferred_dispatcher end [/sourcecode]
and finally, the contacts_controller.rb needs the corresponding format.json render statements.
[sourcecode language="ruby"] class ContactsController < ApplicationController # GET /contacts # GET /contacts.xml # GET /contacts.json def index
@contacts = Contact.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => @contacts }
format.json { render :json => @contacts }
end
end
# GET /contacts/1 # GET /contacts/1.xml # GET /contacts/1.json def show
@contact = Contact.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @contact }
format.json { render :json => @contact }
end
end
# GET /contacts/new # GET /contacts/new.xml # GET /contacts/new.json def new
@contact = Contact.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => @contact }
format.json { render :json => @contact }
end
end
# GET /contacts/1/edit def edit
@contact = Contact.find(params[:id])
end
# POST /contacts # POST /contacts.xml # POST /contacts.json def create
@contact = Contact.new(params[:contact])
respond_to do |format|
if @contact.save
flash[:notice] = 'Contact was successfully created.'
format.html { redirect_to(@contact) }
format.xml { render :xml => @contact, :status => :created, :location => @contact }
format.json { render :json => @contact, :status => :created, :location => @contact }
else
format.html { render :action => "new" }
format.xml { render :xml => @contact.errors, :status => :unprocessable_entity }
format.json { render :json => @contact.errors, :status => :unprocessable_entity }
end
end
end
# PUT /contacts/1 # PUT /contacts/1.xml # PUT /contacts/1.json def update
@contact = Contact.find(params[:id])
respond_to do |format|
if @contact.update_attributes(params[:contact])
flash[:notice] = 'Contact was successfully updated.'
format.html { redirect_to(@contact) }
format.xml { head :ok }
format.json { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => @contact.errors, :status => :unprocessable_entity }
format.json { render :json => @contact.errors, :status => :unprocessable_entity }
end
end
end
# DELETE /contacts/1 # DELETE /contacts/1.xml def destroy
@contact = Contact.find(params[:id])
@contact.destroy
respond_to do |format|
format.html { redirect_to(contacts_url) }
format.xml { head :ok }
format.json { head :ok }
end
end end [/sourcecode]
Final Notes
- You may have noticed that by default you get RESTful XML when you generated the resource. It doesn't work without the needed dm-serializer. Remember, we're using DataMapper here and not ActiveResource. If you try, you'll get this beautiful error.
SEVERE: [1272067889120000] RuntimeError (Not all elements respond to to_xml)
- If you inspect your DataMapper model, you'll notice that every field is a required field. This is key for when you're testing your POST method.
- The last thing that is currently being looked into, is that the POST requires you to pass in the Class in lowercase for it to properly work. I'm not sure if this is a case-sensitive issue, or just purely needing to change the param to be ":Contact" instead of ":contact" in the controller.
Source Code
- The fully working demo can be found running on Google AppEngine at: iphone-json.appspot.com
- source is available on Github.
- And there is an accompanying iPhone REST client also available on Github. (Android client is currently in development.)
Alternatively, if you just want to quickly test your newly created JSON RESTful resource, you can use the REST Client for Firefox or REST Client for Google Chrome.