Skip to content


Quick REST with ActiveResource and rails

Posted by McWong 53 days ago
This walkthrough shows the creation of a simple REST API using ActiveResource. It includes a nested model, authorization scheme, and a client library. The intent is not to exhaustively explore ActiveResource, but to show how to quickly get up and running with a reasonable REST API.

Step 1: create an application

rails ares-demo

I’m using rails 2.0.2 here, but I don’t think anything is version specific.

Step 2: install two great rest plugins
Resource This is a plugin that adds default rest-based actions to a controller. It has so-so support for nested resources… you won’t be able to use it out of the box for HABTM relationships for instance, but for strict hierarchies it works well, and really beats hand coding the controllers. You can install it with

script/plugin install http://jnewland.com/svn/public/ruby/rails/plugins/resource_this/

Restful Authentication is derived from acts_as_authenticated, and adds a rest-ish session controller, as well as the usual controller helpers (login_required, etc.) You can install it with

script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/

Step 3: setup restful authentication
This bit is easy. Just run the generator to get your migrations for users created, along with a couple other things. I honestly didn’t dig into this too much, it just works.

./script/generate authenticated user sessions

Step 4: create application model
Next up is generating your model. We’ll do a simple one. Posts contain many Comments. Both Posts and Comments belong to a user.

./script/generate scaffold post
./script/generate scaffold comment

Next set up your migrations. db/migrate/002_create_posts.rb should contain

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.column :content, :text
      t.column :user_id, :integer
      t.timestamps
    end
  end
 
  def self.down
    drop_table :posts
  end
end

and db/migrate/003_create_comments.rb should contain

class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :content, :text
      t.column :user_id, :integer
      t.column :post_id, :integer
      t.timestamps
    end
  end
 
  def self.down
    drop_table :comments
  end
end

Next you’ll need to add the relationships to your model classes. Add the following bit to the front of app/models/user.rb

class User < ActiveRecord::Base
  # custom relationships
  has_many :posts
  has_many :comments
 
  app/models/post.rb should look like
 
  class Post < ActiveRecord::Base
    belongs_to :user
    has_many :comments
  end
 
  and app/models/comment.rb should look like
 
  class Comment < ActiveRecord::Base
    belongs_to :user
    belongs_to :post
  end
end

Now run migrate to get your database all configured

rake db:migrate

Step 5: populate dummy data
Next we’ll want to get some default data in there. Execute script/console and run through the following

>> u1 = User.new(:login => "test1", :email => "test1@foo.com", :password => "test1", :password_confirmation => "test1")
=> #<User id: nil, login: "test1", email: "test1@foo.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil>
>> u1.save
=> true
>> u2 = User.new(:login => "test2", :email => "test2@foo.com", :password => "test2", :password_confirmation => "test2")
=> #<User id: nil, login: "test2", email: "test2@foo.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil>
>> u2.save
=> true
>> p = Post.new(:user => u1, :content => "The first post")
=> #<Post id: nil, content: "The first post", user_id: 1, created_at: nil, updated_at: nil>
>> p.save
=> true
>> c = Comment.new(:user => u2, :post => p, :content => "first!")
=> #<Comment id: nil, content: "first!", user_id: 2, post_id: 1, created_at: nil, updated_at: nil>
>> c.save
=> true
>>

Step 6: resource_this!
Now the boring part us over. So far we have a basic rails app. Now we get to the REST part! You’re three edits away… Change app/controllers/posts_controller.rb to the following

class PostsController < ApplicationController
  resource_this
end

Change app/controllers/comments_controller.rb to the following

class CommentsController < ApplicationController
  resource_this :nested => [:post]
end

And finally update config/routes.rb

ActionController::Routing::Routes.draw do |map|
  map.resources :posts do |post|
    post.resources :comments
  end
 
  map.resources :users
 
  map.resource :session
end

That’s it! You’re ready to look through your API

Step 7: browse
Start up your server with script/server. You’ll need to clean up the erb views if you want to use the HTML interface, but your xml interface should be working just fine. Try the following urls for verification

* http://localhost:3000/posts/1.xml
* http://localhost:3000/posts/1/comments.xml

Step 8: lock it down
So far our service is open to anybody. We’ll add very simple authentication at this stage… we’ll require a valid user for anything other than show and index requests. All we have to do for this is include AuthenticatedSystem in our application controller, and add a before_filter. Make app/controllers/application.rb look like the following

class ApplicationController < ActionController::Base
  include AuthenticatedSystem
  before_filter :login_required, :only => [:create,:edit,:new,:update,:destroy]
  helper :all # include all helpers, all the time
 
  # See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the cookie session store
  protect_from_forgery # :secret => 'b845cf981afbfef06600e5d7f23e0fed'
end

Step 9: create client lib
We could test this with curl or a browser, but the real fun of ActiveResource is getting to use ruby objects. So… we’ll create a simple client api. Create a new file in lib called ‘ares_sample_client.rb’ and add the following code

require 'activeresource'
 
module Sample
  module Client
    class API
      class Post < ActiveResource::Base
        self.site = "http://localhost:3000"
      end
 
      class Comment < ActiveResource::Base
        self.site = "http://localhost:3000"
      end
    end
  end
end

Now we can play with our creation using the console. Make sure your server is running on port 3000, and startup script/console.

>> require 'ares_sample_client.rb'
=> ["Sample"]
>> posts = Sample::Client::API::Post.find(:all)
=> [#<Sample::Client::API::Post:0xb710bec4 @attributes={"updated_at"=>Wed Jan 09 02:36:34 UTC 2008, "id"=>1, "content"=>"The first post", "user_id"=>1, "created_at"=>Wed Jan 09 02:36:34 UTC 2008}, @prefix_options={}>]
>>
 
>> p = Sample::Client::API::Post.create
ActiveResource::UnauthorizedAccess: Failed with 401 Unauthorized
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:125:in `handle_response'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:112:in `request'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:101:in `post'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:803:in `create'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:636:in `save_without_validation'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/validations.rb:262:in `save'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
from /usr/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/object/misc.rb:28:in `returning'
from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
from (irb):3
>>

Our API works, and the authentication is as it should be. We didn’t specify credentials, so we can’t actually create a post. Let’s fix up our client lib to add credentials

module Sample
  module Client
    class API
      class Post < ActiveResource::Base
        self.site = "http://test1:test1@localhost:3000"
      end
 
      class Comment < ActiveResource::Base
        self.site = "http://test1:test1@localhost:3000"
      end
    end
  end
end

Note that credentials apply to the whole class… this makes the API fairly easy to use, but makes using multiple users difficult. Anyway, with our authentication in place, let’s try again

>> p = Sample::Client::API::Post.create(:content => "Should succeed", :user_id => 1)
=> #<Sample::Client::API::Post:0xb71b81d8 @attributes={"updated_at"=>Wed Jan 09 02:55:22 UTC 2008, "id"=>3, "content"=>"Should succeed", "user_id"=>1, "created_at"=>Wed Jan 09 02:55:22 UTC 2008}, @prefix_options={}>
>> p.save
=> true
>>

Sweet! Now we can not only browse posts and comments, but create them as well!

Posted in rails.


0 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.



Some HTML is OK

or, reply to this post via trackback.