Tuesday, September 4, 2007

Rails Restful Routes for Facebook

So you start a new Ruby on Rails application specifically for Facebook. You want to hang with the cool kids, so you're using RESTful routes, but you find out that all facebook callbacks are POSTs, which doesn't work with the default Rails RESTful routes because the default paths rely on the full vocabulary of HTTP verbs to disambiguate actions.

To address this I first tried using resources options such as


:member => { :edit => :post }

This added the needed routes based on POST, but left the url helpers creating GETs. So instead I went with named routes, creating a new set of url helpers prefixed with the name of the action. All the helpers except index act on and thus refer to the singular resource. I googled around for an example specific to facebook with no luck, so the below might save someone some time. Lets say you have a users class, and each user has_many gifts. To setup the users class to work with facebook, put this in routes.rb:

map.with_options :controller => 'users' do |user|
user.new_user 'users/new', :action => 'new', :conditions => { :method => :post }
user.index_users 'users/index', :action => 'index', :conditions => { :method => :post }
user.show_user 'users/:id/show', :action => 'show', :id => /\d+/, :conditions => { :method => :post }
user.create_user 'users/create', :action => 'create', :conditions => { :method => :post }
user.edit_user 'users/:id/edit', :action => 'edit', :id => /\d+/, :conditions => { :method => :post }
user.update_user 'users/:id/update', :action => 'update', :id => /\d+/, :conditions => { :method => :post }
user.destroy_user 'users/:id/destroy', :action => 'destroy', :id => /\d+/, :conditions => { :method => :post }
end


Next, you want routes for gifts, nested with users, so add this:

map.with_options :controller => 'gift' do |gift|

gift.new_gift 'users/:user_id/gift/new', :action => 'new', :user_id => /\d+/, :conditions => { :method => :post }

gift.index_gift 'users/:user_id/gift/index', :action => 'index', :user_id => /\d+/, :conditions => { :method => :post }

gift.show_gift 'users/:user_id/gift/:id/show', :action => 'show', :user_id => /\d+/, :id => /\d+/, :conditions => { :method => :post }

gift.create_gift 'users/:user_id/gift/create', :action => 'create', :user_id => /\d+/, :conditions => { :method => :post }

gift.edit_gift 'users/:user_id/gift/:id/edit', :action => 'edit', :user_id => /\d+/, :id => /\d+/, :conditions => { :method => :post }

gift.update_gift 'users/:user_id/gift/:id/update', :action => 'update', :user_id => /\d+/, :id => /\d+/, :conditions => { :method => :post }

gift.destroy_gift 'users/:user_id/gift/:id/destroy', :action => 'destroy', :user_id => /\d+/, :id => /\d+/, :conditions => { :method => :post }


end

Run 'sake routes' to see the routes produced. Nice.

4 comments:

Anonymous said...

Thanks - that was really helpful!

Shane V said...

Why not pass :conditions => { :method => :post } into with_options as well to keep things DRY?

Logan Henriquez said...

That makes sense, if with_options accepts that condition statement. Let me know if if works for you.

Shane V said...

with_options can accept a list:

map.with_options :controller => 'search', :conditions => { :method => :post } do |blah| ...