Rails Facebook Development – Sharing some code for tracking active/inactive users…

Posted: January 9th, 2010 | Author: | Filed under: posts | Tags: , , , , | 14 Comments »

The next version of FanGamb will be a Facebook app.  It’s not a 1:1 translation of the current game to Facebook – we’ll be incorporating many of our game design learnings from running the first version of the game for NFL/NCAA football this fall. Since FanGamb is a Rails app, we’ve been working on setting up a collaborative development process around building Facebook apps in a team environment.  I’ll post more on this process to come, but for now, I wanted to contribute some code from our Rails Facebooker-based (that’s the main Rails Facebook API plugin) app.

Documentation on Facebooker is a little light, so there isn’t a whole lot to go on.  There is a fairly active Google group, though, and a list of the methods, so we’re not totally in the dark.  But as far as hunting for best practices and example code, it’s been a lot of trial and error.

One of the things that we wanted to implement in our application is making sure we know which users have our app installed.  It’s pretty common to store the user ids in the app’s database – you need to be able to link it to other data elements that you’re storing for them.  However, Facebooker doesn’t by default help you keep that user model in sync with which users actually have the app installed on Facebook.  You can tell which of a user’s friends have the app, but not a master list.  When you have an active Facebook user session, you can look at User#has_added_app, but we want to be able to tell if a user has the app added even if they aren’t on the app at that moment (for things like leaderboards, etc.).

So, we need to be able to track when a user uninstalls the application and set their user record to be inactive.  Facebook has the post_authorize and post_deauthorize call backs for this tracking.  As I said, there isn’t a lot of sample Facebooker code out there, so if you’re looking to implement the same thing, here are our callback actions for you to utilize.

The first thing you’re going to want to do is probably put your callback actions in a separate controller, so that you can limit which filters are run on the controller.  Facebooker has a before filter ensure_app_installed (suggested for your application_controller) which basically forces a user trying to use the app to install it.  This won’t work for your post_deauthorize action, so you need to turn that off:

skip_before_filter :ensure_app_installed, :except => :post_authorize

Also, because Facebook posts to these URLs, Rails treats it as a form submission and will try to verify the authenticity token.  So you need to turn that off for both post_authorize and post_deauthorize:

skip_before_filter :verify_authenticity_token

With this setup, you’re able to create the rest of the methods.  Here’s the stub code:

class CallbacksController < ApplicationController
  skip_before_filter :ensure_app_installed, :except => :post_authorize
  skip_before_filter :verify_authenticity_token
  def post_authorize
    if request.post?
      #do something here
      render :nothing => true
    end
  end
  def post_deauthorize
    if request.post?
      #do something here
      render :nothing => true
    end
  end
end

One important point – the above code will work, but we can’t forget to verify the signature to ensure that the request is actually from Facebook, since these actions are outside of the Facebooker plugin (which typically handles this validation).

Looking at the Facebook development wiki, you can see some sample code for RFacebook on how to validate this.  Implementing this, our callback controller now looks like the following:

class CallbacksController < ApplicationController
  skip_before_filter :ensure_app_installed, :except => :post_authorize
  skip_before_filter :verify_authenticity_token
  def post_authorize
    if request.post?
      if verify_uninstall_signature
        #set user's has_app indicator to 1 to indicate active
      end
    end
    render :nothing => true
  end
  def post_deauthorize
    if request.post?
      if verify_uninstall_signature
        #set user's has_app indicator to 0 to indicate inactive
      end
    end
    render :nothing => true
  end
  private
      #based on http://wiki.developers.facebook.com/index.php/Post-Remove_URL
      def verify_uninstall_signature
        signature = ''
        keys = params.keys.sort
        keys.each do |key|
          next if key == 'fb_sig'
          next unless key.include?('fb_sig')
          key_name = key.gsub('fb_sig_', '')
          signature += key_name
          signature += '='
          signature += params[key]
        end
        signature += FACEBOOK_YAML['secret_key']
        calculated_sig = Digest::MD5.hexdigest(signature)
        if calculated_sig != params[:fb_sig]
          logger.warn "\nWARNING :: potential spoofing :: expected signatures did not match"
          logger.info "\nSignature (fb_sig param from facebook) :: #{params[:fb_sig]}"
          logger.info "\nSignature String (pre-hash) :: #{signature}"
          logger.info "\nMD5 Hashed Sig :: #{calculated_sig}"
          #check to see if ip variables are nil
          if not request.env['HTTP_X_FORWARDED_FOR'].nil? and not request.env['HTTP_X_REAL_IP'].nil?
            ip = request.env['HTTP_X_FORWARDED_FOR'] || request.env['HTTP_X_REAL_IP']
          else
            ip = request.remote_ip
          end
          logger.info "\nRemote IP :: #{ip}"
          return false
        else
          #logger.warn "\n\nSUCCESS!! Signatures matched.\n"
        end
        return true
      end
end

Get the code in a Gist here.

Note the one line that references FACEBOOK_YAML[‘secret_key'].  This should refer to your secret Facebook key.  We put this into the initializer block in environment.rb:

#load facebooker configuration for usage
facebook_config = File.join(RAILS_ROOT, 'config', 'facebooker.yml')
FACEBOOK_YAML = YAML::load(ERB.new(File.read(facebook_config)).result)[RAILS_ENV]

Phew!  That should do it – you should now be able to track whether your users have the app installed or not via the post callbacks.  Hopefully, this will help someone looking to keep track of active/inactive users in their Rails Facebook app!

Big props to my co-founder Alan deLevie who’s figured out a big chunk of this Facebooker stuff for the FanGamb team.

EDIT: typo fixed.  Thanks Sting Tao!


  • http://twitter.com/slainer68 Nicolas Blanco

    thanks!

    maybe it would be very useful to create a reusable rack module to handle all these special “cases” with facebook. For example like the fb:comments tags that needs to send POST method on a GET action to read comments (a thing that is incompatible with the way REST is implemented in Rails).

    Nicolas.

  • http://www.robertshedd.com Robert Shedd

    Thanks for the comment. Glad it was useful.

    I think certainly having this kind of code extracted and consolidated into a module for ease of reusability makes a ton of sense. Along the same lines, perhaps just having a shell-app that consolidates these best practices would be helpful, as well. There's the Karate Poke (http://github.com/mmangino/karate_poke/) app from the author of Facebooker, but there's certainly much that could be added to that…

  • Sting Tao

    Hi…. Good code….I'll reference in a Chinese blog by translating your points, credit on you and link here….
    Suppose it would be ok? Thanks in advance.

  • http://www.robertshedd.com Robert Shedd

    Sure, you're welcome to translate it. Just reference the original source as this post. Thanks!

  • Sting Tao

    Thanks.
    And…I also worked for IBM Global Services before…. (Taiwan)… We were colleagues…ha….

  • http://www.robertshedd.com Robert Shedd

    Very cool – nice to meet you out on the web! IBM GBS is a great organization – tons of fantastic people… What are you working on now?

  • Sting Tao

    worked for Korean internet company – NHN for the last two+ years. Trying to do something on internet all the time.
    Ha…I found one spelling in your article. Search “post_auathoriz”

  • http://www.robertshedd.com Robert Shedd

    Thanks for catching the typo! We caught that (obviously) when we tried to run the code, but forgot to correct it for the post. Thanks for pointing it out. All fixed.

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

     Hi Friends

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

    hi nishit

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

    Ruby On Rails

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

    Hi Nishit

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

    hi dipak

  • http://www.facebook.com/people/Dipak-Panchal/100002167412765 Dipak Panchal

    Hi