s

Shoulda Matcher Model Extras

Feb 20, 2013

If you use RSpec with your Rails projects, chances are you also use shoulda_matchers (and if you don't, what are you doing with your life?!). You probably already know all about the basic model matchers, like the ones below.

# app/models/user.rb
class User < ActiveRecord::Base
  belongs_to :organization
  has_many :projects

  validates :name, :presence => true
  validates :email, :uniqueness => true
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'associations' do
    it { should belong_to(:organization) }
    it { should have_many(:projects) }
  end

  context 'validations' do
    it { should validate_presence_of(:name) }
    it { should validate_uniqueness_of(:email) }
  end
end

Association Extras

There are a ton of extra options you can use with the association matchers (pretty much any option you can pass to an association). Below are just a few.

# app/models/user.rb
class User < ActiveRecord::Base
  belongs_to :organization, :class_name => 'UserOrganization'
  has_many :contracts
  has_many :jobs, :through => :contracts
  has_many :projects, :order => 'date DESC', :dependent => :destroy

  accepts_nested_attributes_for :projects, :limit => 3
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'associations' do
    it { should belong_to(:organization).class_name('UserOrganization') }
    it { should have_many(:contracts) }
    it { should have_many(:jobs).through(:contracts) }
    it { should have_many(:projects).order('date DESC').dependent(:destroy) }
    it { should accept_nested_attributes_for(:projects).limit(3) }
  end
end

Validation Extras

There are even more options for use with the validation matchers. Here's a small sampling (including some mass assignment matchers).

# app/models/user.rb
class User < ActiveRecord::Base
  validates :name, :length => { :minimum => 10, :maximum => 100 }
  validates :email, :format => { :with => /^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/ }
  validates :status, :inclusion => { :in => %w(active inactive suspended) }

  attr_accessible :name, :email
  attr_accessible :name, :email, :status, :as => :admin
end

# spec/models/user_spec.rb
require 'spec_helper'
describe User do
  context 'validations' do
    it { should ensure_length_of(:name).is_at_least(10).is_at_most(100) }
    it { should validate_format_of(:email).with('user@email.com') }
    it { should validate_format_of(:email).not_with('user@email') }
    it { should ensure_inclusion_of(:status).in_array(['active', 'inactive', 'suspended']) }
  end

  context 'mass assignment' do
    it { should allow_mass_assignment_of(:name) }
    it { should allow_mass_assignment_of(:email) }
    it { should_not allow_mass_assignment_of(:status) }
    it { should allow_mass_assignment_of(:status).as(:admin) }
  end
end

These are just a few of the extras that shoulda_matchers offers. I would highly recommend that you read through the documentation to discover all the things you can do.

Tagged: rails3rspec