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

Pastie link

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”

  1. Shane Vitarana Says:

    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!

Leave a Reply

We're Hiring
RSB is currently looking for Ruby On Rails developer and Web / Graphic Designer to join our amazing family.