a facebooker tutorial: launch an app with rails
Actually going somewhere with facebook and the rails facebooker plugin
Ok, so this isn't a complete how-to, or a all-inclusive guide, but hopefully it should cover the basics, and explain the tougher bits. Unfortunately developing for facebook doesn't (to me) feel as easy as it could. Facebook themselves went ahead and took the liberty of inventing, uhm, 4 Domain Specific Lanugages that you have to learn and then the facebooker library itself abstracts the real API calls into ruby-friendly goodness, bringing the total amount of information to absorb fairly high.
Thanks to recent work by the facebooker crew, integration with rails is relatively painless. If you are not a reader, but a doer, click this link to add David Clement's Fackbooker Tutorial Application to your facebook account.
I'm not going to take you on the 'how facebook applications work' ride. You can hop on that somewhere else. Oh, and if you are serious about getting an app going go join the facebooker list, cause you'll need the help
Oh, and this is for Rails 2.0.2 and facebooker as of January 2008. So, if you are in the future, maybe some stuff has changed (silver suits, melted icecaps, flying cars). Even in February some major changes have happened. Be prepared for change.
On facebook
Get on facebook. Add the developer application, and create a new facebook app. Edit the settings. They look like this:

Note the Callback Url. See how it is your domain there? Good. Keep it like that. If you are coming from rFacebook to facebooker, don't freak out, you want your plain jane domain there, bub.
On rails
Install the plugin
1 |
script/plugin install http://facebooker.rubyforge.org/svn/trunk/facebooker/
|
You'll get a file called config/facebooker.yml
This is different from the facebooker past, where you had to add manual config to an initializers file, etc. It is done when you install the plugin, so jump in and edit that file.
Routes
Ok. Let's start with routes. Pretend you built your app already. Wow, that was easy. Ok, now, someone added your application and is cruising your app inside facebook. They are digging it (maybe literally).
Did you work with rFacebook before? Then get this in your skull - don't try and play with callback_paths, canvas_paths, etc. Forget about it. It is easy. All incoming calls from facebook with your app's name on it are going to hit the root of your rails app. The root. What you say? You already have other stuff mapped to root? Yeah, cool. But you don't have other FACEBOOK stuff mapped to root, and so there is no need to worry. facebooker does a great job at seperating normal calls from facebook calls, so don't worry if you are overlapping your mapping and routes...the routing is smart and knows when it is talking with facebook.
Just remember: facebooker is hot. It makes life easier than you think. It is best you forget about the hard stuff and just write your rails code.
So, try this out in routes.rb
1 |
map.resources :profiles, :conditions=>{:canvas=>true} |
Yum! That looks familiar. That will give us create, update, destroy, and all that other stuff. That will generate fake restful routes. Fake? Fake because really facebook only will issue POSTs to your app. Restful because uhmm....they fake you out and look restful. Silly, but hey, easy to remember. Actually, forget I said anything. They are restful, ok? The only way this differs in reality from real rails restful routes (rrrrrr!) is that you are adding a condition: Canvas has to be true before this route will do anything for you. Where you do set canvas? Nowhere, facebooker handles that part. Just remember...if you want a route to respond to in-canvas calls, add the condition. If you want the route to respond to regular rails calls, uhm, just keep doing your thing as you normally would.
Ok, wait. There is one other difference with facebook routes. Because they only WANT TO BE restful, we can't use the same exact url helpers in all cases. Instead, we have to be a bit more explicit. So, if you want to get the url for deleting a profile it is destroy_profile_path(@profile). Get it? Here is an example:
1 2 3 4 5 6 7 8 9 10 |
# Generates the following routes: # # new_profile POST /profiles/new {:controller=>"profiles", :action=>"new"} # profiles POST /profiles/index {:controller=>"profiles", :action=>"index"} # show_profile POST /profiles/:id/show {:controller=>"profiles", :action=>"show"} # create_profile POST /profiles/create {:controller=>"profiles", :action=>"create"} # edit_profile POST /profiles/:id/edit {:controller=>"profiles", :action=>"edit"} # update_profile POST /profiles/:id/update {:controller=>"profiles", :action=>"update"} # destroy_profile POST /profiles/:id/destroy {:controller=>"profiles", :action=>"destroy"} |
Now remember, this call to map.resources ain't no hack on the inside, so you can do all your same fancy tricks as always, such as:
1 |
map.resources 'facebook', :controller => 'facebook_accounts', :member => {:add_thingy_to_profile => :post}, :collection => {:search => :get}, :conditions =>{:canvas => true} |
Oh, and normal routes work fine. For example, this might be a good time to map the root of your app to a facebook-friendly controller.
1 |
map.facebook_home 'facebook_home', :controller => 'facebook_accounts', :action => 'index', :canvas => true |
Controller stuff
Ok, so in the controller, you get two before filters. They are:
1 2 |
ensure_authenticated_to_facebook ensure_application_is_installed_by_facebook_user |
I'm not going to explain what they are because I know how smart you are. It is strange because you don't see 'before_filter' there, but don't worry, you can still do this:
1 |
ensure_authenticated_to_facebook :only => [:create, :update, :destroy] |
Ok, I will explain, just in case you are new. What happens with that before filter? If the user does not have the app installed, a magic redirect to login and/or install your application will occur.
What if you just want to check if the user has the app installed, but NOT redirect. You know, peep the situation a bit. Maybe you allow everyone to visit your facebook app's main page, but want to display special stuff for those that have installed your app:
1 |
application_is_installed? |
Everything you need is in the session
I'll repeat that. All fancy dancy calls you need to make to the facebook API you are going to do from the session. Like so:
Want to grab the friends of the current user?
1 |
@current_friends = session[:facebook_session].user.friends! |
The friends! method (with exclamation) will not only grab the friends, but grab the friends info (name, etc) and cache it in memory because it knows how often you need to check to see if you have friends, remember where they live, and remember how many you have.
Wont to set the user's profile box code? This will do it if you have a profile partial:
1 |
session[:facebook_session].user.profile_fbml = (render_to_string :partial => 'profile') |
See, you have to render to string, because you are actually posting that data for facebook to store on it's servers.
redirects are smart (of course), so you can do
1 |
redirect_to facebook_user_path |
It knows if you are in the facebook canvas (aka, your app is running within facebook) to redirect to a proper in-canvas url (like http://apps.facebook.com/yourapp/facebook_user/345 )
Organizing your app
I'm not going to tell you how to run your ship. My facebook app is in the same app as a larger rails app. So what did I do? Just created a new model and controller called facebook_accounts, and put all my facebook related stuff there. I have a little before filter that runs on all actions:
1 2 3 4 |
def find_facebook_user @facebook_user = session[:facebook_session].user @facebook_account = FacebookAccount.find_or_create_by_fb_user_id(@facebook_user.id) end |
Actually, my facebook controller isn't very restful, but I don't really care. I dont need it to be. M
Useful things
You probably want to save some of the facebook junk inside your trunk.
Well, you are going to need a big trunk. Facebook has a lot of users, and you'll need to get all BIGINT on it if you want to store facebook ids in your DB.
As far as I know, facebooker doesn't help you out with models. I think it should be kept that way. Personally, my users are my users, and my facebook_users are my facebook_users.
If you are storing the facebook users, you need to BIGINT the DB column that is storing the facebook user id. Add the following to a migration, but make sure you specify whatever table and column YOU are using. You probably have something more sane than 'fb_user_id' as your column name, right? Good. This is what it looks like for MySQL:
1 |
execute("alter table facebook_accounts modify fb_user_id bigint")
|
Views
Same story. Rails is smart, facebooker is smart. What do you want to generate when inside the facebook canvas? FBML. Ok, so name your views like so:
1 |
profile.fbml.erb |
or if you are funky and like HAML
1 |
profile.fbml.haml |
rFacebook users, don't flinch. You don't need to do a single other thing (like set a before filter or manually set the format, or any of the Giant Robots rFacebook tutorial stuff). Just name your views right.
In the views, link_to will detect if you are in facebook canvas or not and write the appropriate path. Same with image_tag. If you are running a normal rails app together with facebook stuff....no worries, it is smart, like we said. It won't touch your link_to, buddy.
There are a handful of special view helpers to help you avoid dealing with FBML. Most notably, facebook_form_for, fb_profile_pic...uhm...fb_pronoun...yeah, the list goes on, check Facebooker::Rails::Helpers and don't try and write all that FBML bullshit by yourself. Use ruby, dude(ette). Actually, do FBML if you like that. It is your choice - poke around the facebook documentation and write FBML or poke around rdoc (yuk) and write rubyish things.
Flash Messages
If you know rails, you know flash[:error] and flash[:notice]
Use those as normal in your controller, and place this in your views:
1 |
<%= facebook_messages %>
|
You may find yourself backing away from using linkto, and using urlfor more often because of the way facebook demands forms and stuff like that. Please note that if the page being rendered is being rendered in canvas, you will always get your canvaspath prepended to the url. Say that ten times fast. That means linkto is always going to keep you in the canvas, and won't let you link to your main rails app or anywhere outside of http://apps.facebook.com/yourappname. If you DO want to escape the jail that the facebook canvas is (for example linking to an external file, a download, whatever) then you can use the option :canvas => false like so:
1 |
url_for :controller => 'blah', :action 'blee', :canvas => false |
One strange thing about the facebook_form_for is that it is a bit unintuitive if you want to build an url from a hash (not a helper):
1 |
facebook_form_for :asset, asset, :url => (url_for(:controller => 'profile', :action => 'add_to_profile', :id => asset.id)) |
Didn't quite get why it is like that. The code seems to say that whatever goes to the :url option gets passed directly to the form action.
Try it out, the smart way
The facebooker rails plugin also comes with a tunnel task. rFacebook folks know about this: facebook has it too, don't worry. If you are new, listen up, this is a big deal: This lets you develop your facebook app locally, but view and test out your app live on apps.facebook.com. Get it? You don't have to deploy anything, though you do need to have a production server setup somewhere and then generate this config file.
1 |
rake facebooker:tunnel:configure to create a blank config in config/tunnel.yml |
Edit that file, then run
1 |
rake facebooker:tunnel:start |
What this does is route all requests from your production server to your local machine. A proxy. It means, when facebook comes asking for something, your little localhost-that-could is serving it what it needs.
For this to work, you need to go and edit your facebook application's settings (on facebook itself) and make sure the callback url to be http://yourpublicsite.com:8888 like I did -replace the 8888 with whatever port you want to be using. Also, you may want to change the ssh timeout on your server so that it can just hang there forever. Otherwise you may get booted from the server if you wait a whole gosh darn 2 minutes between requests.
Nothing to do with shame, but indeed a pliggity plug
Hey, I learned facebooker and wrote this article to do one thing. Build my baby alonetone, a kickass home for musicians to upload and share their tunes. Musicians are kinda screwed these days and being taken advantage of left and right. DIY musicians and independent musicians have very little in the way of real, down home options. alonetone is an alternative to the sketchy other musician homes out there. It cost nothing, and yet music makers get everything they need (and nothing they don't need). We don't run ads, it is built by a musician (me) and is non-commercial (I'm personally losing money).
If this article helped you out, do me a favor and the next time a musician friend asks you where s/he can upload their stuff, tell them alonetone.com will give them a good home.