ruby

RSpec + Capybara + Devise Login Tests

For the new version of Connect.Me (in development), the interface is mostly driven through AJAX interactions. In the past, I’ve used Cucumber + Capybara to test JavaScript, but I really don’t like the Cucumber syntax and slow tests. I recently switched to RSpec request specs with Capybara. The only problem is testing testing JS on pages that require a login.

Since Capybara runs in a different thread from the tests, session data is not available. So the Devise sign_in method does nothing for Capybara.

There are basically two solutions to get Capybara to authenticate:

  1. Use Capybara to login through a Devise login form by posting the user’s email and password
  2. Set session data in Capybara to simulate a login (basically do the same thing that Devise’s sign_in does)

For speed of tests reasons, I’d prefer to just set session data. After Googling around, I found this solution and modified it a bit.

# spec/support/request_helpers.rb
require 'spec_helper'
include Warden::Test::Helpers

module RequestHelpers
  def create_logged_in_user
    user = Factory(:user)
    login(user)
    user
  end

  def login(user)
    login_as user, scope: :user
  end
end

Now you just need to call create_logged_in_user and you’re good to go.

describe "user settings" do
  let(:authed_user) { create_logged_in_user }

  it "should allow access" do
    visit user_settings_path(authed_user)
    # should be good!
  end
end

Validating a Single Attribute in Rails ActiveRecord

Checking a single attribute on a model for validity doesn’t seem to be possible in Rails. Or at least I couldn’t find a quick answer googling around or looking through the ActiveRecord code.

What I really want to do is pass a hash into valid? with the attributes I want to validate or have an attribute_valid? method. This level of granularity is often useful in AJAX heavy apps. For instance, checking for username availability and validity as the user types during username selection.

Here’s a quick solution for a User model.

def attribute_valid?(attr, value)
  u = User.new(attr => value))
  u.valid?
  !u.errors.has_key?(:username)
end

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.

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.