rails3

Resque Admin in Rails 3 Routes with CanCan

Resque is a background jobs queue that’s highly recommended over Delayed::Job if you are processing a lot of jobs. It uses Redis as the backend which doesn’t suffer from db related bottlenecks under high load.

Resque comes with a built-in admin interface that’s Rack compatible. In Rails 3, you can mount the Resque server admin directly in your routes.rb file.

mount Resque::Server, at: '/resque'

But you’ll definitely want to add password protection. Ryan Bates in his Resque RailsCast covers the basics of using Devise and HTTP auth. However, you’ll probably want to hook into your existing ACL system. In my case, I’m using CanCan.

CanCan is not available in the routes.rb by default, but it’s pretty easy to manually load the user and check permissions.

# routes.rb
namespace :admin do
  constraints CanAccessResque do
    mount Resque::Server, at: 'resque'
  end
end
# config/initializers/admin.rb
class CanAccessResque
  def self.matches?(request)
    current_user = request.env['warden'].user
    return false if current_user.blank?
    Ability.new(current_user).can? :manage, Resque
  end
end
# ability.rb
class Ability
  include CanCan::Ability
  def initialize(user)
    user ||= User.new
    if user.is_admin?
      can :manage, Resque
    end
  end
end

You’ll need User.is_admin? method or change the logic in Ability to suit your project.

Now an authenticated user with is_admin? == true will be able to access Resque admin. Other users will get a 404 since no route matches.

Thanks to Arcath’s blog post for initially pointing me in the right direction.

Rescuing from Rack::Timeout to Close MongoDB Connection

I’m using Rack::Timeout on Heroku to kill requests before Heroku’s 30 second limit is reached. This helps applications play nice with cloud infrastructure but can cause some unexpected bugs with MongoDB connections being reused by the next request.

The errors showed up as…

Mongo::ConnectionFailure: Expected response 372 but got 371

Mongo ruby driver >=1.3 catches the reused request and raises an error, but this still means the first and next requests both returned errors. It’s much better to catch the initial timeout and close the connection.

It seems like rescue_from Timeout::Error in ApplicationController should work, but for some reason the exception passed in is a Class and not Timeout::Error – most likely due to Rack::Timeout wrapping the entire app.

I googled around and couldn’t find a more elegant solution, but the below snippet in ApplicationController does the trick.

  # note: can not rescue from Timeout::Error directly because a timeout from Rack::Timeout ends up passing in Class as the exception
  rescue_from Exception do |exception|
    # catch Timeout::Error or message from Rack::Timeout
    if exception.is_a?(Timeout::Error) || /execution expired/ =~ exception.message
      # prevent subsequent requests from reusing this mongo connection
      Mongoid.database.connection.close
    end
    raise
  end

As a side note, Heroku’s new cedar stack does not have the 30 second limit if you’re streaming data – Rails 3.1 supports streaming.

Rails 3′s unobtrusive javascript support…

Rails 3′s unobtrusive javascript support makes it easy to integrate jQuery UI Autocomplete and any backend or custom query you want. There’s a Rails3 jQuery Autocomplete gem already available on github, but it’s not very RESTful and makes a lot of assumptions that might not fit your application.  So why not roll your own?

It’s easy.

First, make sure your rails app is setup with jQuery support.

Next, download jQuery UI Autocomplete, copy the appropriate files into your javascripts and stylesheets directories, and make sure the JS and CSS files are loading properly in your app.

Here’s the general process I use to setup autocomplete for Rails 3:

In the controller

  1. create a method to handle the autocomplete search request

In routes

  1. add a route for the method you created in the controller – this is your autocomplete search endpoint

In the view

  1. add a text field with a unique class name like ‘autocomplete’
  2. add a unique data attribute like ‘data-endpoint’ to the text field and set it to the path of your autocomplete endpoint

In application.js

  1. enable jQuery Autocomplete on any DOM element with the class ‘autocomplete’ (or whatever you used in the view)
  2. use the value of data-endpoint as the source URL for the jQuery Autocomplete widget

In the following example, I’m tagging a user and using autocomplete to help suggest tags.

controllers/users/tags_controller.rb

class Users::TagsController < ApplicationController
  respond_to :json
  skip_before_filter :authenticate_user!, :only => [:index]

  def index
    @user = User.find(params[:user_id])
    @tags = Tag.where(["LOWER(name) LIKE ?", "#{params[:term].downcase}%"]).select('name').limit(15).map {|tag| tag.name}
    respond_with(@tags)
  end

  def create
    @user = User.find(params[:user_id])
    tag = @user.tags.create!(params[:tag])
    flash[:success] = 'Tag added'
    redirect_to user_path(@user)
  end
end

The tags controller has two methods. One for creating a tag, and one (index) for handling tag autocomplete searches.

The index method expects user_id and term params. In this example, the user_id is being handled in the path /users/:user_id/tags and jQuery Autocomplete sends in the term param.

routes.rb

resources :users do
  resources :tags, :only => [:index, :create], :controller => 'users/tags'
end

This creates a /users/:user_id/tags route and a users_tags_path helper to use in your views.

views/users/show.html.haml

= form_for [@user, @user.tags.build] do |f|
  .field
    = f.label :name, 'Tag'
    = f.text_field :name, :class => 'autocomplete', :'data-endpoint' => user_tags_path(@user)
  = f.submit 'Add Tag'

javascripts/application.js

jQuery(function($){
  $('.autocomplete').each(function(){
    var self = $(this);
    self.autocomplete({source: self.attr('data-endpoint')});
  });
});

That’s all there is to it. You can easily modify your controller and model code to use MongoDB, Solr, Redis, or whatever you want.

Happy coding.

Rails 3 + STI: Making Associations Work Properly

I really like the idea of Single Table Inheritance (STI) for all sorts of applications to keep code DRY and make it easier to organize object behavior. The only problem is that Rails 3.0.3 doesn’t fully support STI with association collections.

Let’s say you have a User model that has many badges. The badges will be stored in the badges table but you want to implement each badge in a subclass. All you have to do is make sure there’s a :type field of type string in your badges table and Rails STI support should take care of the rest (well, in theory).

class User < ActiveRecord::Base
  has_many :badges
end

class Badge < ActiveRecord::Base
  belongs_to :user
  def award
    raise "Must implement in subclass"
  end
end

class Badges::Superhero < Badge
  def award
    user.status = 'superhero'
  end
end

Now you can do cool things like create a new Superhero badge and add it to a user’s badge collection.

user = User.first
badge = Badges::Superhero.new
user.badges << badge

But for some weird reason, you can’t use the best practice of building a badge directly from the user’s badges collection.

user = User.first
badge = user.badges.build(:type => Badges::Superhero)
# badge.class == Badge

This is particularly annoying if you’re trying to create new badges from a form where :type is a drop down menu.

The reason the collection build method doesn’t work as expected is because :type is a protected field and ActiveRecord::AssociationReflection doesn’t fully support STI (at least in Rails 3.0.3).

Not to fret, hacks to the rescue!

You have two options to make STI work as expected.

Option 1: Override the Badge.new method to handle :type

class Badge < ActiveRecord::Base
  belongs_to :user
  self.abstract_class = true

  class << self
    def new_with_cast(*a, &b)
      if (h = a.first).is_a? Hash and (type = h.symbolize_keys[self.class.inheritance_column.to_sym]) and (klass = type.to_s.constantize) != self
        raise "Must be a subclass of Badge" unless klass < self  # klass should be a descendant of self
        return klass.new_without_cast(*a, &b)
      end
      raise "Badge must be created through a subclass."
      new_without_cast(*a, &b)
    end
    alias_method_chain :new, :cast
  end
end

Option 2: Patch AssociationReflection to behave more intelligently

class ActiveRecord::Reflection::AssociationReflection
  def build_association(*opts)
    col = klass.inheritance_column.to_sym
    if (h = opts.first).is_a? Hash and (type = h.symbolize_keys
) and type.class == Class opts.first
.to_s.constantize.new(*opts) elsif klass.abstract_class? raise "#{klass.to_s} is an abstract class and can not be directly instantiated" else klass.new(*opts) end end end

My preference is Option 2 even though it might break in future releases of Rails. I’d rather have Rails behaving as expected than pepper my models code with repetitive hacks.

The above solutions were inspired from a couple of different posts and sources.

I submitted Option 2 as a patch for Rails.

Devise + OmniAuth + Facebook API Hacks

While using DeviseOmniAuth for a new Rails 3 project, I got really curious about how to reuse OmniAuth’s strategy classes to fetch additional API data after a user has authenticated.

A lot of people probably use libraries like FbGraph along with OmniAuth, but I wanted something extremely lightweight without duplicating code already in OmniAuth.

I mostly followed Ryan Bates’ railscasts on setting up Devise + OmniAuth.

If you open the oa-oauth gem, you’ll see a lib/omniauth/strategies folder filled with all sorts of OAuth2 providers like Facebook, Twitter, and LinkedIn. Each of these provider classes has most if not everything needed to make direct API calls once an OAuth2 access_token is obtained.

The basic idea is to store the access_token during an OAuth2 login process (Ryan covers this in his railscast) and later on use the token to fetch more data. To do this, you use the access_token to instantiate an OAuth2::AccessToken along with a Facebook specific OAuth2::Client. The Facebook client really just sets the site URL variable so the AccessToken knows where to send requests. It seems simple enough to just use OmniAuth’s strategy classes for the OAuth2::Client. Then I don’t have to maintain provider specific code myself.

I dug around in the Devise, OmniAuth, and OAuth2 code for hours.  Long story short, the simplest solution is to just write your own provider client class instead of trying to reuse OmniAuth’s. Bummer. But way easier in the end. OmniAuth is a rack application designed for authentication only. It’s just too convoluted to try to reuse its strategy classes for additional API calls.

Here’s the simple solution I ended up writing…

Create lib/facebook.rb in your rails app

module OAuth2
  module Clients
    class Facebook < Client
      def initialize(client_id, client_secret, opts = {})
        opts[:site] = 'https://graph.facebook.com/'
        super(client_id, client_secret, opts)
      end
    end
  end
end

Now you can use the Facebook client along with a user specific access_token (stored in a database during the OmniAuth login process) to fetch additional API data.

require 'facebook'
client = OAuth2::Clients::Facebook.new('', '')
token = OAuth2::AccessToken.new(client, access_token)
token.get('/me/friends')

Note that you don’t need to set the client_id, and client_secret when creating a new Facebook object. The id and secret are only needed to initially obtain the access_token which you already have.

On a side note, it’s good to keep in mind that every additional OmniAuth provider creates a new rack application which gets called on every request. Hopefully this will not be the case in future versions so that an arbitrary number of providers can be supported without any performance concerns.