For those used to ActiveRecord-style

development the following might not seem all that bad:

class User < ActiveRecord :: Base belongs_to :account has_many :posts validates_format_of :email , :with => /@/ after_create :deliver_welcome_email after_create :mark_pending private def mark_pending update_attributes! :state => 'pending' end def deliver_welcome_email ApplicationMailer . deliver :welcome_email , email end end

If you’ve read Sandi Metz’ book (or have an OO

background) you’ll notice something about this conventional Rails code.

Not only is it a huge mess of dependencies, it violates damn near every

part of

[SOLID](http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)

object-oriented design that we practice as a means of keeping our code

nimble.

So what’s the alternative?. The Data Access

Object pattern uses an

object to manage persistence and doesn’t let that object do anything

else. Effectively, it makes the above class look like this:

class User < ActiveRecord :: Base belongs_to :account has_many :posts end

Or, if you want to get religious:

class User < ActiveRecord :: Base end

(Personally, I think associations are the thing ActiveRecord does right

so I’m fine leaving those in.)

We just took a lot of code out of the User class and it has to go

somewhere else. Where? Well, that depends on what it does. Consider this

reorganization:

class User < ActiveRecord :: Base belongs_to :account has_many :posts end module Validator class Invalid < StandardError ; end def self . validate! ( record ) unless record . email =~ /@/ raise Invalid end end end module Email def self . welcome ( email ) ## Handle email delivery end end class StateMachine attr_reader :record def initialize ( record ) # I'm making the StateMachine instantiable because @record = record # the 'record' object feels like internal state and end # there may be actions we want to perform on that # object during a state transition def pending! record . state = 'pending' record . save! end end module Signup # You now have to use this to tell Signup def self . persistence = ( db_model ) # what object is in charge of database @persistence = db_model # persistence. This is where you pass in end # your DAO or, in tests, your stub object def self . complete! data # I'm not bothering to make Signup an instantiable record = @persistence . new ( data ) # class at this point because we don't need it yet. Validator . validate! record record . save! StateMachine . new ( record ) . pending! Email . welcome data [ :email ] end end

We now have many more lines of code and many more objects so it might

look like we just added complexity but in fact we’ve reduced it. If

complexity is the entanglement of concepts, then we’ve removed nearly all

complexity from this application. Let’s walk through the changes bit by bit.

Removing callbacks

We turned a couple after_create callbacks into

direct code execution. You now know exactly when the email address will

be validated and when the welcome message will be sent. You also know

how to stub out the objects that perform those functions in your tests.

Introducing a proper state machine

This is my favorite. There’s a Rails application at work that

moves a lot of money around and needs to handle many intermediate states

with very precise rules about when things are purchased, paid for,

approved, rejected, returned, refunded, etc. If we were to try to

implement this logic inside an ActiveRecord class we’d be fighting

against the weight of Rails every time we extended the state graph.

Most Rails applications I’ve worked on have eventually had some kind of

state machine in them. I’ve tried acts_as_state_machine , two gems both

called state_machine and I’ve built several terrible and bug-prone

versions myself. Nothing has worked as well as pulling all the logic out

into a new class and being very explicit about what kind of state

transitions your application allows. You’ll notice the interface to the

persistence object is only two methods large. As long as the object you

pass in responds to state= and save! the StateMachine class will

work. Which means you do NOT need to pass in an ActiveRecord instance in

your tests – any simple mock will do.

Adding sane validations

Two months ago I was in the middle of a Rails2->Rails3 upgrade and I

found myself in a pry console staring at the insides of a

validates_each block deep inside Rails. For trivial validations and/or

trivial applications the Rails way of adding data validation is just

fine. But when you write the validations yourself you get all the

following benefits:

test your validations without simultaneously testing that

ActiveRecord#save still works

still works test the edge cases of your validations because you actually control

how they’re defined and when they fire

how they’re defined and when they fire easily debug your validations because you implemented them in a

well-factored class

well-factored class write hundreds of validation tests if you want because they’ll all run in

less than a second.

Separating application-level features from database objects

The conventional path would have you type User.create! params[:user]

in your controller. This is only good if you’re certain that creating

a new User record is the action you’re trying to complete. But more

likely what you want is to complete a user signup. So

Signup.complete! params[:user] is much more descriptive of what you

want. Maybe you want to create a User record. Maybe you want to create a

Business record. Maybe it’s such an important action that you’ll be

generating Account , Business , User , and Product records all at

once. By keeping the controller-level semantics high-level you won’t

have to change them to keep up with an evolving implementation.

And then your tests go zoom

This post isn’t called ‘how to make your Rails tests faster’ but it

might as well be. The reason your tests are slow is ActiveRecord and

ActionController . Your code (and Ruby) is actually blazing fast but

it’s organized in such a way that you have to test each object’s

dependencies if you want to test the object itself.

The tests

Let’s take a look at what these tests would look like before and after

the above reorganization. First, the conventional Rails way:

## file:spec/models/user_spec.rb require 'config/environment' # This loads all of Rails and autoloads describe User do # every object automatically describe 'creating' do subject { User . create! params } let ( :email ) { 'm@rvelo.us' } let ( :name ) { 'Marvelous' } let ( :params ) { { email : email , name : name } } context "with invalid email" do let ( :email ) { 'whoops' } it "doesn't create a new record" do expect { subject } . to_not change { User . count } end end context "with valid params" do it "creates a new record" do expect { subject } . to change { User . count } . by ( 1 ) end it "sends a welcome email" do ActionMailer . should_receive ( :deliver ) . with ( :welcome , email ) subject end it "sets the 'state' column to 'pending'" do subject User . last . state . should == 'pending' end end end end

That’s a respectable spec. It’s reasonably clean, tests most of the

important stuff, and doesn’t go off track testing unnecessary edge cases

or the implementation of the code (other than the ActionMailer bit).

However, every example in that snippet booted all of ActiveRecord and

tested the full forest of callbacks, validations, database connection

mechanisms, and relational associations inside ActiveRecord. Rails has

its own tests so there’s no point in me or you re-testing the Rails

internals. That just turns our app into a Seti@Home that’s guaranteed

not to find anything.

Let’s rewrite that to test only what we care about.

## file:spec/unit/services/signup_spec.rb require 'app/services/signup' # We only require the thing we care about # and we trust that _it_ will require any # require any dependencies that it needs. module FakeDAO require 'ostruct' # We don't want to go saving to the actual def self . instance # database all the time, we'll just assume that @instance ||= OpenStruct . new # ActiveRecord still works. end def self . new ( attrs ) attrs . each do | k , v | instance . send " #{ k } =" , v end instance . send "save!=" , true instance end end describe Signup do before { Signup . persistence = FakeDAO } # This is where we inject the dependency subject { Signup . complete! data } let ( :email ) { 'm@rvelo.us' } let ( :name ) { 'Marvelous' } let ( :data ) { { email : email , name : name } } context "with invalid email" do let ( :email ) { 'whoops' } it "doesn't create a new record" do expect { subject } . to raise_error ( Validator :: Invalid ) end end context "with valid data" do it "creates a new record" do FakeDAO . instance . should_receive ( :save! ) . at_least ( 1 ) . times subject end it "sends a welcome email" do Email . should_receive ( :welcome ) . with ( email ) subject end it "sets the 'state' column to 'pending'" do subject FakeDAO . instance . state . should == 'pending' end end end

That’s about the same size code as the previous example but it runs in

less than a hundredth of a millisecond. It also explicitly tests only

Signup – the object under test.

We then get to add individual tests for the other components we’ve

extracted. Each of those will be focused and obvious.

You’re stubbing things. What if a bug slips through the cracks?

It’s only okay to stub the dependencies of an object if two conditions

hold: You’re stubbing in such a way that you’re unlikely to change the

core behavior of the object under test and you still have high-level

integration tests that test the whole suite.

So if you’re reasonably sane about what you choose to stub and you still

write a basic high-level test that only tests for the primary cases (not

edge cases) you can get away with this approach.

We’ve had enough success with this pattern at work that

Xavier added the following to our

spec/unit_helper.rb :

RSpec . configure do | config | config . before ( :suite ) do if Object . const_defined? ( "Rails" ) || Object . const_defined? ( "ActiveRecord" ) raise "The Rails environment should not be loaded for unit tests." end end end

That’s right, we throw an error if you’ve required any part of Rails in

your code. This enforces that you’ve written loosely-coupled,

dependency-injected, DAO-style persistence-oriented classes whether you

are familiar with those terms or not. I can’t recommend this snippet of

code highly enough.