Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Ruby JavaScript
branch: master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
app
config
db
doc
lib/tasks
public
script
test
vendor/plugins
Gemfile
Gemfile.lock
README.textile
Rakefile
config.ru
textmateProj.tmproj

README.textile

Building the Group Hug Rails app

These steps show you how to build the Group Hug Rails application and deploy it on the Engine Yard Cloud. If you prefer video over written tutorials, you can view some helpful screencasts here.

Table of Contents

  1. Install Ruby, Rails, and create the app
  2. Create a Facebook application
  3. Configure Facebooker in Rails
  4. Creating the Layout
  5. Adding the Facebook Connect button
  6. Tying in the database
  7. Users, Sessions, and Logins
  8. List the user’s Facebook groups
  9. Using JQuery to save the Default Group
  10. Showing posts
  11. Get Group Messages, and Profile pictures using Mogli
  12. Post Messages to Facebook
  13. Implementing pagination
  14. Tying up loose ends
  15. Creating the second Facebook app for production
  16. Deploying on Engine Yard

A little Warning

Before you start this tutorial, you really, really, truly, deeply, sorta, kinda have to login to the application and use it. It’s the only way to understand what you will be building.

Install Ruby, Rails, and create the app

top

  • On OSX or Linux, you can use RVM by entering the following in your terminal ( see prerequisites here ):

curl -O -L http://rvm.beginrescueend.com/src/bootstrap_rails_environment && source ./bootstrap_rails_environment

  • Create rails app:

rails new grouphug

  • Switch to the grouphug directory

cd grouphug

rails plugin install git://github.com/mmangino/facebooker2.git

What’s Facebooker2 Watch a screen cast by Christopher Johnson

Create a Facebook application

top

  • We need to create two Facebook applications, one for development, and one for production. Name the first one something fun, with _development at the end. We called ours icanhazrails_development.

fb create app

  • Click Create App
  • Go to the Website settings, and set the site URL to the IP address of your computer, along with port 3000, which is the port that Rails uses for its test server. Note: make sure, if you are on a wireless network, that your firewall settings, and IP / Port forwarding are properly configured.

fb website settings

Configure Facebooker in Rails

top

  • Copy the settings from your Facebook application page. To find your settings, go to the facebook developer page and click on the application you created. The page should appear as follows:

fb app settings

  • Create config/facebooker.yml and paste the settings from your application page as follows (for now keep the production and development settings the same. You will change the production settings when you deploy).

development:
  app_id: your application id
  secret: your application secret
  api_key: your application key
production:
  app_id: your application id
  secret: your application secret
  api_key: your application key

  • Create config/initializers/facebooker2.rb and place the following line in it.

Facebooker2.load_facebooker_yaml

  • Add the following line to app/controllers/application_controller.rb

include Facebooker2::Rails::Controller

Creating the Layout

top

  • In rails, there is a special Layout in app/views/layouts/application.html.erb. This file will be sent to the browser no matter what page is sent. It’s the perfect place to put headers and footers. There is already some standard HTML in that file, placeholders for style sheets, javascript, metatags, and a Ruby yield method as the placeholder for running the actual page that was requested.

<!DOCTYPE html>
<html>
<head>
<title>Grouphug</title>
<%= stylesheet_link_tag :all >
<= javascript_include_tag :defaults >
<= csrf_meta_tag >
</head>
<body>
<= yield %>
</body>
</html>

Adding the Facebook Connect button

top

  • Put some more code into app/views/layouts/application.html.erb to display the Facebook login button on every page. The conditional logic will replace the login button with the user’s name, if he/she is already logged in. Here we are using embedded ruby. Put this code above the yield method:

<%# include JavaScript to use facebook connect%>
<%=fb_connect_async_js%>
<%if current_facebook_user !=nil%>
<%="Welcome #{@current_user.name}!" if @current_user !=nil%>
<%=fb_logout_link("Logout", request.url)%><br />
<%else%>
<%=fb_login_and_redirect( url_for(:action => ‘create’, :controller => ‘user’, :only_path => false), :perms => ‘email,user_groups,read_stream, publish_stream’)%>
<%end%>

What’s Embedded Ruby? Watch a screen cast by Christopher Johnson

Tying in the database

top

  • Create the model to store application users

rails generate model User

What’s a Model? Watch a screen cast by Ryan Bigg

  • Then create a database migration file by typing

rails generate migration create_users

This creates a file in the folder db/migrate/ named something like 20110203050933_create_users.rb

class CreateUsers < ActiveRecord::Migration
 def self.up
  create_table :users do |t|
  t.string :email, :name, :facebook_id, :facebook_session_key, t.timestamps
  end
 end
 
 def self.down
  drop_table :users
 end
end

  • Run the migration

rake db:migrate

Users, Sessions, and Logins

top

  • Create the user controller and login action

rails generate controller User

  • Open the page you just created app/controllers/user_controllers.rb
  • Create an empty action for the login page, this will just show the login button, which we already put on the application.html.erb page.

def login
end

  • Create two other methods in the same controller, underneath the login action. The first action will be called create and it will create a session. The second one will be called create_via_facebook_connect and it is called from the former method to grab the user id for this facebook user from the local database, or create a new record if the user has never signed into group hug before.

def create
 @user = User.find_by_email(params[:email])
 create_via_facebook_connect if @user.nil?
 if @user != nil
  session[:user_id] = @user.id
  redirect_to url_for(groups_path)
  session[:return_to]=nil
 else
  flash[:error] = “Unable to log you in”
  render :action=>"login"
 end
end
 
def create_via_facebook_connect
 if current_facebook_user
  #look for an existing user
  @user = User.find_by_facebook_id(current_facebook_user.id)
  if @user.nil?
   #if one isn’t found – fetch user data via Mogli lib and create new user
   current_facebook_user.fetch
   @user = User.new(:name => current_facebook_user[:name], :email => current_facebook_user[:email], :facebook_id => current_facebook_user[:id], :facebook_session_key => current_facebook_client.access_token)
   @user.save
  end
 end
end

  • Create an empty view for the login page ( app/views/user/login.html.erb ). Remember, the login button is actually in the application.html.erb page, which puts it on every view.
  • Edit the view as follows:

def create
 @user = User.find_by_email(params[:email])
 create_via_facebook_connect if @user.nil?
 if @user != nil
  session[:user_id] = @user.id
  redirect_to url_for(groups_path)
  session[:return_to]=nil
 else
  flash[:error] = “Unable to log you in”
  render :action=>"login"
 end
end
 
def create_via_facebook_connect
 if current_facebook_user
  #look for an existing user
  @user = User.find_by_facebook_id(current_facebook_user.id)
  if @user.nil?
   #if one isn’t found – fetch user data via Mogli lib and create new user
   current_facebook_user.fetch
   @user = User.new(:name => current_facebook_user[:name], :email => current_facebook_user[:email], :facebook_id => current_facebook_user[:id], :facebook_session_key => current_facebook_client.access_token)
  @user.save
  end
 end
end

  • After successful login users will go to groups controller. Let’s create a groups controller first:

rails generate controller Groups

  • Now we can edit config/routes.rb to send people to the groups controller whenever a url is requested with the verb ‘groups’ after the domain name. For instance http://icanhazrails.com/groups will load the controller app/controllers/groups_controller.rb. You created that controller when you typed the generate command in the last step.

resources :groups

  • Set the home page to login

resources :groups root :to => “user#login”

  • Use standard ‘<controller>/<action>’ routes by uncommenting the line in your config/routes.rb

match ‘:controller(/:action(/:id(.:format)))’

  • Add methods to ApplicationController ( app/controllers/application_controller.rb ) to check if the user has logged in and determine his/her user id. Things in the ApplicationController are available to every controller in the application.

def current_user
 if session[:user_id]
  @current_user ||= User.find(session[:user_id])
 elsif current_facebook_user and @current_user.nil?
  @current_user = User.find_by_facebook_id(current_facebook_user.id)
 end
end

  • Next, we make the above code a helper method. This is a bit of trickery in that usually helper methods are created in their own files under the helpers directory and are more useful in views, rather than controllers, because they are visual. We will create non-tricky helper methods in a few minutes. This tricky one is turned into a helper method using the helper_method method.

helper_method :current_user

  • Also in the ApplicationController is a method for checking if the user is logged in.

def login_required
 if current_user.nil?
  flash[:notice] = “You must login to access this page”
  session[:return_to] = request.request_uri
  redirect_to :controller =>’user’, :action =>’login’ and return
 end
end

  • Next we edit the groups controller app/controllers/groups_controller.rb. The first thing we will do is add the before_filter method, which basically intercepts every request to the controller, and runs certain method before anything else has a chance. In this case, we are going to run the login_required method to make sure the user is logged in.

before_filter :login_required

List the user’s Facebook groups

top

  • To grab the user’s Facebook groups, define the default behavior (index) for the group controller. Note that we have some extra code in there that let’s the user set a default group, which we will save in the database a little later in the tutorial.

def index
 #get current users groups
 @groups = current_facebook_user.groups
 #determine selected user group or get first group id if default group is not selected
 @default_group = @current_user.default_group
 @default_group = @groups.first.id if @default_group.nil? && @groups.first != nil
 if (params[:current_group] != nil)
  @current_group = params[:current_group]
 else
  @current_group = @default_group
 end
end

  • Open the groups view ( app/views/groups/index.html.erb ). This was automagically created when you generated the controller.
  • Add some HTML and embedded ruby to this view. This creates a select box of all the groups the user has on Facebook. The inner HTML of the posts container will be replaced with the group posts from Facebook using JQuery in a minute.

<select name="group" id="group">
<% @groups.each do |group| >
<option value="<=group.id%>" <%= ‘selected’ if @default_group == group.id >><=group.name%></option>
<% end %>
</select>
<br/>
<input id="def_group" name="def_group" type="checkbox" selected="false" /> Default group <br/>
<div id="posts_container" >Posts</div>

Using JQuery to save the Default Group

top

  • We will use JQuery to communicate with Facebook when a user selects their default group using the checkbox.
  • Install jQuery via the instructions at https://github.com/lleger/Rails-3-jQuery/

How does JQuery work? Watch a screen cast by Matt Dolian

  • We need to tell Rails to load any Javascript (JQuery in particular) that is sent to the page. You will see how to send it in a moment. Right now, we’re just receiving it. Go back and edit the app/viewslayouts/application.html.erb and put this up in the header of your HTML document.

<script>
<%= yield (:javascript) %>
</script>

  • Now put some JQuery on the group view ( app/views/groups/index.html.erb ). We send it back to that yield statemement, above, using the content_for method like this:

<% content_for (:javascript) do >
var def_group = ’<=@default_group%>’;

  • We also set a little Ruby code inside of the JQuery script for the default group. This value comes from the groups_controller. Hopefully you are seeing, now how controllers send data to views.
  • Under this last chunk, we put the JQuery code, which handles events for when the user clicks the checkbox to select their default group.

 $(document).ready(function() {
  // bind checkbox change
  $(‘#def_group’).bind(‘change’, function() {
   var group_id = $(this).is(‘:checked’) ? $(‘#group’).val() : ‘nil’
   $.post(‘/groups/set_default/’ + group_id , function() {
    alert (‘Default group was saved’);
   });
   //chenge default group
   def_group = group_id == ‘nil’ ? null : group_id;
  });
 });
<% end %>

  • So now we have combo box with all user groups.
  • Notice that the JQuery code posts the default group_id to the groups controller, and calls the set_default method. Let’s build this method in GroupsController ( app/controllers/groups_controller.rb )

def set_default
 render :json => { :result => @current_user.update_attributes(:default_group => params[‘id’] == ‘nil’ ? nil : params[‘id’]) }
end

Showing posts

top

  • Lets use nested resources to relate groups and posts. Open your config/routes.rb file and add the following:

resources :groups do
 resources :posts
end

  • Using JQuery, we will bind the select list to the post container, meaning that every time the select box is changed, the inner html of the post container will be replaced with messages from that Facebook group.
  • We put this code in the document.ready block, underneath the code you wrote a few steps ago in app/views/groups/index.html.erb

// bind select group
$(‘#group’).bind(‘change’, function() {
  $(‘#posts_container’).html(‘<p>Loading…</p>’);
  loadData(‘/groups/’ + $(this).val() + ‘/posts’, null)
  //check if current group is default and mark checkbox checked or not
 $(‘#def_group’).attr(‘checked’, $(this).val() == def_group);
});
}); //ends the document.ready block

  • In the same page ( app/views/groups/index.html.erb ) create another function to load the data

function loadData(rem_url, params) {
 var temp = rem_url;
 if (params != null ) {
  temp += ‘?’ + params;
 }
 $(‘#loading_div’).show();
 $.ajax({ url: temp,
  dataType: ‘html’,
  type: ‘GET’,
  error: function(req,data) {
   alert("Error getting remote data: " + data);
   $(‘#loading_div’).hide();
  },
  success: function(data) {
   $(‘#loading_div’).hide();
   $(‘#posts_container’).html(data);
   FB.XFBML.parse(document.getElementById(‘posts_container’));
  }
 });
}

The Facebook magic in this JQuery script is well documented at the Facebook Developer page here:

  • Finally, we add some code (same page still) that will display the default group when the page loads:

$(‘#posts_container’).load(‘/groups/<%=@default_group%>/posts’, function() {
 FB.XFBML.parse(document.getElementById(‘posts_container’));
});

Get Group Messages, and Profile pictures using Mogli

top

  • Mogli is a Ruby Gem that is installed with Facebookr2 as one of its dependencies. It is the Facebook Open Graph Library for Ruby. You can read more about Open Graph here. In a nuthsell, Open Graph lets Ruby developer integrate their websites with Facebook, so that users don’t have to visit Facebook to interact with it. We use Mogli to retrieve the posts for the selected group, as well as attributes like profile pictures.
  • Here, we grab messages for the selected group app/controllers/posts_controller.rb.

def index
 group = Mogli::Group.new({:id=>params['group_id']}, current_facebook_client) &nbsp;group.fetch
 #getting posts for page 0
 @posts = @group.feed
 render :layout => false
end

  • Then we show the messages, along with facebook profile pics in app/views/posts/index.rb:

<% @posts.each do |post| >
 < begin >
  <div class=“post”>
  <span class=“pic”><fb:profile-pic uid="<=post.from.id if post.from !=nil%>" linked=“false” size=“q”></fb:profile-pic><br/>
  <% from = post.from.fetch if post.from !=nil > <=from[:first_name] if from != nil > <=from[:last_name] if from != nil >
  </span>
  <span>
  <=post.message%><br/>
  <%= time_ago_in_words(post.updated_time) > ago
  </span>
  </div>
 < rescue >
 < end >
< end %>

Post Messages to Facebook

top

  • At the top of the page (above the code you just inserted), create a link to post a new message in app/views/posts/index.rb:

<%= link_to “Post”, url_for(new_group_post_path(@group.id)) %>

  • Before we display a form to post messages to Facebook groups, we need to figure out which group is the currently selected one. We do this on app/controllers/posts_controller.rb.

def new
 group = Mogli::Group.new({:id=&gt;params['group_id']}, current_facebook_client) &nbsp;group.fetch
end

  • Then we create a simple view for new posts in app/views/posts/new.rb:

<h1>Post a wall message to: <%= group.name %&gt;&lt;/h1&gt; &lt;% form_tag url_for(group_posts_path(group.id)), :method=>:post do -%>
 <%= hidden_field_tag ‘group_id’, @group.id >
 <div class=“field”>
  <= text_area_tag ‘message’, nil, :rows => 10, :cols => 60 >
 </div>
 <div class=“field”>
  <= check_box_tag ‘send_via_email’, ‘yes’, true > Send the whole group an email
 </div>
 <div class=“actions”>
  <= submit_tag ‘Save’ >
 </div>
< end -%>
<%= link_to ‘Back’, url_for(groups_path) %>

  • When the form is submitted, it will post to the create action to create action in app/controllers/posts_controller.rb

def create
 group = Mogli::Group.new({:id=&gt;params['group_id']}, current_facebook_client) &nbsp;group.fetch
 if current_facebook_user
  current_facebook_client.class.post(current_facebook_client.api_path(@group.id + ‘/feed’),
   :query=>current_facebook_client.default_params.merge(
    {:name => “#{current_facebook_user.name} Post a message using app!”,
    :link=>‘http://staging.operations.engineyard.com/groups’,
    :message=>params[‘message’]}))
 end
 redirect_to groups_path(:current_group => params[‘group_id’])
end

  • Run your app via:

rackup

Implementing pagination

top

  • The Facebook Graph API return only the first 25 posts at a time. Here, we will provide links to get the next and previous items. First, we implement server side functionality to return json data. We do this by modifying the index method. this in app/controllers/posts_controller.rb.

def index
 group = Mogli::Group.new({:id=&gt;params['group_id']}, current_facebook_client) &nbsp;group.fetch
 #getting posts for page 0
 posts = @group.feed &nbsp;page = 0
 if params[:next]
  #if ‘next’ parameter passed – lets get page to display
  page = params[:next].to_i &nbsp;&nbsp;# and get api url to get next or previous page &nbsp;&nbsp;rest_url = (session[:page] &lt; @page) ? session[:posts_next] : session[:posts_prev] &nbsp;&nbsp;# getting posts and map them to appropriate Mogli class &nbsp;&nbsp;posts = current_facebook_client.get_and_map_url(rest_url,@posts.classes)
 end
 # setting data to session to get url if the user want to see next
 session[:posts_next] = @posts.next_url
 session[:posts_prev] = @posts.previous_url
 session[:page] = @page
 # render template to string
 html = render_to_string :template => “posts/index.html.erb”, :layout => false
 # produce json output with html, next and previous pages.
 render :json => {
  :html => html,
  :next => @page + 1,
  :prev => @page > 0 ? @page – 1 : nil
 }
end

  • Let’s also modify the HTML for the groups page. This is located in app/views/groups/index.html.erb. We add a little spinny thing (which you can grab from our github repo, along with all of the source code for this application). We also add the previous and next buttons.

<div id=“posts_container” ></div>
<div id=“loading_div” style=“display:none” ><%= image_tag “loading-spinner.gif”, :alt => “Loading” %></div>
<div>
 <input type=“button” value=“Previous” id=“prev_btn” class=“prvnxt” style=“display:none”> <input type=“button” value=“Next” id=“next_btn” class=“prvnxt” style=“display:none”>
</div>

Tying up loose ends

top

  • Let’s just check that our JavaScript is doing everything it needs to be in app/views/groups/index.html.erb. Gets default group. Check. Loads data. Check. Checks if current is default and marks the checkbox. Hmm, did we overlook that? Ok, well it’s below. Send a clicked checkbox to the set_default method in groups controller. Yep.

<% content_for (:javascript) do >
 var def_group = ’<=default_group%&gt;'; &nbsp;$(document).ready(function() { &nbsp;&nbsp;// bind select group &nbsp;&nbsp;$('#group').bind('change', function() { &nbsp;&nbsp;&nbsp;$('#posts_container').html('&lt;p&gt;Loading...&lt;/p&gt;'); &nbsp;&nbsp;&nbsp;loadData('/groups/' + $(this).val() + '/posts', null) &nbsp;&nbsp;&nbsp;//check if current group is default and mark checkbox checked or not &nbsp;&nbsp;&nbsp;$('#def_group').attr('checked', $(this).val() == def_group); &nbsp;&nbsp;}); &nbsp;&nbsp;// preload default group &nbsp;&nbsp;loadData('/groups/&lt;%=current_group%>/posts’, null);
  // bind checkbox change
  $(‘#def_group’).bind(‘change’, function() {
   var group_id = $(this).is(‘:checked’) ? $(‘#group’).val() : ‘nil’
   $.post(‘/groups/set_default/’ + group_id , function() {
    alert (‘Default group was saved’);
   });
   //chenge default group
   def_group = group_id == ‘nil’ ? null : group_id;
  });
 });

  • Also, we should add some Javascript that displays / hides the Next and Previous buttons appropriately. Whether they show up or not depends upon what comes back from the JSON package.

 function loadData(rem_url, params) {
  var temp = rem_url;
  if (params != null ) {
   temp += ‘?’ + params;
  }
  $(‘#loading_div’).show();
  $.ajax({ url: temp,
   dataType: ‘json’,
   type: ‘GET’,
   error: function(req,data) {
    alert("Error getting remote data: " + data);
    $(‘#loading_div’).hide();
   },
   success: function(data) {
    $(‘#loading_div’).hide();
    $(‘#posts_container’).html(data.html);
    if (data.next != null) {
     $(‘#next_btn’).show();
     $(‘#next_btn’).unbind(‘click’);
     $(‘#next_btn’).click(function () {
      loadData(rem_url, ‘next=’ + data.next);
     });
    } else {
     $(‘#next_btn’).css(‘display’, ‘none’);
    }
    if (data.prev != null) {
     $(‘#prev_btn’).show();
     $(‘#prev_btn’).unbind(‘click’);
     $(‘#prev_btn’).click(function () {
      loadData(rem_url, ‘next=’ + data.prev);
     });
    } else {
     $(‘#prev_btn’).css(‘display’, ‘none’);
    }
    FB.XFBML.parse(document.getElementById(‘posts_container’));
   }
  });
 }
<% end %>

Creating the second Facebook app for production.

top

  • Go back and create another Facebook application for your production application. Name it the same thing minus the _development bit.

fb app settings again

  • Go back to the facebooker.yml file. Under your development settings, set your production settings.

development:
  app_id: your DEVELOPMENT application id
  secret: your DEVELOPMENT application secret
  api_key: your DEVELOPMENT application key
production:
  app_id: your PRODUCTION application id
  secret: your PRODUCTION application secret
  api_key: your PRODUCTION application key

Deploying on Engine Yard

top

  • We will expand this section in upcoming weeks.
Something went wrong with that request. Please try again.