Rails / REST
From WhyNotWiki
Aliases: Rails / REST, RESTful Rails
Rails / REST edit (Category edit)
Contents |
[edit] REST
Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST)
[edit]
Ryan Daigle (2007-03-14). REST & ActiveResource (http://ryandaigle.com/assets/2007/3/14/REST_ARes.pdf).
[edit] Grammar Abused
GET http://addressbook/contacts/destroy/1Implied action [destroy] conflicts with HTTP method [GET]
[edit] Nouns
Represent Resources with URIs (Uniform Resource Identifier)
- ‘contacts’ = http://addressbook/contacts
- Different representations of the same resource (html, xml, json)
- contacts.html, contacts.xml etc...
...
[edit] Restful Rails in general
(and anything that spans both creating and consuming restful resources)
[edit] Tutorial
(I couldn't find a tutorial out there that I liked, so I wrote my own. I put this together after going through the process once (i.e., I didn't take notes as I did it), so I may have omitted a step somewhere along the way or messed up. Please feel free to make corrections.)
Create an app containing the resource to be consumed:
> rails restful_resource
> svn add restful_resource/
> cd restful_resource/
> ruby -ropen-uri -e 'eval(open("http://balloon.hobix.com/tyler_svn_configure").read)'
> rake rails:freeze:edge
> script/generate scaffold task name:string description:text due_date:date
> rake db:migrate
> devserver restart
Go to http://localhost:3000 and insert a new record if you want.
Now create the app that will consume that resource:
> rails restful_resource_consumer
> svn add restful_resource_consumer
> cd restful_resource_consumer
> svn add ../restful_resource_consumer/
> ruby -ropen-uri -e 'eval(open("http://balloon.hobix.com/tyler_svn_configure").read)'
> rake rails:freeze:edge
> svn export http://dev.rubyonrails.org/svn/rails/trunk/activeresource vendor/rails/activeresource
Create your ActiveResource model:
class Task < ActiveResource::Base self.site = 'http://localhost:63234/' end
Test it out:
restful_resource_consumer > ./script/console
irb -> task = Task.new
=> #<Task:0xb7ce33a0 @prefix_options={}, @attributes={}>
irb -> task.name = 'Mow the lawn'
=> "Mow the lawn"
irb -> task.due_date = Time.now
=> Fri Jul 20 17:59:49 -0700 2007
irb -> task.save
=> true
irb -> task
=> #<Task:0xb7ce33a0 @prefix_options={}, @attributes={"name"=>"Mow the lawn", "id"=>2, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>
irb -> task = Task.find(2)
=> #<Task:0xb7c04984 @prefix_options={}, @attributes={"name"=>"Mow the lawn", "id"=>2, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>
irb -> Task.find(:all)
=> [#<Task:0xb7e08500 @prefix_options={}, @attributes={"name"=>"Do the dishes", "id"=>1, "description"=>nil, "due_date"=>Fri, 20 Jul 2007}>, #<Task:0xb7e07f9c @prefix_options={}, @attributes={"name"=>"Mow the lawn", "id"=>2, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>, #<Task:0xb7dfd09c @prefix_options={}, @attributes={"name"=>nil, "id"=>3, "description"=>nil, "due_date"=>Fri, 20 Jul 2007}>, #<Task:0xb7df4f50 @prefix_options={}, @attributes={"name"=>nil, "id"=>4, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>]
irb -> Task.delete 3
=> #<Net::HTTPOK 200 OK readbody=true>
irb -> Task.find(4).destroy
=> #<Net::HTTPOK 200 OK readbody=true>
It works!
Some things don't work as expected (= the same as ActiveRecord), however:
irb -> Task.find_by_name('Do the dishes')
NoMethodError: undefined method `find_by_name' for Task:Class
irb -> pp Task.find(:all, :conditions => ['name = ?', 'Do the dishes'])
[#<Task:0xb79a2c54
@attributes=
{"name"=>"Do the dishes",
"id"=>1,
"description"=>nil,
"due_date"=>Fri, 20 Jul 2007},
@prefix_options={}>,
#<Task:0xb79a2c18
@attributes=
{"name"=>"Mow the lawn",
"id"=>2,
"description"=>nil,
"due_date"=>Sat, 21 Jul 2007},
@prefix_options={}>]
When I do that back in restful_resource (using plain old ActiveRecord), by contrast, this is what I get:
restful_resource > ./script/console irb -> pp Task.find(:all, :conditions => ['name = ?', 'Do the dishes']) [#<Task id: 1, name: "Do the dishes", description: "", due_date: "2007-07-20">]
If you'd like to download these example apps and try it out for yourself, you can get it from:
- http://svn.tylerrick.com/public/rails/examples/restful_resource
- http://svn.tylerrick.com/public/rails/examples/restful_resource_consumer
[edit]
Ralf Wirdemann, Thomas Baustert (b-simple.de) (2007-03-26). RESTful Rails Development (http://www.b-simple.de/documents/download/6).
...In accordance with the REST philosophy, an update is transmitted via PUT. But as we know, neither PUT or DELETE are supported by web browsers. The solution Rails offers is the usage of a method key in the :html hash of form for:
form_for(:project, :url => project_path(@project), :html => { :method => :put }) do |f| ... => <form action="/projects/1" method="post"> <div style="margin:0;padding:0"> <input name="_method" type="hidden" value="put" /> </div>Rails generates a hidden method field that contains the appropriate HTTP verb put. The dispatcher looks at this field and routes the request to the update action.
...
[edit] Creating restful resources
(Including, but not limited to, restful routes)
[edit] Nested CRUD resources
Web 2.0 Technologies: Nested CRUD resources in Rails 1.2 (http://earthcode.com/blog/2007/01/nested_crud_resources_in_rails.html) (2007-01).
The CRUD methodology in Rails 1.2 is a great way to simplify your application structure. One of the first things you will find yourself doing is nesting one resource inside another. This is just the RESTful way of working with has_many relationships.The first couple times I did this, I forgot some of the steps (particularly changing the url paths in the controllers). So, I'm going to iterate the steps here -- hopefully this will help someone going through the learning process on Rails 1.2 CRUD.
[edit] 1. Create the resource scaffolding and migrations
Don't worry,
scaffold_resource[the resource generator] doesn't have the same stigma that old-school scaffolds had back in the day.scaffold_resourcescript/generate resource is the easiest way to jump-start a RESTful controller, and the actions/views it creates are both legitimate and useful. Let's step through the process with post has_many :commentsruby script/generate resource Post title:string body:text ruby script/generate resource Comment body:text...
[edit] 3. Modify routes.rb
The resource generator calls will have created two lines in routes.rb. You just have to nest them like so:
map.resources :posts do |posts| posts.resources :comments end...
[edit] c) scope your Comment (nested class) finders to the Post (nestee class)
This is a security measure -- it ensures that the Comment ID passed to the controller really does represent a comment which belongs to the Post in question. It's all part of ensuring that the nested class is always dealt with in the context of a nestee class. In terms of your controller code, this means finding instances of
Comment.findand changing them to@post.comments.find. [...]
[edit] What happened to the scaffold_resource generator?
It looks like DHH killed scaffold_resource in revision 6632 (2007-04-29):
See http://dev.rubyonrails.org/changeset/6632/trunk/
The uninformative commit message is "You're dead! We killed you!".
I asked on the Ruby on Rails: Talk mailing list and found out that scaffold_resource actually became the default -- that is, it replaced the old (non-restful) scaffold generator.
[edit] How is the resource generator different?
According to Trotter Cashion. Rails Refactoring to Resources: Using CRUD and REST in Your Rails Application, p. 34-35 of 73 (http://safari.oreilly.com/9780321501745/ch04lev1sec2?imagepage=34). ,
Rails provides two similar resource generators. The first,scaffold_resource, generates full scaffolding of model, view, helper, tests, and controllers for a resource. As arguments, it takes the model name (in singular form) and an optional list of property names and data types for the model. Thescaffold_resourcegenerator is run usingruby script/generate scaffold_resource task name:string description:text due_date:dateThe second generator,
resource, provides the same functionality asscaffold_resource, but does not create any views. Its syntax is identical to thescaffold_resourcesyntax. Theresourcegenerator is useful when views written byscaffold_resourcewill be overwritten immediately with custom views.Both generators create controller code [wrong!] that is similar to the code shown earlier in the
TasksController. In addition, the generators create a model with an appropriate migration to create the fields specified in the command line. These two tools are useful for cranking out many skeleton resources at the beginning of a project.
If only that were true! Unfortunately, when I do ./script/generate scaffold_resource task name:string description:text due_date:date (again, using edge Rails, r7200), this is what I get:
> cat app/controllers/tasks_controller.rb class TasksController < ApplicationController end
And when I go to http://...:63234/tasks, this is what I see (and I quote):
Unknown actionNo action responded to index
Pretty neat. Contrast that with what happens when I do this:
> script/destroy resource task
> script/generate scaffold_resource task name:string description:text due_date:date
> cat app/controllers/tasks_controller.rb
class TasksController < ApplicationController
# GET /tasks
# GET /tasks.xml
def index
@tasks = Task.find(:all)
respond_to do |format|
format.html # index.erb
format.xml { render :xml => @tasks }
end
end
...
And when I go to http://...:63234/tasks, this is what I see:
Listing tasksName Description Due date
Much more useful!
Here is a diff between the two directories (.../generators/components/scaffold_resource/ and .../generators/components/resource/):
> diff ./vendor/rails/railties/lib/rails_generator/generators/components/scaffold_resource/ ./vendor/rails/railties/lib/rails_generator/generators/components/resource/ 2,8c2,3 < The scaffold resource generator creates a model, a controller, and a < set of templates that's ready to use as the starting point for your < REST-like, resource-oriented application. This basically means that it < follows a set of conventions to exploit the full set of HTTP verbs < (GET/POST/PUT/DELETE) and is prepared for multi-client access (like one < view for HTML, one for an XML API, one for ATOM, etc). Everything comes < with sample unit and functional tests as well. --- > The resource generator creates an empty model, controller, and functional # <-------- "empty"!!! > suitable for inclusion in a REST-like, resource-oriented application. 11,14c6,8 < model name is then pluralized to get the controller name. So < "scaffold_resource post" will generate a Post model and a < PostsController and will be intended for URLs like /posts and < /posts/45. --- > model name is then pluralized to get the controller name. So "resource > post" will generate a Post model and a PostsController and will be > intended for URLs like /posts and /posts/45. 18,22c12,14 < prepopulate the migration to create the table for the model and to give < you a set of templates for the view. For example, "scaffold_resource < post title:string created_on:date body:text published:boolean" will < give you a model with those four attributes, forms to create and edit < those models from, and an index that'll list them all. --- > prepopulate the migration to create the table for the model. For > example, "resource post title:string created_on:date body:text > published:boolean" will give you a Post model with those four attributes. 28,33c20,26 < The generator also adds a declaration to your config/routes.rb file < to hook up the rules that'll point URLs to this new resource. If you < create a resource like "scaffold_resource post", it will add < "map.resources :posts" (notice the plural form) in the routes file, < making your new resource accessible from /posts. < --- > The generator also adds an appropriate map.resources declaration to > your config/routes.rb file, hooking up the rules that'll point URLs to > this new resource. > > Unlike the scaffold_resource generator, the resource generator does not > create views or add any methods to the generated controller. > 35,37c28,30 < ./script/generate scaffold_resource post # no attributes, view will be anemic < ./script/generate scaffold_resource post title:string created_on:date body:text published:boolean < ./script/generate scaffold_resource purchase order_id:integer created_at:datetime amount:decimal --- > ./script/generate resource post # no attributes > ./script/generate resource post title:string created_on:date body:text published:boolean > ./script/generate resource purchase order_id:integer created_at:datetime amount:decimal
> cat ./vendor/rails/railties/lib/rails_generator/generators/components/resource/templates/controller.rb
class <%= controller_class_name %>Controller < ApplicationController
end
> cat ./vendor/rails/railties/lib/rails_generator/generators/components/scaffold_resource/templates/controller.rb
class <%= controller_class_name %>Controller < ApplicationController
# GET /<%= table_name %>
# GET /<%= table_name %>.xml
def index
@<%= table_name %> = <%= class_name %>.find(:all)
respond_to do |format|
format.html # index.erb
format.xml { render :xml => @<%= table_name %> }
end
end
...
Yeah, I'd pretty much say that the scaffold generator generates an empty controller. Which is probably great for coders who are experienced with restful controllers and would just discard the scaffolding anyway...
But as for me personally, I'm only just learning about how all the REST stuff works under Rails. So it would be immensely helpful to have a generated controller to start out with so that I can learn how this stuff works, have something useful from the get-go, and be able to start modifying it to suit my needs.
As Web 2.0 Technologies: Nested CRUD resources in Rails 1.2 (http://earthcode.com/blog/2007/01/nested_crud_resources_in_rails.html) (2007-01). says:
script/generate resource is the easiest way to jump-start a RESTful controller, and the actions/views it creates are both legitimate and useful.
(But, as Trotter Cashion. Rails Refactoring to Resources: Using CRUD and REST in Your Rails Application, p. 35 of 73 (http://safari.oreilly.com/9780321501745/ch04lev1sec2?imagepage=35). points out, "the repetition in RESTful controllers is readily apparent." So you may want to create a base CrudController class that all your controllers inherit from (described below?), which almost makes the presence of scaffold_resource a moot point...!)
It looks like the resource generator generates the exact same thing as the scaffold generator with the exception that it generates an empty controller rather than a scaffold controller.
So the moral of the story is, use the scaffold generator if you want to generate a resource and a scaffold (i.e., you're a beginner); use the resource generator if you're an expert and you don't want the scaffold part...
[edit] How do I continue using scaffold_resource if I really want to use it still?
Well, you could just content yourself with staying at 6631 for the rest of your like, like this:
> rake rails:freeze:edge REVISION=6631
But I, for one, would prefer not to be tied down to an old version. So (as of this writing), I'm frozen to 7200 but I've exported the code that was removed in 6632, like this:
> svn export -r 6631 http://dev.rubyonrails.org/svn/rails/trunk/railties/lib/rails_generator/generators/components/scaffold_resource/@6631 vendor/rails/railties/lib/rails_generator/generators/components/scaffold_resource/ > svn add vendor/rails/railties/lib/rails_generator/generators/components/scaffold_resource > svn ci vendor/rails/railties/lib/rails_generator/generators/components/scaffold_resource -m 'Added scaffold_resource back in, from r6631'
[edit] resource generator
> ./script/generate resource User username:string group_id:integer
exists app/models/
exists app/controllers/
exists app/helpers/
create app/views/users
exists test/functional/
exists test/unit/
dependency model
exists app/models/
exists test/unit/
exists test/fixtures/
identical app/models/user.rb
identical test/unit/user_test.rb
create test/fixtures/users.yml
exists db/migrate
create db/migrate/001_create_users.rb
create app/controllers/users_controller.rb
create test/functional/users_controller_test.rb
create app/helpers/users_helper.rb
route map.resources :users
[edit] Peepcode
http://peepcode.com/products/restful-rails, $9
RESTful routes are a concept that will be a big part of the upcoming Rails 1.2, but they are also very confusing!
This screencast covers the basics of REST and walks through a simple application to show how REST routes work. You’ll learn about the magic that goes on behind the scenes and how you can design a REST application with confidence.
Routes, URLs, interface elements, template redirection, testing, and general tips are covered over the course of 85 minutes.
If you are already familiar with how to build a Rails application, this screencast will give you the knowledge you need to convert an existing application to REST or design a new app with the principles of REST.
[edit] Consuming restful resources
[edit] ActiveResource
ActiveResource edit (Category edit)
Rails / REST edit (Category edit)
[edit]
http://dev.rubyonrails.org/svn/rails/trunk/activeresource/README

[edit]
Ryan Daigle (2007-03-14). REST & ActiveResource (http://ryandaigle.com/assets/2007/3/14/REST_ARes.pdf).
[edit] Nested Resources
- Nested != Inherited
- has_many relationships often represented w/ nesting
- Nesting specified via URI: http://addressbook/users/1/contacts.xml
Containing model:
class User < ActiveResource::Base self.site = "http://addressbook" endNested model:
class Contact < ActiveResource::Base self.site = "http://addressbook/users/:user_id" # This is the URI *prefix* used for all requests through this ARes model # An actual request URI might look something like this: # "http://addressbook/users/2/contacts.xml" end[edit] Nested Model Initialization
contact = Contact.new(:name => 'Ryan', :last_name => 'Daigle', {:user_id => 2} # This hash is used to populate nesting entities in URI (:user_id in "http://addressbook/users/:user_id") )...
[edit] Token-Based Authentication
class User < ActiveResource::Base self.site = "http://addressbook/2kal893jl" end[edit] ARes Caveats
- No ActiveRecord-like DSL [I assume they mean shortcuts like find_by_name and stuff? Or what exactly is ARes missing in comparison to AR?]
[edit]
http://www.ryandaigle.com/articles/2006/06/30/whats-new-in-edge-rails-activeresource-is-here :
CRUD is great because it’s consistent, simple, expressive and foundational. Every application should be built solely on CRUD operations – and Rails is going to help you construct such applications...
If you haven’t noticed, it appears that the next version of Rails will whole heartedly embrace the RESTful model of application development...
ActiveResource provides a large piece of the REST puzzle by basically implementing the client side of a RESTful system – the parts of a decoupled system that consume RESTful services. At its essence, ActiveResource provides a way to utilize model objects as REST-based client proxies to remote services.
Let’s look at an example of a typical find call on a Person model object:
Person.find(1).name #=> "Ryan"Doesn’t appear to be anything out of the ordinary going on here – except that under the covers it’s not a database SELECT that’s occuring. Instead, if the Person model is an Active Resource, this find call is actually sending an HTTP GET request across the wire to a RESTful service:
GET http://api.myremote.com/people/1 <= <person><name>Ryan</name></person>
[edit] How to get it installed/working
This apparently doesn't export activeresource for you:
> rake rails:freeze:edge
(I'm not sure why not.) See?:
> ffind activeresource; ffind active_resource (nothing)
In fact, this will throw an error until you make activeresource available:
> ./script/about
/usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require': no such file to load -- active_resource (MissingSourceFile)
from /usr/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require'
from /home/tyler/code/examples/restful_resource/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:495:in `require'
from /home/tyler/code/examples/restful_resource/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:342:in `new_constants_in'
from /home/tyler/code/examples/restful_resource/vendor/rails/activerecord/lib/../../activesupport/lib/active_support/dependencies.rb:495:in `require'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:160:in `require_frameworks'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:160:in `each'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:160:in `require_frameworks'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:88:in `process'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:49:in `send'
from ./script/../config/../vendor/rails/railties/lib/initializer.rb:49:in `run'
from /home/tyler/code/examples/restful_resource/config/environment.rb:13
from /home/tyler/code/examples/restful_resource/vendor/rails/railties/lib/commands/about.rb:1:in `require'
from /home/tyler/code/examples/restful_resource/vendor/rails/railties/lib/commands/about.rb:1
from ./script/about:3:in `require'
from ./script/about:3
Wow! So Rails (edge version 7200) expects to find activeresource but yet Rails doesn't provide activeresource out of the box?
> svn export http://dev.rubyonrails.org/svn/rails/trunk/activeresource vendor/rails/activeresource > ffind activeresource; ffind active_resource ./vendor/rails/activeresource ./vendor/rails/activeresource/lib/active_resource.rb ./vendor/rails/activeresource/lib/active_resource
[edit] Problems
[edit] Dates are transmitted as UTC times (not local times) and the time part of the date is lost/truncated
Here was my schema (sqlite3):
CREATE TABLE tasks ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255) DEFAULT NULL, "description" text DEFAULT NULL, "due_date" date DEFAULT NULL);
restful_resource_consumer > ./script/console
...
irb -> task.due_date = Time.now
=> Fri Jul 20 17:59:49 -0700 2007
irb -> task.save
=> true
irb -> task
=> #<Task:0xb7ce33a0 @prefix_options={}, @attributes={"name"=>"Mow the lawn", "id"=>2, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>
irb -> task = Task.find(2)
=> #<Task:0xb7c04984 @prefix_options={}, @attributes={"name"=>"Mow the lawn", "id"=>2, "description"=>nil, "due_date"=>Sat, 21 Jul 2007}>
I would say, "maybe it's a problem with date types in sqlite3 with ActiveRecord", except that I can't reproduce the problem when I access the model via ActiveRecord directly as opposed to through ActiveResource:
restful_resource > ./script/console
irb -> Task.create(:due_date => Time.now)
=> #<Task id: 3, name: nil, description: nil, due_date: "2007-07-20 18:24:34">
irb -> Task.find(3)
=> #<Task id: 3, name: nil, description: nil, due_date: "2007-07-20">
Obviously I need to do a bit more digging to find out what's going on... I'd like to look at the actual XML data that gets posted by ActiveResource...
