Monday, November 28, 2011

callback_skipper for faster factories

The Rails community has taken a strong stance that test fixtures are evil and factories are the new hotness. The most compelling reason for this shift being that factories greatly reduce maintance costs as your application grows over time.

The largest drawback when replacing fixtures with factories is the additional performance overhead when initializing your test state. This is due to the fact that factories utilize the full ActiveRecord lifecycle (initialize object, validate, save to database) compared to fixtures which are a glorified database bulk import which bypass object creation/validation.

Take this example ActiveRecord model and testcase (using FactoryGirl factories):

class Foo < ActiveRecord::Base
  after_create :do_something_expensive
end

def test_new_instance_has_bar
  foo = Factory.create :foo

  assert foo.bar?
end

This is a classic example where using factories will be much slower than fixtures. It may not seem like a big deal, but it becomes a real issue when you have a sizable application testsuite, and I'm a firm believer that if a testsuite isn't fast, it doesn't get run.

Sending email with after_save callback is another great usecase. How can we get the benefits of factories without the additional overhead? Can we have our cake and eat it too?

Since 99% of our testcases rely on the basic factory instance and are not dependent upon the expensive callback being fired, it would be ideal to skip the expensive callback for most of your testcases and only fire it for testscases that are explicitly asserting it's behavior.

The newly released callback_skipper gem fulfills this exact usecase. The goal is to make it trivial to skip a particular model callback for a specific instance. This is a homerun for test factories to setup a default factory which skip non-critical callbacks and still have the flexibility to create a factory that does fire the slow-ass callbacks for specific tests.

# spec/factories/foo_factory.rb
Factory.define :foo do |f|
  f.after_build do |o|
    o.skip_callback :save, :after, :do_something_expensive
  end
end
Factory.define :foo_with_expensive_callback, :class => 'Foo' do |f|
end

Oh yes, we can have our cake and eat it too...

The callback_skipper gem is equivalent to the core ActiveRecord.skip_callback method with the added benefit of only skipping the callback for a specific instance instead of globally for all invocations.

As always, the gem is 100% opensource and suggestions are welcome!

Wednesday, November 9, 2011

Cron Backgrounded Resque Jobs

Analog Time Sand

Cron is still the de facto standard when it comes to scheduling execution of jobs. It does one thing, and it does it well.

Using cron to fire off heavyweight Ruby/Rails jobs is fairly trivial to get up and running. When each cron job fires up a full Rails process, it won't be long until you accrue enough jobs that your cron server will be brought to it's knees. Since you already have a pool of Resque background workers running to process async jobs, why not leverage dormant workers to process jobs that are kicked off via cron?

Here is a simple solution that can be used to have cron enqueue jobs into Backgrounded Resque jobs without loading the entire Rails environment.
This is an excellent optimization to increase the availability of your cron server and avoid running out of memory when multiple jobs fire up at the same time.

crontab invocation:

script/backgrounded enqueue Foo.bar --queue baz

script/backgrounded:

#!/usr/bin/env ruby
require 'rubygems'
require 'thor'

module Backgrounded
  class CLI < Thor

    desc 'enqueue', 'enqueue a clazz.method invocation for resque backgrounded workers'
    method_option :queue, :aliases => "-q", :desc => "resque queue to enqueue the operation to", :default => 'backgrounded'
    method_option :rails_env, :aliases => '-e', :desc => 'control which rails env used to load the redis config', :default => 'production'
    # operation Clazz.method to enqueue into resque backgrounded queue (ex: Foo.bar)
    def enqueue(operation)
      require 'bundler'
      Bundler.setup
      require "thread"
      require "active_support/inflector"
      require "resque"
      require "yaml"
      clazz, method = operation.to_s.split('.')
      raise 'invalid operation' unless clazz && method
      Resque.redis = YAML.load_file(File.join("config", "resque.yml"))[options[:rails_env]]
      Resque::Job.create(options[:queue], 'Backgrounded::Handler::ResqueHandler', clazz, -1, method)
    end
  end
end

Backgrounded::CLI.start

Use cron for it's excellent scheduling capabilities and take advantage of your background processing infrastructure to handle the incoming request!

Tuesday, October 4, 2011

Cleaner RSpec/FactoryGirl Integration

Think Different...
For the past few days, I've been re-thinking how to integrate FactoryGirl fixtures into my RSpec tests. The transition from Test::Unit to RSpec has taken me some time and I'm still "Unlearning what I have learned" and adapting to the RSpec way of doing things. It's not that Test::Unit style tests are wrong, it just that I need to think a bit differently in order to take full advantage of the expressiveness of RSpec tests.

One common pattern for Rails application tests is to instantiate fixture data and perform assertions on the instances:
describe User do
  context 'basic user' do
    before do
      @user = Factory.create :user
    end
    it { @user.should be_inactive }
    it { @user.should_not be_happy }
  end
end

According to The Anatomy of an Effective Unit Test the before block in this example is playing the role of both fixture setup and test execution. The ambiguous definition of the before method is analogous to how horrible it is to use API's with incredibly useful methods such as run or execute. Sure, we know when the block is executed, but method names are key to understanding their purpose as well.

I'm a strong proponent of expressive method names as a way to self document your code and that's exactly what we need in this scenario. We need a clear and concise way to encapsulate our fixture data initialization outside of the before method and hook it into the RSpec test lifecycle. This will help remove the ambiguous purpose of the before method in this example.

Luckily, The RSpec DSL can be easily be extended.  The new factory_girl_rspec gem adds the with method which is a seriously clean and concise DSL for initializing your fixtures:
describe User do
  context 'basic user' do
    with :user
    it { user.should be_inactive }
    it { user.should_not be_happy }
  end
end

The with method is a simple utility that maps directly to the FactoryGirl fixtures that you have defined for your test environment. Each execution of with will create your fixture instance and define a local method within your test context retrieve the instance. Compare the readability of this test with the previous incarnation. It reads like a story right? Watch out Cucumber, who needs text processing stories when you've got human readable ruby code!

Deviations of fixture data are extremely common in unit tests as well, and the with method expresses differences in an equally concise manner:
describe User do
  context 'user with first_name == "Jim"' do
    with :user, :first_name => "Jim"
    it { user.should be_inactive }
    it { user.should be_happy }
  end
end