[uf-rest] RESTified Rails Controller
Dan Kubb
dan.kubb at autopilotmarketing.com
Tue Nov 15 00:57:33 PST 2005
Hi all,
I created a proof of concept controller that implements some of the
ideas we've been talking about for a more RESTful Rails:
http://www.onautopilot.com/oss/rails/rest_controller.rb
Here's an example of its usage:
===================================================================
# config/routes.rb
ActionController::Routing::Routes.draw do |map|
map.connect ':controller/:action', :action =>
'index', :requirements => { :action => /(?:[a-z](?:[-_]?[a-z]+)*)/ }
map.connect ':controller/:id', :action =>
'show', :requirements => { :id => /\d+/ }
map.connect ':controller/:id/:action',
end
===================================================================
# app/controllers/person_controller.rb
class PersonController < RestController
verbs_for :index do
def get
@person_pages, @people = paginate(:people, :per_page => 20)
@etag = @people.collect { |p| p.lock_version }.join(',')
@modified_at = @people.collect { |p| p.updated_at }.sort.last
end
def post
@person = Person.new(params[:person])
if meets_conditions? and @person.save
flash[:notice] = 'The person was successfully created'
render_post_success(:id => @person)
end
end
end
verbs_for :show do
def load_person
person = Person.find(params[:id])
@etag = person.lock_version
@modified_at = person.updated_at
person
rescue ActiveRecord::RecordNotFound
flash[:warning] = 'No person found'
end
def get
@person = load_person
end
def put
if @person = load_person
@person.attributes = params[:person]
if meets_conditions? and @person.save
flash[:notice] = 'The person was successfully updated'
render_put_success(:id => @person)
end
end
end
def delete
if @person = load_person and meets_conditions? and
@person.destroy
flash[:notice] = 'The person was successfully deleted'
render_delete_success(:id => @person)
end
end
end
end
===================================================================
A couple of things will happen:
- For the index and show actions, depending on the HTTP method
a different method is dispatched to. So if someone does
a GET on /person/1 the get() method inside the "show" action
is executed.
This is based on the verbs_for example David came up with last
week on the list.
Since the method "handlers" are defined explicitly it will
handle HTTP OPTIONS requests automatically. Through introspection
an exact list of supported methods will be returned for any
given URI.
- Conditional GET is automatically supported when the @etag
and @modified_at attributes are set.
Conditional GET basically allows you to skip rendering of
a view if the copy the client has is the same as what you
were going to output -- there may be some big performance
benefits for large pages or complex views.
Most people will glance over this and not give it a second
thought, but I really encourage you to look at it in more
detail -- IMHO, other than the method-based dispatching, I
think this is the coolest thing in the controller.
Especially be sure to check out the methods than handle
the conditional logic, I'd like a second pair of eyes
familiar with RFC 2616 to look them over:
if_match?
if_none_match?
if_modified_since?
if_unmodified_since?
conditional_status
meets_conditions?
etag
- As per David's suggestion the render() method can support
an :etag and :modified_at arguments -- however to support
things like conditional PUT/DELETE there needs to have
a way to set this data way before the render stage.
- You'll note that the example above properly supports
conditional PUT and DELETE. For people doing AJAX apps
this can be nice to avoid the stale update problem --
when a client takes a copy of an object, changes it, and
goes to commit the changes, but the underlying data has
changed. With conditional PUT the client specifies the
ETag and the Last-Modified date of the original object
when updating it, and the server will ONLY perform the
update if the server state has not changed.
- This supports HTTP method tunneling of PUT and DELETE over
POST using specifically named input tags, using the
naming conventions here:
http://www.microformats.org/wiki/rest/rails#PUT_.26_DELETE
While I don't really like doing this, I would prefer to
do this than create alternate pathways to handle crippled
user agents or rely 100% on AJAX for submitting forms with
methods other than GET or POST.
Ideally I'd like to create a patch so some of this can go into the
rails core, but I'm not sure what would stay in a base REST
controller, and what would be merged into the existing code base.
I'd love to get some feedback -- advice from more experienced rails
developers would be greatly appreciated. I've done a fair amount
with HTTP, but I've only been coding in Ruby for a couple of weeks.
--
Thanks,
Dan
__________________________________________________________________
Dan Kubb Email: dan.kubb at autopilotmarketing.com
Autopilot Marketing Inc. Phone: 1 (604) 820-0212
Web: http://www.autopilotmarketing.com
__________________________________________________________________
More information about the microformats-rest
mailing list