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
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!