Rails Routing and Facebook’s POSTs
If you have done any amount of Facebook development with Rails, you will quickly encounter the number one thing that has pissed off every Rails developer - Everything Is A POST!
With Facebooker’s pseudo-resource route generator, things are not so bad. It allows us declarations such as
facebook_resources :entries
facebook_resources :photos
in config/routes.rb which is nice and concise. However, the paths generated are so old school!
GET /entries # index
GET /entries/new # new
POST /entries/create # create
GET /entries/1/show # show
GET /entries/1/edit # edit
POST /entries/1/update # update
POST /entries/1/destroy # destroy
Yuck!
Rails taught us to love HTTP verbs, did it not? The above could be simplified if we took into account the HTTP request method, like so,
GET /entries # index
GET /entries/new # new
POST /entries # create
GET /entries/1 # show
GET /entries/1/edit # edit
PUT /entries/1 # update
DELETE /entries/1 # destroy
Unfortunately, you are stuck with just POST, buddy. Don’t even dream about your PUT and DELETE. Is there anything we can do? Fortunately, yes!
We Can Have Our RESTful Routes Back!
So, how do you solve this with Facebook? Facebook very recently added an additional request parameter to disambiguate between GET and POST via fb_sig_request_method. The actual requests from Facebook are still POSTs but we can make use of fb_sig_request_method as an added condition in our routes.
This is how you do it. Stick the following piece of code in lib and require it in a config/initializers file, or if you are using Facebooker as a plugin, modify vendor/plugins/facebooker/lib/facebooker/rails/routing.rb
def facebook_resources(name_sym)
name = name_sym.to_s
with_options :controller => name, :conditions => { :method => :post } do |map|
map.with_options :conditions => { :fb_sig_request_method => 'GET' } do |get|
get.named_route("new_#{name.singularize}", "#{name}/new", :action => 'new')
get.named_route(name, name, :action => 'index')
get.named_route(name.singularize, "#{name}/:id", :action => 'show', :id => /\d+/)
get.named_route("edit_#{name.singularize}", "#{name}/:id/edit", :action => 'edit', :id => /\d+/)
end
map.with_options :conditions => { :fb_sig_request_method => 'POST' } do |post|
post.named_route("create_#{name.singularize}", name, :action => 'create')
post.named_route("update_#{name.singularize}", "#{name}/:id", :action => 'update', :id => /\d+/)
post.named_route("destroy_#{name.singularize}", "#{name}/:id/destroy", :action => 'destroy', :id => /\d+/)
end
end
end
Notice the new :condition => { :fb_sig_request_method => 'GET'} and :condition => { :fb_sig_request_method => 'POST'}? That is the key we need to be able to overload a path like /entries/1 to call #show or #update depending on the condition. As a result, our routes now generate much nicer paths. Check it:
GET /entries # index
GET /entries/new # new
POST /entries # create
GET /entries/1 # show
GET /entries/1/edit # edit
POST /entries/1 # update
POST /entries/1/destroy # destroy
A patch has been submitted to Facebooker. Hopefully the maintainers will see it fit to merge this in. As an added bonus, the patch also includes singleton resources via
facebook_resource :entry
to generate the following routes:
GET /entry # show
GET /entry/new # new
POST /entry # create
GET /entry/edit # edit
POST /entry/update # update
POST /entry/destroy # destroy
Note that this is still very much a workaround until Facebook moves all the fb_sig metadata into the request headers and use real GETs and POSTs. But until then, happy programming!
One Response to “Breaking News! Differentiate GET and POST Requests In Facebook”
Leave a Reply


December 14th, 2007 at 3:07 am
Kamal- This looks great. We should monkey patch resources to run this code when we’re in a Facebook canvas, and fallback to regular Rails resources when it is not, as Mike M. suggested on the list. If done along with tests then I’ll commit it to the project.
Thanks!