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!
Edge Rails now comes with protection from CSRF attacks which is implemented via a token that gets inserted in forms. This causes problems when developing Facebook applications on Rails if coupled with the Cookie Session Store which is the default Session Store in Rails 2.0 and Edge since Changeset 6184.
With Cookie Session Store, the token generator uses a salt generated from the cookie itself. I suspect Facebook modifies the cookie in between requests in ways that yield a different salt each time, causing the the token verifier to flag it as invalid.
Until I understand this problem in more detail, the current solution is to use ActiveRecord Session Store and set your own salt via the :secret option on protect_from_forgery. Rails will now successfully verify the token as legit.
Update: Looks like you can keep your Cookie Session Store. Just set the :secret manually as in the paragraph above and everything should be dandy.
Update: To make it clear, the exact exception you will hit is the ActionController::InvalidAuthenticityToken.
Update: Ignore the update that you can use Cookie Session Store with Facebook. You can’t. Until Facebook-the-giant-proxy forwards our cookies, any cookie that we read from the client will not from our own app but Facebook’s own.
At RSB, we strongly believe in tests - copious amounts of tests. We have so many tests that we had to modify our Capistrano deployment recipes to NOT checkout our spec directory. Okay, I’m exaggerating. We do have decent amount of test, especially on the still-under-development Rails plugin to ease an aspect of Facebook development (we like to call it our secret sauce - look out for that soon).
So, we have autotest to keep us honest. The thing that bugs us is that autotest eats significant CPU resources as it constantly polls the filesystem to check file modification times. Aizat Faiz, one of our newest member to the RSB family, decided to hack autotest to utilize Leopard’s new File System Events with a bit of RubyCocoa glue. Now, autotest is informed of filesystem changes. Beautiful.
Go check out the blog entry, grab the code and paste it in your ~/.autotest file. Your machine will thank you.

