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!
0 Responses
Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.