has_secure_password with Rails 4.1

I just started a new project with Rails 4.1 and explored the has_secure_password feature. Really awesome stuff!

I hope you are not storing passwords in clear text to your database! You should always store some kind of hashed values instead of clear text passwords. In case somebody steals your database he/she still doesn’t has the passwords.

There are a couple good tutorials how to hash and store passwords in a secure way. I implemented it a couples times by myself with Ruby.

A more sophisticated solution is devise, a very robust gem for security and authentication.

However. Since Rails 3.1 you can take advantage of has_secure_password. This mechanism in Rails takes care of password validation and encryption. It requires a field ‘password_digest’ in your model, where it will store the encrypted password. Let’s generate a simple model.

rails g model user username:string password_digest:string

Let’s add this line to the user model.

has_secure_password

This will add an attribute password and password_confirmation to your model. This 2 fields are now part of your model but not part of the database schema! Because we don’t want to store cleartext passwords.

Let’s add some tests.

require 'spec_helper'

describe User do

  it "fails because no password" do
    User.new({:username => "hans"}).save.should be_false
  end

  it "fails because passwrod to short" do
    User.new({:username => "hans", :password => 'han'}).save.should be_false
  end

  it "succeeds because password is long enough" do
    User.new({:username => "hans", :password => 'hansohanso'}).save.should be_true
  end

end

3 very simple tests. Persisting a new user without password should fail. Persisting a new user with too short password should fail as well. And creating a new user with a long password should succeed. If you run this tests with RSpec the 2nd test will fail. By default Rails doesn’t has a validation for the length of the password. So let’s add it to our user model.

class User < ActiveRecord::Base

  has_secure_password
  validates :password, :length => { :minimum => 5 }

end

If you run the tests again, they will be green. If you take a look into your database you will see that the user table/collection has a column password_digest with a very cryptical value. But there are no columns for password! That’s exactly what we wanted.

Now lets do the authentication. Assume a new user signed up at your portal and now he wants to login. This is how you authenticate him.

user = User.find_by_username("USERNAME").authenticate("CLEAR_TEXT_PASSWORD")

If the username and the clear text password from the HTTP Request is correct it will return a valid user from the database. Otherwise it will return nil!

has_secure_password validates the password on creation time, the very first time you persist it. It doesn’t check the password field after that, for updates for example. And that’s OK. Because that means you can load a user later from db, change it and persist it without knowing the password.

Another feature of this mechanism is password confirmation. has_secure_password also adds an attribute password_confirmation to your model. This attribute gets only validated if it’s not nil. If it’s not nil it must be equal to the password attribute. Let’s add 2 more tests for that one.

  it "fails because password confirmation doesnt match" do
    User.new({:username => "hans",
      :password => 'hansohanso',
      :password_confirmation => 'aa'}).save.should be_false
  end

  it "succeeds because password & confirmation match" do
    User.new({:username => "hans",
      :password => 'hansohanso',
      :password_confirmation => 'hansohanso'}).save.should be_true
  end

To make this tests pass you have to add one more line to the model.

class User < ActiveRecord::Base   
  has_secure_password   
  validates :password, :length => { :minimum => 5 }
  validates_confirmation_of :password
end

The line “validates_confirmation_of :password” will check the password confirmation.

Rails doesn’t force you to have a password confirmation for your model, but if you want it you can turn it on.

I really like this feature because it saves me a lot of code and development time. And for most applications this is really enough.

Let me know what you think about this, either in the comments or on Twitter.

Ruby on Rails + ElasticSearch

This is a tutorial how to use ElasticSearch with Ruby on Rails. ElasticSearch is a distributed RESTful Search Engine build on top of Apache Lucene.

Sure! You can use your SQL database for search. But that is usually slow and you will not get very good search results. With ElasticSearch you can deliver a fuzzy search. Let’s say you have a record “Hibernate” in the database. If somebody is doing a search for “hibernate” you will get a match with a simple SQL query. But what if your customers input looks like this:

  • hibernate 3.2
  • hibernate.jar
  • hibernate.jar 3.5

In this cases you will have 0 results with a simple SQL query. With ElasticSearch you would still have results. Depending on your configuration. So let’s start!

ES

Just download the current version from here: http://www.elasticsearch.org/download/. Unpack it and run the ES server with this command:

./bin/elasticsearch -f

The ES server is now running on localhost:9200. If you type in “http://localhost:9200/_search” into your browser you should get some basic results.

I assume that you know already Ruby on Rails and ActiveRecord. Of course there is a GEM to interact with ElasticSearch. Checkout the Tire GEM. That is a pretty good wrapper for ElastisSearch. Just add it to your Gemfile.

gem 'tire', '0.5.4'

and run:

bundle update

In your application.rb you need to require the new package:

require 'tire'

And in environment.rb you need to init the package:


begin
  Tire.configure do
    logger STDERR
    url Settings.elasticsearch_url
  end
rescue => e
  p "Wrong configuration: #{e}"
end

On the GitHub Page there is a description how to integrate Tire into your model. But honestly I don’t like that very much. That just blows up the model class. I prefer a clear separation between my models and the interaction with ElasticSearch.

The model I wanted to map and to make searchable with ElastiSearch is “product.rb”. It is located in “app/models/”. I created another directory called “app/elastics/”. And here I placed a new file “product_elastic.rb”, which is mapping my Product class to ElasticSearch and is responsible for the interaction with the ES server.

The first thing you have to do is to create a mapping. You have to map your properties from your model to ElasticSearch. This is how I did my first mapping:


def self.create_index_with_mappings
  Tire.index Settings.elasticsearch_product_index do
    create :mappings => {
      :product => {
        :properties => {
          :_id => { :type => 'string', :analyzer => 'keyword', :include_in_all => false },
          :name => {:type => 'string', :analyzer => 'snowball', :boost => 100},
          :description => { :type => 'string', :analyzer => 'snowball' },
          :description_manual => { :type => 'string', :analyzer => 'snowball' },
          :language => { :type => 'string', :analyzer => 'keyword'}
        }
      }
    }
  end
 end

The analyzers are documented on the ElasticSearch homepage: http://www.elasticsearch.org/guide/. Here is the magic happening 😉

Than I wrote 2 more methods (clean_all and reset) to delete the “product” index at ES and to create the mappings.


def self.clean_all
  Tire.index( Settings.elasticsearch_product_index ).delete
end

def self.reset
  self.clean_all
  self.create_index_with_mappings
end

Don’t call the reset method in production 😉 With this 3 methods you can delete old mapping and create a new one. You can now run the rails console and try out the methods. That should all work fine.

If creating the mapping was successful, the next step is to index the data in the database. To index a model, the model itself must offer a method which returns their values as JSON. I added a “to_indexex_json” method to the Product class:


def to_indexed_json
 {
   :_id => self.id.to_s,
   :_type => "product",
   :name => self.name,
   :description => self.description ? self.description : "" ,
   :description_manual => self.description_manual ? self.description_manual : "" ,
   :language => self.language,
   :group_id => self.group_id ? self.group_id : "",
   :prod_key => self.prod_key,
 }
 end

The first 2 attributes are required by Tire, to link the response from ES with your model. And here is the method in “product_elastic.rb” to index one record.


def self.index( product )
  Tire.index Settings.elasticsearch_product_index do
    store product.to_indexed_json
    product.update_attribute(:reindex, false)
  end
rescue => e
  p "ERROR in index(product) Message: #{e.message}"
  p "ERROR in index(product) backtrace: #{e.backtrace}"
end

And this here is the method to index all products from the DB:


def self.index_all
  Product.all.each do |product|
    ProductElastic.index product
  end
  self.refresh
end

def self.refresh
  Tire.index( Settings.elasticsearch_product_index ).refresh
end

Easy! Right? You can try the methods in the rails console. All right. You can now create mappings and index data. The only thing which is missing now is the search method. There is a lot to say about the search. I really recommend that you take your time and read the documentation about the search at the Tire homepage and the ES homepage. But here is one example.


def self.search(q, page_count = 1)
  if (q.nil? || q.empty?)
    raise ArgumentError, "query is empty! This is not allowed"
  end

  page_count = 1 if page_count.nil? || page_count.to_i < 1
  results_per_page = 30
  from = results_per_page * (page_count.to_i - 1)

  q = "*" if !q || q.empty?

  s = Tire.search( Settings.elasticsearch_product_index,
   load: true,
   from: from,
   per_page: results_per_page,
   size: results_per_page) do |search|

    search.sort { by [{:_score => 'desc'}] }

    search.query do |query|
      query.string "name:" + q
    end

  end

  s.results
end

The method above is with paging. You can easily use it with the “will_paginate” GEM.

There is much more to say about the mapping and the search. But this is the part there you have to invest time to figure it out, to deliver the search results you want. You will find a pretty good documentation on the ElasticSearch homepage: http://www.elasticsearch.org/guide/. And the core committer from the Tire project always responded in less than 24 hours to my tickets. The project support is very good.

Together with Timo I integrated ElasticSearch into VersionEye. Since them the search is much faster and the results are better. Even if there is no perfect match you will get results.

Rails ActiveRecord by-pass validation

ActiveRecord is my favorite way to access a SQL database. It offers very useful validation inside of a model. But sometimes you just want to save an attribute quickly, without validating the whole object. For that you can pass a parameter to the save method.

object.save(:validate => false)

That saved me a lot of time. Awesome!

Rails 3.X – “You are being redirected” – “406 Not Acceptable”

I used “redirect_to” in a controller to redirect the user to a different page. And I always got a:

“You are being redirected”

In the Browser. And a “406 Not Acceptable” in the console. I googled the whole day for this issue. In the end I found out that the problem was a new action in the controller. 2 days ago I added a method with the name “location”. I guess with this method name I have overwritten something in rails. As soon I renamed the method all my problems disappeared.

SSL in Ruby on Rails

There is a very good support for SSL in Ruby on Rails. Assuming that you installed a valid SSL certificat on your production server or on your PaaS Provider (Heroku), it is very easy to switch all links to https. If you want to force every link to https, just add this line to your production.rb

config.force_ssl = true

This is a good way to deliver the whole app over https. If you are writing an Online Banking Software, this is the way to go. Otherwise I would not recommend that.

There are several reasons not to deliver every page via https. The most important reason for me is speed. Usually the SSL Handshake between Server and Browser takes 50 to 100 ms. But in a bad case it can take 600 ms. That is a big performance issue.

I recommend to enable https just for certain pages. For example for Registration Pages, Login Pages and Settings Pages. That can be achieved pretty easy by adding this line to the controller.

force_ssl

it is like a filter. You can customize it with “only” and “except”.


class SessionsController < ApplicationController

  force_ssl :only => [:new, :create]

  def new
    @title = "Sign in"
  end

  # ... more ruby code here

end

This will force just certain links to https. If you want to switch back to regular http, you can use this filter here:


def force_http
  if request.ssl? && Rails.env.production?
    redirect_to :protocol => 'http://', :status => :moved_permanently
  end
end

Just add it to a controller like this:

before_filter :force_http

If you forced before everything to https and afterwards you decide to force some pages to http, than you can run in an recursive redirect issue. The Browser remembers that the pages is just available via https, but your server is redirecting to http. If you get this issue you have to clear your Browser cache.

Cron Jobs on Heroku

Heroku is an App Engine for Ruby on Rails … and other Languages and Frameworks. Heroku is running your application and you don’t have to care about hardware or IT-Infrastructure. It is one abstraction layer above the Amazon Cloud EC2.

First of all, there are no cron jobs on Heroku. Because it is an App Engine you don’t have access to the linux os and the native cron daemon or crontab. Forget it! You have to use heroku worker dynos. Keep reading.

Usually you push your rails app via git to Heroku like this:

git push heroku master

and then it will be deployed on x web dynos. There are different kind of dynos. The default is “web”. If you want to do some background jobs you need a “worker” dyno. You can add worker dynos with this command:

heroku ps:scale worker=1

Now you have to create a Procfile in the root of your application. That can look like this:

web: bundle exec rails server -p $PORT
worker: bundle exec rake do_work

The Procfile is defining the different types of dynos which are available for your application. With “heroku ps:scale” you can scale the dyno types. If you want to have 7 web dynos and 1 worker just execute this:

heroku ps:scale web=7 worker=1

And Heroku will immediately deploy your web application on 7 web dynos and your background job on 1 worker dyno. That is fucking awesome!

All right we added one worker dyno and we defined in the Procfile that “bundle exec rake do_work” should be executed on the worker dyno. Now we just have to define the task “do_work” in the Rakefile.

There are different GEMs for scheduling jobs. For example clockwork, rufus and Qu. I prefer a more simple/native way. With a couple lines of code you can write your own scheduler. This rake task here is executing jobs at a given time. In this example every day at 07:00 AM.


task :do_work => :environment do
  puts "START"

  start_hour = 7
  start_minute = 0

  until 2 < 1 do
    now = Time.now
    hour = now.hour
    minute = now.min
    if hour == start_hour && minute == start_minute

      puts " do work #{Time.now}"

      # execute code here !!!

      if Time.now.hour == start_hour && Time.now.min == start_minute
        sleep(60)
      end
    end
  end
end

Just replace the comment with your code. And push it to heroku

heroku maintenance:on
git push heroku master
heroku maintenance:off

That’s it.

Failed to connecto to primary node

If you try to connect to MongoDB Replica Set via MognoID and you get this error message here:

/opt/local/lib/ruby1.9/gems/1.9.1/gems/mongo-1.5.2/lib/mongo/repl_set_connection.rb:165:inconnect': Failed to connect to primary node. (Mongo::ConnectionFailure) from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongo-1.5.2/lib/mongo/repl_set_connection.rb:500:insetup'
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongo-1.5.2/lib/mongo/repl_set_connection.rb:144:in initialize' from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid/config/replset_database.rb:24:innew'
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid/config/replset_database.rb:24:in configure' from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid/config.rb:316:inconfigure_databases'
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid/config.rb:119:in from_hash' from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid/config.rb:136:inload!'
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/mongoid-2.3.4/lib/mongoid.rb:147:in load!' from /Users/reiz/workspace/versioneye/versioneye/config/application.rb:33:inclass:Application'
from /Users/reiz/workspace/versioneye/versioneye/config/application.rb:18:in <module:Versioneye>' from /Users/reiz/workspace/versioneye/versioneye/config/application.rb:17:in'
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/railties-3.1.0/lib/rails/commands.rb:52:in require' from /opt/local/lib/ruby1.9/gems/1.9.1/gems/railties-3.1.0/lib/rails/commands.rb:52:inblock in '
from /opt/local/lib/ruby1.9/gems/1.9.1/gems/railties-3.1.0/lib/rails/commands.rb:49:in tap' from /opt/local/lib/ruby1.9/gems/1.9.1/gems/railties-3.1.0/lib/rails/commands.rb:49:in'
from script/rails 
in require' from script/rails:6:in'

Than you should check out this link here: https://github.com/mongoid/mongoid/issues/1783#issuecomment-4319677Durran Jordan helped me to solve problem. Thx for that!

Rails + MongoDB ReplicaSet Configuration

This article shows how to configure a Ruby on Rails app for a MongoDB ReplicaSet. How to set up a MongoDB ReplicaSet I described here: MongoDB ReplicaSet Tutorial. And how to configure Rails to work together with MongoDB is described here: Rails + MongoDB Quickstart Tutorial.

If you want to connect to a ReplicaSet you just have to change your configuration a little bit. This is how your mongoid.yml file should look like:

production:
  database: mydb_prod
  hosts:
    - - mongonode1:1222
    - - mongonode2:1222
    - - mongonode3:1222
  read: :secondary

“mongonode1”, “mongonode2” and “mongonode3” should be mapped to a real ip address, of course! Usually you do that in “/etc/hosts”.

You don’t have to define a PRIMARY! The driver will figure out which of them is the PRIMARY. I like this! You also don’t need a load balancer. Because all nodes are known by the driver. And so the driver will figure it out.

The property “read” can have 2 values: [“:secondary”, “:primary”]. “:secondary” means that read operations can also be routed to secondary nodes. “:primary” means that all read operations will be routed to the PRIMARY node.

Rails + MongoDB Tutorial. Quickstart.

This is a Quickstart Tutorial for Rails + MongoDB. Ruby on Rails and MongoDB, a document based database, is a pretty good fit. It is very easy to integrate MongoDB with Rails. If you want to know how to install MongoDB, check out this article here: Installing MongoDB on Mac OS X Lion. Or this here: Installing MongoDB on Linux.

I assume you are familiar with Ruby on Rails 🙂

There are several GEMs to interact with MongoDB. I used the MongoID GEM. That worked pretty good for me. Just add this to your Gemfile:

gem 'bson_ext', '1.6.0'  
gem 'mongo', '1.6.0'
gem 'mongoid', '2.4.6'

And run “bundle install” on your project, to load the GEM from the internet. And add the file mongoid.yml to your config folder:

development:
    host: localhost
    database: myapp_dev

test:
    host: localhost
    database: myapp_test

production:
    host: localhost
    database: myapp_prod

This is the file where you are configuring the access to the database. If your mongodb instance is running locale you don’t need any username or password settings. Now you have to load the mongoid.yml file. You can do that by adding this line to your application.rb inside of the “class Application”.

Mongoid.load!("config/mongoid.yml")

Than just add a new Model to your project. For example “User”. That could look like that:

class User
include Mongoid::Document
include Mongoid::Timestamps
field :username, type: String
field :firstname, type: String
field :llastname, type: String
field :email, type: String
filed :age, type: Integer

end

That’s it. You just have to include the Mongoid:Document.

include Mongoid::Document

And define your fields! Now you can create a new user and save it like that:

user = User.new
user.username = "mike"
user.firstname = "Mike"
user.lastname = "Boby"
user.age = 44
user.save

Or make a query like that:

user = User.where( username: "mike")

Check out the Criteria API for MongoID. That is pretty straight forward. Worked pretty well for me.

ie.css isn’t precompiled

If you get this Error Message:

ActionView::Template::Error (blueprint/ie.css isn't precompiled)

Than that means that your ie.css isn’t precompiled 🙂
No. Seriously. If you get this message, probably you are running on production and your css files are not precompiled. Take a deeper look to the Assets Pipeline in Rails 3.1.X. If your css and js files are in the right place, you can fix this error message by executing this command here:

rake assets:precompile

Or with JRuby:

jruby -S rake assets:precompile

That should generate all precompiled css and js files and fix the problem.

ActiveRDF + DBPedia + Ruby on Rails

This article show how to use ActiveRDF in Ruby on Rails to write queries for DBPedia. DBPedia is the semantic version of WikiPedia. Basically the whole knowledge in Wikipedia is available in DBPedia over RDF (Resource Definition Framework). There is a query language for RDF called SPARQL. With SPARQL and RDF you can write queries and fetch data from DBPedia/Wikipedia.

That is pretty awesome. You can see DBPedia/Wikipedia as a big Database or Knowledge base.

ActiveRDF is a Ruby GEM to use RDF in Ruby. To use it in Rails you have to add this two lines to your Gemfile

gem 'activerdf'
gem 'activerdf_sparql'

In you application.rb file you have to add this line pretty much in the top.

require 'active_rdf'

In the same file, in the class part you have to configure the connection pool for RDF.


module TrophicWeb
  class Application < Rails::Application

  adapter = ConnectionPool.add_data_source(:type => :sparql, :results => :sparql_xml, :engine => :virtuoso, :url ="http://dbpedia.org/sparql")
  adapter.enabled = true
  Namespace.register(:dbpedia, "http://dbpedia.org/ontology/")

That’s it for the initialization. Now you can use it in your code. Here is an example how to fetch a species and the corresponding thumbnail uri.

species = RDFS::Resource.new("http://dbpedia.org/resource/Symphurus")
thumbnail = species.dbpedia::thumbnail.uri

Thats it.

Mizuno

Mizuno is Webserver for JRuby on Rails.

I looked for different options to run my JRuby App. After some googleing I found Mizumo. It was very easy to install:

jruby -S gem install mizuno

and very easy to start. Just go into the root directory of your rails app and exec.

mizuno

or if you want to start on Port 80.

sudo mizuno --port 80

Mizuno is the fastest option for Reck applications on JRuby:

Mizuno: 6106.66 req/s (mean) 
Jetty (via jruby-rack): 2011.67 req/s (mean) 
Mongrel: 1479.15 req/sec (mean)

RAILS_ENV

In Ruby on Rails you have always 3 environments.

  1. development
  2. test
  3. production
by default you are working in “development” environment. You can control the environments by setting the “RAILS_ENV” as a system variable in your operating system. Under Linux or Mac OS X you can enforce the environment by adding this variable to you “/etc/bashrc” file.
export RAILS_ENV="test"
On the production Server it should look like this:
export RAILS_ENV="production"
Another way to enforce the ruby environment is to set the Variable directly in the “environment.rb” file in you ruby on rails app. Like this:
ENV['RAILS_ENV'] = 'development'
This worked for me pretty good.

Using Java in Rails with JRuby

JRuby is a pretty good Java implementation of Ruby. One of the biggest advantages of JRuby is that you can use Java Classes in your Ruby on Rails App. You can import JARs and take advantages of all the Java Libraries and Frameworks. In that way you don’t have to throw away your old Java Code. You can build a JAR and use it inside your new fancy Ruby App.

I recommend to build a Uber JAR, which contains all dependencies of your Java App. That is more easy to handle. You have to put your Uber JAR into the “lib” directory of your rails app. Than you have to import the JAR in the “application.rb”.

require "lib/your-java-all.jar"

That’s it! Know you can access the Classes inside the JAR. If you want to use a Java Class in a Controller you have to include the class:

include_class Java::org.trophic.graph.service.LocationServiceImpl
include_class Java::org.trophic.graph.factory.LocationFactory

And here is how you can use the Java Classes:

locationService = LocationFactory.locationService
locations = locationService.locations

And here is the full example of the controller:

class PageController < ApplicationController

  include_class Java::org.trophic.graph.service.LocationServiceImpl
  include_class Java::org.trophic.graph.factory.LocationFactory

  def home
      locationService = LocationFactory.locationService
      @locations = locationService.locations
  end

end

It is super easy! Isn’t it?

Single Sign-On with Facebook & Ruby on Rails

There are over 400 Million people on earth with a Facebook Account. From this point of view it makes absolute sense to offer a Single Sign-On with Facebook.

Facebook offers a Login via OAuth 2.0. The Facebook documentation for the Authentication process you can find here: http://developers.facebook.com/docs/authentication/.

Before you are using the API from Facebook you have to set up an “Facebook App”. Every Facebook App has a client_id and a client_secret. You need both to communicate with the Facebook API. Here you can create a Facebook App: http://www.facebook.com/developers/

If your set up is done you can start with the implementation. Maybe you want to offer a “Login with Facebook” button like this:

Login with Facebook
Login with Facebook

To achieve this you can embed this code in your HTML Page:

<pre><a href='javascript:var popupWindow = window.open(link, "popupWindow", "toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, menubar=1, resizable=1, width="+winWidth+",height="+winHeigh+"top="+top+", left="+left);'
           style="margin-right: 5px;" >
    <img src="/images/fb_login_icon.gif"  style="border: 0px solid white; padding: 0;" />
</a></pre>

This will open a JavaScript popup window with the Facebook Login mask, if you are not already logged in by Facebook via Cookie.

To make this work you need a little bit JavaScript:

var winWidth = 950;
var winHeigh = 550;
var left = (screen.width/2)-(winWidth/2);
var top = (screen.height/2)-(winHeigh/2);

var domainlink = "https://graph.facebook.com/oauth/authorize?";
var req_perms = "req_perms=email&";
var clientid = "client_id=111111111111&";
var scope = "scope=email,offline_access&";
var redirect = "redirect_uri=http://yourdomain.com/facebook/start";
var link = domainlink + req_perms + clientid + scope + redirect;

Just replace  “111111111111” with your client_id and “yourdomain” with your domain. The link contains a redirect_uri on your server. After the Facebook Authentication is done, Facebook will redirect the user to your “redirect_uri”.

OK. And here is the corresponding Controller with Ruby on Rails.

class FacebookController < ApplicationController

  layout "plain"

  def start
    code = params['code']

    domain = 'https://graph.facebook.com'
    uri = '/oauth/access_token'
    query = 'client_id=111111111111&'
    query += 'redirect_uri=http://yourdomain.com/facebook/start&'
    query += 'client_secret=2222222222222222222&'
    query += 'code=' + code
    link = domain + uri + '?' + query

    response = HTTParty.get(URI.encode(link))

    data = response.body
    access_token = data.split("=")[1]

    user = get_user_for_token access_token
    if !user.nil?
      sign_in user
    end
  end

  private

    def get_user_for_token(token)
      json_user = JSON.parse HTTParty.get('https://graph.facebook.com/me?access_token=' + URI.escape(token)).response.body
      user = User.find_by_fb_id json_user['id']
      if user.nil?
        user = User.new
        user.update_from_fb_json json_user
      end
      user.fb_token = token
      user.save
      user
    end

end

I am using here the “HTTParty” gem. You should have this line in your Gemfile:

gem 'httparty', '0.7.4'

And you have to map the controller action in your routes.rb file:

match '/facebook/start',    :to => 'facebook#start'

The “def start” action will be called from Facebook after the User Auth.  Facebook will send you with this request a “code”. You can fetch the code like this:

code = params['code']

Together with the given token, your client_id and your client_secret you can fetch an access_token from facebooks GRAPH API.

    domain = 'https://graph.facebook.com'
    uri = '/oauth/access_token'
    query = 'client_id=111111111111&'
    query += 'redirect_uri=http://yourdomain.com/facebook/start&'
    query += 'client_secret=2222222222222222222&'
    query += 'code=' + code
    link = domain + uri + '?' + query

    response = HTTParty.get(URI.encode(link))

    data = response.body
    access_token = data.split("=")[1]

With the access_token you can fetch a user as JSON object.

json_user = JSON.parse HTTParty.get('https://graph.facebook.com/me?access_token=' + URI.escape(token)).response.body

That’s it.