Module: Shoulda::Matchers::ActiveRecord

Overview

This module provides matchers that are used to test behavior within ActiveRecord classes.

Instance Method Summary collapse

Instance Method Details

#accept_nested_attributes_for(name) ⇒ AcceptNestedAttributesForMatcher

The accept_nested_attributes_for matcher tests usage of the accepts_nested_attributes_for macro.

class Car < ActiveRecord::Base
  accepts_nested_attributes_for :doors
end

# RSpec
RSpec.describe Car, type: :model do
  it { should accept_nested_attributes_for(:doors) }
end

# Minitest (Shoulda) (using Shoulda)
class CarTest < ActiveSupport::TestCase
  should accept_nested_attributes_for(:doors)
end

Qualifiers

allow_destroy

Use allow_destroy to assert that the :allow_destroy option was specified.

class Car < ActiveRecord::Base
  accepts_nested_attributes_for :mirrors, allow_destroy: true
end

# RSpec
RSpec.describe Car, type: :model do
  it do
    should accept_nested_attributes_for(:mirrors).
      allow_destroy(true)
  end
end

# Minitest (Shoulda)
class CarTest < ActiveSupport::TestCase
  should accept_nested_attributes_for(:mirrors).
    allow_destroy(true)
end
limit

Use limit to assert that the :limit option was specified.

class Car < ActiveRecord::Base
  accepts_nested_attributes_for :windows, limit: 3
end

# RSpec
RSpec.describe Car, type: :model do
  it do
    should accept_nested_attributes_for(:windows).
      limit(3)
  end
end

# Minitest (Shoulda)
class CarTest < ActiveSupport::TestCase
  should accept_nested_attributes_for(:windows).
    limit(3)
end
update_only

Use update_only to assert that the :update_only option was specified.

class Car < ActiveRecord::Base
  accepts_nested_attributes_for :engine, update_only: true
end

# RSpec
RSpec.describe Car, type: :model do
  it do
    should accept_nested_attributes_for(:engine).
      update_only(true)
  end
end

# Minitest (Shoulda)
class CarTest < ActiveSupport::TestCase
  should accept_nested_attributes_for(:engine).
    update_only(true)
end


93
94
95
# File 'lib/shoulda/matchers/active_record/accept_nested_attributes_for_matcher.rb', line 93

def accept_nested_attributes_for(name)
  AcceptNestedAttributesForMatcher.new(name)
end

#belong_to(name) ⇒ AssociationMatcher

The belong_to matcher is used to ensure that a belong_to association exists on your model.

class Person < ActiveRecord::Base
  belongs_to :organization
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:organization) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization)
end

Note that polymorphic associations are automatically detected and do not need any qualifiers:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
end

# RSpec
RSpec.describe Comment, type: :model do
  it { should belong_to(:commentable) }
end

# Minitest (Shoulda)
class CommentTest < ActiveSupport::TestCase
  should belong_to(:commentable)
end

Qualifiers

conditions

Use conditions if your association is defined with a scope that sets the where clause.

class Person < ActiveRecord::Base
  belongs_to :family, -> { where(everyone_is_perfect: false) }
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should belong_to(:family).
      conditions(everyone_is_perfect: false)
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:family).
    conditions(everyone_is_perfect: false)
end
order

Use order if your association is defined with a scope that sets the order clause.

class Person < ActiveRecord::Base
  belongs_to :previous_company, -> { order('hired_on desc') }
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:previous_company).order('hired_on desc') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:previous_company).order('hired_on desc')
end
class_name

Use class_name to test usage of the :class_name option. This asserts that the model you're referring to actually exists.

class Person < ActiveRecord::Base
  belongs_to :ancient_city, class_name: 'City'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:ancient_city).class_name('City') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:ancient_city).class_name('City')
end
with_primary_key

Use with_primary_key to test usage of the :primary_key option.

class Person < ActiveRecord::Base
  belongs_to :great_country, primary_key: 'country_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should belong_to(:great_country).
      with_primary_key('country_id')
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:great_country).
    with_primary_key('country_id')
end
with_foreign_key

Use with_foreign_key to test usage of the :foreign_key option.

class Person < ActiveRecord::Base
  belongs_to :great_country, foreign_key: 'country_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should belong_to(:great_country).
      with_foreign_key('country_id')
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:great_country).
    with_foreign_key('country_id')
end
with_foreign_type

Use with_foreign_type to test usage of the :foreign_type option.

class Visitor < ActiveRecord::Base
  belongs_to :location, foreign_type: 'facility_type', polymorphic: true
end

# RSpec
RSpec.describe Visitor, type: :model do
  it do
    should belong_to(:location).
      with_foreign_type('facility_type')
  end
end

# Minitest (Shoulda)
class VisitorTest < ActiveSupport::TestCase
  should belong_to(:location).
    with_foreign_type('facility_type')
end
dependent

Use dependent to assert that the :dependent option was specified.

class Person < ActiveRecord::Base
  belongs_to :world, dependent: :destroy
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:world).dependent(:destroy) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:world).dependent(:destroy)
end

To assert that any :dependent option was specified, use true:

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:world).dependent(true) }
end

To assert that no :dependent option was specified, use false:

class Person < ActiveRecord::Base
  belongs_to :company
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:company).dependent(false) }
end
counter_cache

Use counter_cache to assert that the :counter_cache option was specified.

class Person < ActiveRecord::Base
  belongs_to :organization, counter_cache: true
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:organization).counter_cache(true) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).counter_cache(true)
end
touch

Use touch to assert that the :touch option was specified.

class Person < ActiveRecord::Base
  belongs_to :organization, touch: true
end

# RSpec
RSpec.describe Person, type: :model do
  it { should belong_to(:organization).touch(true) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).touch(true)
end
strict_loading

Use strict_loading to assert that the :strict_loading option was specified.

class Organization < ActiveRecord::Base
  has_many :people, strict_loading: true
end

# RSpec
RSpec.describe Organization, type: :model do
  it { should have_many(:people).strict_loading(true) }
end

# Minitest (Shoulda)
class OrganizationTest < ActiveSupport::TestCase
  should have_many(:people).strict_loading(true)
end

Default value is true when no argument is specified

# RSpec
RSpec.describe Organization, type: :model do
  it { should have_many(:people).strict_loading }
end

# Minitest (Shoulda)
class OrganizationTest < ActiveSupport::TestCase
  should have_many(:people).strict_loading
end
autosave

Use autosave to assert that the :autosave option was specified.

class Account < ActiveRecord::Base
  belongs_to :bank, autosave: true
end

# RSpec
RSpec.describe Account, type: :model do
  it { should belong_to(:bank).autosave(true) }
end

# Minitest (Shoulda)
class AccountTest < ActiveSupport::TestCase
  should belong_to(:bank).autosave(true)
end
inverse_of

Use inverse_of to assert that the :inverse_of option was specified.

class Person < ActiveRecord::Base
  belongs_to :organization, inverse_of: :employees
end

# RSpec
describe Person
  it { should belong_to(:organization).inverse_of(:employees) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).inverse_of(:employees)
end
required

Use required to assert that the association is not allowed to be nil. (Enabled by default in Rails 5+.)

class Person < ActiveRecord::Base
  belongs_to :organization, required: true
end

# RSpec
describe Person
  it { should belong_to(:organization).required }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).required
end
without_validating_presence

Use without_validating_presence with belong_to to prevent the matcher from checking whether the association disallows nil (Rails 5+ only). This can be helpful if you have a custom hook that always sets the association to a meaningful value:

class Person < ActiveRecord::Base
  belongs_to :organization

  before_validation :autoassign_organization

  private

  def autoassign_organization
    self.organization = Organization.create!
  end
end

# RSpec
describe Person
  it { should belong_to(:organization).without_validating_presence }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).without_validating_presence
end
optional

Use optional to assert that the association is allowed to be nil. (Rails 5+ only.)

class Person < ActiveRecord::Base
  belongs_to :organization, optional: true
end

# RSpec
describe Person
  it { should belong_to(:organization).optional }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should belong_to(:organization).optional
end


377
378
379
# File 'lib/shoulda/matchers/active_record/association_matcher.rb', line 377

def belong_to(name)
  AssociationMatcher.new(:belongs_to, name)
end

#define_enum_for(attribute_name) ⇒ DefineEnumForMatcher

The define_enum_for matcher is used to test that the enum macro has been used to decorate an attribute with enum capabilities.

class Process < ActiveRecord::Base
  enum status: [:running, :stopped, :suspended]

  alias_attribute :kind, :SomeLegacyField

  enum kind: [:foo, :bar]
end

# RSpec
RSpec.describe Process, type: :model do
  it { should define_enum_for(:status) }
  it { should define_enum_for(:kind) }
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status)
  should define_enum_for(:kind)
end

Qualifiers

with_values

Use with_values to test that the attribute can only receive a certain set of possible values.

class Process < ActiveRecord::Base
  enum status: [:running, :stopped, :suspended]
end

# RSpec
RSpec.describe Process, type: :model do
  it do
    should define_enum_for(:status).
      with_values([:running, :stopped, :suspended])
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_values([:running, :stopped, :suspended])
end

If the values backing your enum attribute are arbitrary instead of a series of integers starting from 0, pass a hash to with_values instead of an array:

class Process < ActiveRecord::Base
  enum status: {
    running: 0,
    stopped: 1,
    suspended: 3,
    other: 99
  }
end

# RSpec
RSpec.describe Process, type: :model do
  it do
    should define_enum_for(:status).
      with_values(running: 0, stopped: 1, suspended: 3, other: 99)
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_values(running: 0, stopped: 1, suspended: 3, other: 99)
end
backed_by_column_of_type

Use backed_by_column_of_type when the column backing your column type is a string instead of an integer:

class LoanApplication < ActiveRecord::Base
  enum status: {
    active: "active",
    pending: "pending",
    rejected: "rejected"
  }
end

# RSpec
RSpec.describe LoanApplication, type: :model do
  it do
    should define_enum_for(:status).
      with_values(
        active: "active",
        pending: "pending",
        rejected: "rejected"
      ).
      backed_by_column_of_type(:string)
  end
end

# Minitest (Shoulda)
class LoanApplicationTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_values(
      active: "active",
      pending: "pending",
      rejected: "rejected"
    ).
    backed_by_column_of_type(:string)
end
with_prefix

Use with_prefix to test that the enum is defined with a _prefix option (Rails 6+ only). Can take either a boolean or a symbol:

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], _prefix: :old
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      with_values([:open, :closed]).
      with_prefix(:old)
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_values([:open, :closed]).
    with_prefix(:old)
end
with_suffix

Use with_suffix to test that the enum is defined with a _suffix option (Rails 5 only). Can take either a boolean or a symbol:

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], _suffix: true
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      with_values([:open, :closed]).
      with_suffix
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_values([:open, :closed]).
    with_suffix
end
without_scopes

Use without_scopes to test that the enum is defined with '_scopes: false' option (Rails 5 only). Can take either a boolean or a symbol:

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], _scopes: false
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      without_scopes
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    without_scopes
end
with_default

Use with_default to test that the enum is defined with a default value. A proc can also be passed, and will be called once each time a new value is needed. (If using Time or Date, it's recommended to freeze time or date to avoid flaky tests):

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], default: :closed
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      with_default(:closed)
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    with_default(:closed)
end
validating

Use validating to test that the enum is being validated. Can take a boolean value and an allowing_nil keyword argument:

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], validate: true
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      validating
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    validating
end

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], validate: { allow_nil: true }
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      validating(allowing_nil: true)
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    validating(allowing_nil: true)
end
without_instance_methods

Use without_instance_methods to exclude the check for instance methods.

class Issue < ActiveRecord::Base
  enum status: [:open, :closed], instance_methods: false
end

# RSpec
RSpec.describe Issue, type: :model do
  it do
    should define_enum_for(:status).
      without_instance_methods
  end
end

# Minitest (Shoulda)
class ProcessTest < ActiveSupport::TestCase
  should define_enum_for(:status).
    without_instance_methods
end


279
280
281
# File 'lib/shoulda/matchers/active_record/define_enum_for_matcher.rb', line 279

def define_enum_for(attribute_name)
  DefineEnumForMatcher.new(attribute_name)
end

#encrypt(value) ⇒ EncryptMatcher

The encrypt matcher tests usage of the encrypts macro (Rails 7+ only).

class Survey < ActiveRecord::Base
  encrypts :access_code
end

# RSpec
RSpec.describe Survey, type: :model do
  it { should encrypt(:access_code) }
end

# Minitest (Shoulda)
class SurveyTest < ActiveSupport::TestCase
  should encrypt(:access_code)
end

Qualifiers

deterministic
class Survey < ActiveRecord::Base
  encrypts :access_code, deterministic: true
end

# RSpec
RSpec.describe Survey, type: :model do
  it { should encrypt(:access_code).deterministic(true) }
end

# Minitest (Shoulda)
class SurveyTest < ActiveSupport::TestCase
  should encrypt(:access_code).deterministic(true)
end
downcase
class Survey < ActiveRecord::Base
  encrypts :access_code, downcase: true
end

# RSpec
RSpec.describe Survey, type: :model do
  it { should encrypt(:access_code).downcase(true) }
end

# Minitest (Shoulda)
class SurveyTest < ActiveSupport::TestCase
  should encrypt(:access_code).downcase(true)
end
ignore_case
class Survey < ActiveRecord::Base
  encrypts :access_code, deterministic: true, ignore_case: true
end

# RSpec
RSpec.describe Survey, type: :model do
  it { should encrypt(:access_code).ignore_case(true) }
end

# Minitest (Shoulda)
class SurveyTest < ActiveSupport::TestCase
  should encrypt(:access_code).ignore_case(true)
end


73
74
75
# File 'lib/shoulda/matchers/active_record/encrypt_matcher.rb', line 73

def encrypt(value)
  EncryptMatcher.new(value)
end

#have_and_belong_to_many(name) ⇒ AssociationMatcher

The have_and_belong_to_many matcher is used to test that a has_and_belongs_to_many association exists on your model and that the join table exists in the database.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :awards
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_and_belong_to_many(:awards) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:awards)
end

Qualifiers

conditions

Use conditions if your association is defined with a scope that sets the where clause.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :issues, -> { where(difficulty: 'hard') }
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_and_belong_to_many(:issues).
      conditions(difficulty: 'hard')
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:issues).
    conditions(difficulty: 'hard')
end
order

Use order if your association is defined with a scope that sets the order clause.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :projects, -> { order('time_spent') }
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_and_belong_to_many(:projects).
      order('time_spent')
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:projects).
    order('time_spent')
end
class_name

Use class_name to test usage of the :class_name option. This asserts that the model you're referring to actually exists.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :places_visited, class_name: 'City'
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_and_belong_to_many(:places_visited).
      class_name('City')
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:places_visited).
    class_name('City')
end
join_table

Use join_table to test usage of the :join_table option. This asserts that the table you're referring to actually exists.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :issues, join_table: :people_tickets
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_and_belong_to_many(:issues).
      join_table(:people_tickets)
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:issues).
    join_table(:people_tickets)
end
validate

Use validate to test that the :validate option was specified.

class Person < ActiveRecord::Base
  has_and_belongs_to_many :interviews, validate: false
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_and_belong_to_many(:interviews).
      validate(false)
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:interviews).
    validate(false)
end
autosave

Use autosave to assert that the :autosave option was specified.

class Publisher < ActiveRecord::Base
  has_and_belongs_to_many :advertisers, autosave: true
end

# RSpec
RSpec.describe Publisher, type: :model do
  it { should have_and_belong_to_many(:advertisers).autosave(true) }
end

# Minitest (Shoulda)
class AccountTest < ActiveSupport::TestCase
  should have_and_belong_to_many(:advertisers).autosave(true)
end


1380
1381
1382
# File 'lib/shoulda/matchers/active_record/association_matcher.rb', line 1380

def have_and_belong_to_many(name)
  AssociationMatcher.new(:has_and_belongs_to_many, name)
end

#have_db_column(column) ⇒ HaveDbColumnMatcher

The have_db_column matcher tests that the table that backs your model has a specific column.

class CreatePhones < ActiveRecord::Migration
  def change
    create_table :phones do |t|
      t.string :supported_ios_version
    end
  end
end

# RSpec
RSpec.describe Phone, type: :model do
  it { should have_db_column(:supported_ios_version) }
end

# Minitest (Shoulda)
class PhoneTest < ActiveSupport::TestCase
  should have_db_column(:supported_ios_version)
end

Qualifiers

of_type

Use of_type to assert that a column is defined as a certain type.

class CreatePhones < ActiveRecord::Migration
  def change
    create_table :phones do |t|
      t.decimal :camera_aperture
    end
  end
end

# RSpec
RSpec.describe Phone, type: :model do
  it do
    should have_db_column(:camera_aperture).of_type(:decimal)
  end
end

# Minitest (Shoulda)
class PhoneTest < ActiveSupport::TestCase
  should have_db_column(:camera_aperture).of_type(:decimal)
end
of_sql_type

Use of_sql_type to assert that a column is defined as a certain sql_type.

class CreatePhones < ActiveRecord::Migration
  def change
    create_table :phones do |t|
      t.string :camera_aperture, limit: 36
    end
  end
end

# RSpec
RSpec.describe Phone, type: :model do
  it do
    should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
  end
end

# Minitest (Shoulda)
class PhoneTest < ActiveSupport::TestCase
  should have_db_column(:camera_aperture).of_sql_type('varchar(36)')
end
with_options

Use with_options to assert that a column has been defined with certain options (:precision, :limit, :default, :null, :scale, :primary or :array).

class CreatePhones < ActiveRecord::Migration
  def change
    create_table :phones do |t|
      t.decimal :camera_aperture, precision: 1, null: false
    end
  end
end

# RSpec
RSpec.describe Phone, type: :model do
  it do
    should have_db_column(:camera_aperture).
      with_options(precision: 1, null: false)
  end
end

# Minitest (Shoulda)
class PhoneTest < ActiveSupport::TestCase
  should have_db_column(:camera_aperture).
    with_options(precision: 1, null: false)
end


105
106
107
# File 'lib/shoulda/matchers/active_record/have_db_column_matcher.rb', line 105

def have_db_column(column)
  HaveDbColumnMatcher.new(column)
end

#have_db_index(columns) ⇒ HaveDbIndexMatcher

The have_db_index matcher tests that the table that backs your model has a specific index.

You can specify one column:

class CreateBlogs < ActiveRecord::Migration
  def change
    create_table :blogs do |t|
      t.integer :user_id
    end

    add_index :blogs, :user_id
  end
end

# RSpec
RSpec.describe Blog, type: :model do
  it { should have_db_index(:user_id) }
end

# Minitest (Shoulda)
class BlogTest < ActiveSupport::TestCase
  should have_db_index(:user_id)
end

Or you can specify a group of columns:

class CreateBlogs < ActiveRecord::Migration
  def change
    create_table :blogs do |t|
      t.integer :user_id
      t.string :name
    end

    add_index :blogs, :user_id, :name
  end
end

# RSpec
RSpec.describe Blog, type: :model do
  it { should have_db_index([:user_id, :name]) }
end

# Minitest (Shoulda)
class BlogTest < ActiveSupport::TestCase
  should have_db_index([:user_id, :name])
end

Finally, if you're using Rails 5 and PostgreSQL, you can also specify an expression:

class CreateLoggedErrors < ActiveRecord::Migration
  def change
    create_table :logged_errors do |t|
      t.string :code
      t.jsonb :content
    end

    add_index :logged_errors, 'lower(code)::text'
  end
end

# RSpec
RSpec.describe LoggedError, type: :model do
  it { should have_db_index('lower(code)::text') }
end

# Minitest (Shoulda)
class LoggedErrorTest < ActiveSupport::TestCase
  should have_db_index('lower(code)::text')
end

Qualifiers

unique

Use unique to assert that the index is either unique or non-unique:

class CreateBlogs < ActiveRecord::Migration
  def change
    create_table :blogs do |t|
      t.string :domain
      t.integer :user_id
    end

    add_index :blogs, :domain, unique: true
    add_index :blogs, :user_id
  end
end

# RSpec
RSpec.describe Blog, type: :model do
  it { should have_db_index(:name).unique }
  it { should have_db_index(:name).unique(true) }   # if you want to be explicit
  it { should have_db_index(:user_id).unique(false) }
end

# Minitest (Shoulda)
class BlogTest < ActiveSupport::TestCase
  should have_db_index(:name).unique
  should have_db_index(:name).unique(true)   # if you want to be explicit
  should have_db_index(:user_id).unique(false)
end


110
111
112
# File 'lib/shoulda/matchers/active_record/have_db_index_matcher.rb', line 110

def have_db_index(columns)
  HaveDbIndexMatcher.new(columns)
end

#have_delegated_type(name) ⇒ AssociationMatcher

The have_delegated_type matcher is used to ensure that a belong_to association exists on your model using the delegated_type macro.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck)
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable)
end

Qualifiers

types

Use types to test the types that are allowed for the association.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck)
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it do
    should have_delegated_type(:drivable).
      types(%w(Car Truck))
  end
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).
    types(%w(Car Truck))
end
conditions

Use conditions if your association is defined with a scope that sets the where clause.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), scope: -> { where(with_wheels: true) }
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it do
    should have_delegated_type(:drivable).
      conditions(with_wheels: true)
  end
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).
    conditions(everyone_is_perfect: false)
end
order

Use order if your association is defined with a scope that sets the order clause.

class Person < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), scope: -> { order('wheels desc') }
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).order('wheels desc') }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).order('wheels desc')
end
with_primary_key

Use with_primary_key to test usage of the :primary_key option.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), primary_key: 'vehicle_id'
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it do
    should have_delegated_type(:drivable).
      with_primary_key('vehicle_id')
  end
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).
    with_primary_key('vehicle_id')
end
with_foreign_key

Use with_foreign_key to test usage of the :foreign_key option.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), foreign_key: 'drivable_uuid'
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it do
    should have_delegated_type(:drivable).
      with_foreign_key('drivable_uuid')
  end
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).
    with_foreign_key('drivable_uuid')
end
dependent

Use dependent to assert that the :dependent option was specified.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), dependent: :destroy
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).dependent(:destroy) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).dependent(:destroy)
end

To assert that any :dependent option was specified, use true:

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).dependent(true) }
end

To assert that no :dependent option was specified, use false:

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck)
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).dependent(false) }
end
counter_cache

Use counter_cache to assert that the :counter_cache option was specified.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), counter_cache: true
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).counter_cache(true) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).counter_cache(true)
end
touch

Use touch to assert that the :touch option was specified.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), touch: true
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).touch(true) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).touch(true)
end
autosave

Use autosave to assert that the :autosave option was specified.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), autosave: true
end

# RSpec
RSpec.describe Vehicle, type: :model do
  it { should have_delegated_type(:drivable).autosave(true) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).autosave(true)
end
inverse_of

Use inverse_of to assert that the :inverse_of option was specified.

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), inverse_of: :vehicle
end

# RSpec
describe Vehicle
  it { should have_delegated_type(:drivable).inverse_of(:vehicle) }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).inverse_of(:vehicle)
end
required

Use required to assert that the association is not allowed to be nil. (Enabled by default in Rails 5+.)

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), required: true
end

# RSpec
describe Vehicle
  it { should have_delegated_type(:drivable).required }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).required
end
without_validating_presence

Use without_validating_presence with belong_to to prevent the matcher from checking whether the association disallows nil (Rails 5+ only). This can be helpful if you have a custom hook that always sets the association to a meaningful value:

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck)

  before_validation :autoassign_drivable

  private

  def autoassign_drivable
    self.drivable = Car.create!
  end
end

# RSpec
describe Vehicle
  it { should have_delegated_type(:drivable).without_validating_presence }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).without_validating_presence
end
optional

Use optional to assert that the association is allowed to be nil. (Rails 5+ only.)

class Vehicle < ActiveRecord::Base
  delegated_type :drivable, types: %w(Car Truck), optional: true
end

# RSpec
describe Vehicle
  it { should have_delegated_type(:drivable).optional }
end

# Minitest (Shoulda)
class VehicleTest < ActiveSupport::TestCase
  should have_delegated_type(:drivable).optional
end


687
688
689
# File 'lib/shoulda/matchers/active_record/association_matcher.rb', line 687

def have_delegated_type(name)
  AssociationMatcher.new(:belongs_to, name)
end

#have_implicit_order_column(column_name) ⇒ HaveImplicitOrderColumnMatcher

The have_implicit_order_column matcher tests that the model has implicit_order_column assigned to one of the table columns.

class Product < ApplicationRecord
  self.implicit_order_column = :created_at
end

# RSpec
RSpec.describe Product, type: :model do
  it { should have_implicit_order_column(:created_at) }
end

# Minitest (Shoulda)
class ProductTest < ActiveSupport::TestCase
  should have_implicit_order_column(:created_at)
end


23
24
25
# File 'lib/shoulda/matchers/active_record/have_implicit_order_column.rb', line 23

def have_implicit_order_column(column_name)
  HaveImplicitOrderColumnMatcher.new(column_name)
end

#have_many(name) ⇒ AssociationMatcher

The have_many matcher is used to test that a has_many or has_many :through association exists on your model.

class Person < ActiveRecord::Base
  has_many :friends
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:friends) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:friends)
end

Note that polymorphic associations are automatically detected and do not need any qualifiers:

class Person < ActiveRecord::Base
  has_many :pictures, as: :imageable
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:pictures) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:pictures)
end

Qualifiers

conditions

Use conditions if your association is defined with a scope that sets the where clause.

class Person < ActiveRecord::Base
  has_many :coins, -> { where(quality: 'mint') }
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:coins).conditions(quality: 'mint') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:coins).conditions(quality: 'mint')
end
order

Use order if your association is defined with a scope that sets the order clause.

class Person < ActiveRecord::Base
  has_many :shirts, -> { order('color') }
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:shirts).order('color') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:shirts).order('color')
end
class_name

Use class_name to test usage of the :class_name option. This asserts that the model you're referring to actually exists.

class Person < ActiveRecord::Base
  has_many :hopes, class_name: 'Dream'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:hopes).class_name('Dream') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:hopes).class_name('Dream')
end
with_primary_key

Use with_primary_key to test usage of the :primary_key option.

class Person < ActiveRecord::Base
  has_many :worries, primary_key: 'worrier_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:worries).with_primary_key('worrier_id') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:worries).with_primary_key('worrier_id')
end
with_foreign_key

Use with_foreign_key to test usage of the :foreign_key option.

class Person < ActiveRecord::Base
  has_many :worries, foreign_key: 'worrier_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:worries).with_foreign_key('worrier_id') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:worries).with_foreign_key('worrier_id')
end
with_foreign_type

Use with_foreign_type to test usage of the :foreign_type option.

class Hotel < ActiveRecord::Base
  has_many :visitors, foreign_key: 'facility_type', as: :location
end

# RSpec
RSpec.describe Hotel, type: :model do
  it { should have_many(:visitors).with_foreign_type('facility_type') }
end

# Minitest (Shoulda)
class HotelTest < ActiveSupport::TestCase
  should have_many(:visitors).with_foreign_type('facility_type')
end
dependent

Use dependent to assert that the :dependent option was specified.

class Person < ActiveRecord::Base
  has_many :secret_documents, dependent: :destroy
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:secret_documents).dependent(:destroy) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:secret_documents).dependent(:destroy)
end
through

Use through to test usage of the :through option. This asserts that the association you are going through actually exists.

class Person < ActiveRecord::Base
  has_many :acquaintances, through: :friends
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:acquaintances).through(:friends) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:acquaintances).through(:friends)
end
source

Use source to test usage of the :source option on a :through association.

class Person < ActiveRecord::Base
  has_many :job_offers, through: :friends, source: :opportunities
end

# RSpec
RSpec.describe Person, type: :model do
  it do
    should have_many(:job_offers).
      through(:friends).
      source(:opportunities)
  end
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:job_offers).
    through(:friends).
    source(:opportunities)
end
validate

Use validate to assert that the :validate option was specified.

class Person < ActiveRecord::Base
  has_many :ideas, validate: false
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_many(:ideas).validate(false) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_many(:ideas).validate(false)
end
autosave

Use autosave to assert that the :autosave option was specified.

class Player < ActiveRecord::Base
  has_many :games, autosave: true
end

# RSpec
RSpec.describe Player, type: :model do
  it { should have_many(:games).autosave(true) }
end

# Minitest (Shoulda)
class PlayerTest < ActiveSupport::TestCase
  should have_many(:games).autosave(true)
end
index_errors

Use index_errors to assert that the :index_errors option was specified.

class Player < ActiveRecord::Base
  has_many :games, index_errors: true
end

# RSpec
RSpec.describe Player, type: :model do
  it { should have_many(:games).index_errors(true) }
end

# Minitest (Shoulda)
class PlayerTest < ActiveSupport::TestCase
  should have_many(:games).index_errors(true)
end
inverse_of

Use inverse_of to assert that the :inverse_of option was specified.

class Organization < ActiveRecord::Base
  has_many :employees, inverse_of: :company
end

# RSpec
describe Organization
  it { should have_many(:employees).inverse_of(:company) }
end

# Minitest (Shoulda)
class OrganizationTest < ActiveSupport::TestCase
  should have_many(:employees).inverse_of(:company)
end


975
976
977
# File 'lib/shoulda/matchers/active_record/association_matcher.rb', line 975

def have_many(name)
  AssociationMatcher.new(:has_many, name)
end

#have_many_attached(name) ⇒ HaveAttachedMatcher

The have_many_attached matcher tests usage of the has_many_attached macro.

Example

class Message < ApplicationRecord
  has_many_attached :images
end

# RSpec
RSpec.describe Message, type: :model do
  it { should have_many_attached(:images) }
end

# Minitest (Shoulda)
class MessageTest < ActiveSupport::TestCase
  should have_many_attached(:images)
end


50
51
52
# File 'lib/shoulda/matchers/active_record/have_attached_matcher.rb', line 50

def have_many_attached(name)
  HaveAttachedMatcher.new(:many, name)
end

#have_one(name) ⇒ AssociationMatcher

The have_one matcher is used to test that a has_one or has_one :through association exists on your model.

class Person < ActiveRecord::Base
  has_one :partner
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:partner) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:partner)
end

Qualifiers

conditions

Use conditions if your association is defined with a scope that sets the where clause.

class Person < ActiveRecord::Base
  has_one :pet, -> { where('weight < 80') }
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:pet).conditions('weight < 80') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:pet).conditions('weight < 80')
end
order

Use order if your association is defined with a scope that sets the order clause.

class Person < ActiveRecord::Base
  has_one :focus, -> { order('priority desc') }
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:focus).order('priority desc') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:focus).order('priority desc')
end
class_name

Use class_name to test usage of the :class_name option. This asserts that the model you're referring to actually exists.

class Person < ActiveRecord::Base
  has_one :chance, class_name: 'Opportunity'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:chance).class_name('Opportunity') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:chance).class_name('Opportunity')
end
dependent

Use dependent to test that the :dependent option was specified.

class Person < ActiveRecord::Base
  has_one :contract, dependent: :nullify
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:contract).dependent(:nullify) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:contract).dependent(:nullify)
end
with_primary_key

Use with_primary_key to test usage of the :primary_key option.

class Person < ActiveRecord::Base
  has_one :job, primary_key: 'worker_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:job).with_primary_key('worker_id') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:job).with_primary_key('worker_id')
end
with_foreign_key

Use with_foreign_key to test usage of the :foreign_key option.

class Person < ActiveRecord::Base
  has_one :job, foreign_key: 'worker_id'
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:job).with_foreign_key('worker_id') }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:job).with_foreign_key('worker_id')
end
with_foreign_type

Use with_foreign_type to test usage of the :foreign_type option.

class Hotel < ActiveRecord::Base
  has_one :special_guest, foreign_type: 'facility_type', as: :location
end

# RSpec
RSpec.describe Hotel, type: :model do
  it { should have_one(:special_guest).with_foreign_type('facility_type') }
end

# Minitest (Shoulda)
class HotelTest < ActiveSupport::TestCase
  should have_one(:special_guest).with_foreign_type('facility_type')
end
through

Use through to test usage of the :through option. This asserts that the association you are going through actually exists.

class Person < ActiveRecord::Base
  has_one :life, through: :partner
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:life).through(:partner) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:life).through(:partner)
end
source

Use source to test usage of the :source option on a :through association.

class Person < ActiveRecord::Base
  has_one :car, through: :partner, source: :vehicle
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:car).through(:partner).source(:vehicle) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:car).through(:partner).source(:vehicle)
end
validate

Use validate to assert that the the :validate option was specified.

class Person < ActiveRecord::Base
  has_one :parking_card, validate: false
end

# RSpec
RSpec.describe Person, type: :model do
  it { should have_one(:parking_card).validate(false) }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:parking_card).validate(false)
end
autosave

Use autosave to assert that the :autosave option was specified.

class Account < ActiveRecord::Base
  has_one :bank, autosave: true
end

# RSpec
RSpec.describe Account, type: :model do
  it { should have_one(:bank).autosave(true) }
end

# Minitest (Shoulda)
class AccountTest < ActiveSupport::TestCase
  should have_one(:bank).autosave(true)
end
required

Use required to assert that the association is not allowed to be nil. (Rails 5+ only.)

class Person < ActiveRecord::Base
  has_one :brain, required: true
end

# RSpec
describe Person
  it { should have_one(:brain).required }
end

# Minitest (Shoulda)
class PersonTest < ActiveSupport::TestCase
  should have_one(:brain).required
end


1222
1223
1224
# File 'lib/shoulda/matchers/active_record/association_matcher.rb', line 1222

def have_one(name)
  AssociationMatcher.new(:has_one, name)
end

#have_one_attached(name) ⇒ HaveAttachedMatcher

The have_one_attached matcher tests usage of the has_one_attached macro.

Example

class User < ApplicationRecord
  has_one_attached :avatar
end

# RSpec
RSpec.describe User, type: :model do
  it { should have_one_attached(:avatar) }
end

# Minitest (Shoulda)
class UserTest < ActiveSupport::TestCase
  should have_one_attached(:avatar)
end


25
26
27
# File 'lib/shoulda/matchers/active_record/have_attached_matcher.rb', line 25

def have_one_attached(name)
  HaveAttachedMatcher.new(:one, name)
end

#have_readonly_attribute(value) ⇒ HaveReadonlyAttributeMatcher

The have_readonly_attribute matcher tests usage of the attr_readonly macro.

class User < ActiveRecord::Base
  attr_readonly :password
end

# RSpec
RSpec.describe User, type: :model do
  it { should have_readonly_attribute(:password) }
end

# Minitest (Shoulda)
class UserTest < ActiveSupport::TestCase
  should have_readonly_attribute(:password)
end


23
24
25
# File 'lib/shoulda/matchers/active_record/have_readonly_attribute_matcher.rb', line 23

def have_readonly_attribute(value)
  HaveReadonlyAttributeMatcher.new(value)
end

#have_rich_text(rich_text_attribute) ⇒ HaveRichTextMatcher

The have_rich_text matcher tests usage of the has_rich_text macro.

Example

class Post < ActiveRecord
  has_rich_text :content
end

# RSpec
RSpec.describe Post, type: :model do
  it { should have_rich_text(:content) }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should have_rich_text(:content)
end


25
26
27
# File 'lib/shoulda/matchers/active_record/have_rich_text_matcher.rb', line 25

def have_rich_text(rich_text_attribute)
  HaveRichTextMatcher.new(rich_text_attribute)
end

#have_secure_token(token_attribute = :token) ⇒ HaveSecureToken

The have_secure_token matcher tests usage of the has_secure_token macro.

class User < ActiveRecord
  has_secure_token
  has_secure_token :auth_token
end

# RSpec
RSpec.describe User, type: :model do
  it { should have_secure_token }
  it { should have_secure_token(:auth_token) }
end

# Minitest (Shoulda)
class UserTest < ActiveSupport::TestCase
  should have_secure_token
  should have_secure_token(:auth_token)
end

Qualifiers

ignoring_check_for_db_index

By default, this matcher tests that an index is defined on your token column. Use ignoring_check_for_db_index if this is not the case.

class User < ActiveRecord
  has_secure_token :auth_token
end

# RSpec
RSpec.describe User, type: :model do
  it { should have_secure_token(:auth_token).ignoring_check_for_db_index }
end

# Minitest (Shoulda)
class UserTest < ActiveSupport::TestCase
  should have_secure_token(:auth_token).ignoring_check_for_db_index
end


47
48
49
# File 'lib/shoulda/matchers/active_record/have_secure_token_matcher.rb', line 47

def have_secure_token(token_attribute = :token)
  HaveSecureTokenMatcher.new(token_attribute)
end

#normalize(*attributes) ⇒ NormalizeMatcher

The normalize matcher is used to ensure attribute normalizations are transforming attribute values as expected.

Take this model for example:

class User < ActiveRecord::Base
  normalizes :email, with: -> email { email.strip.downcase }
end

You can use normalize providing an input and defining the expected normalization output:

# RSpec
RSpec.describe User, type: :model do
  it do
    should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
  end
end

# Minitest (Shoulda)
class User < ActiveSupport::TestCase
  should normalize(:email).from(" ME@XYZ.COM\n").to("me@xyz.com")
end

You can use normalize to test multiple attributes at once:

class User < ActiveRecord::Base
  normalizes :email, :handle, with: -> value { value.strip.downcase }
end

# RSpec
RSpec.describe User, type: :model do
  it do
    should normalize(:email, :handle).from(" Example\n").to("example")
  end
end

# Minitest (Shoulda)
class User < ActiveSupport::TestCase
  should normalize(:email, :handle).from(" Example\n").to("example")
end

If the normalization accepts nil values with the apply_to_nil option, you just need to use .from(nil).to("Your expected value here").

class User < ActiveRecord::Base
  normalizes :name, with: -> name { name&.titleize || 'Untitled' },
    apply_to_nil: true
end

# RSpec
RSpec.describe User, type: :model do
  it { should normalize(:name).from("jane doe").to("Jane Doe") }
  it { should normalize(:name).from(nil).to("Untitled") }
end

# Minitest (Shoulda)
class User < ActiveSupport::TestCase
  should normalize(:name).from("jane doe").to("Jane Doe")
  should normalize(:name).from(nil).to("Untitled")
end


68
69
70
71
72
73
74
# File 'lib/shoulda/matchers/active_record/normalize_matcher.rb', line 68

def normalize(*attributes)
  if attributes.empty?
    raise ArgumentError, 'need at least one attribute'
  else
    NormalizeMatcher.new(*attributes)
  end
end

#serialize(name) ⇒ SerializeMatcher

The serialize matcher tests usage of the serialize macro.

class Product < ActiveRecord::Base
  serialize :customizations
end

# RSpec
RSpec.describe Product, type: :model do
  it { should serialize(:customizations) }
end

# Minitest (Shoulda)
class ProductTest < ActiveSupport::TestCase
  should serialize(:customizations)
end

Qualifiers

as

Use as if you are using a custom serializer class.

class ProductSpecsSerializer
  def load(string)
    # ...
  end

  def dump(options)
    # ...
  end
end

class Product < ActiveRecord::Base
  serialize :specifications, ProductSpecsSerializer
end

# RSpec
RSpec.describe Product, type: :model do
  it do
    should serialize(:specifications).
      as(ProductSpecsSerializer)
  end
end

# Minitest (Shoulda)
class ProductTest < ActiveSupport::TestCase
  should serialize(:specifications).
    as(ProductSpecsSerializer)
end
as_instance_of

Use as_instance_of if you are using a custom serializer object.

class ProductOptionsSerializer
  def load(string)
    # ...
  end

  def dump(options)
    # ...
  end
end

class Product < ActiveRecord::Base
  serialize :options, ProductOptionsSerializer.new
end

# RSpec
RSpec.describe Product, type: :model do
  it do
    should serialize(:options).
      as_instance_of(ProductOptionsSerializer)
  end
end

# Minitest (Shoulda)
class ProductTest < ActiveSupport::TestCase
  should serialize(:options).
    as_instance_of(ProductOptionsSerializer)
end


88
89
90
# File 'lib/shoulda/matchers/active_record/serialize_matcher.rb', line 88

def serialize(name)
  SerializeMatcher.new(name)
end

#validate_uniqueness_of(attr) ⇒ ValidateUniquenessOfMatcher

The validate_uniqueness_of matcher tests usage of the validates_uniqueness_of validation. It first checks for an existing instance of your model in the database, creating one if necessary. It then takes a new instance of that model and asserts that it fails validation if the attribute or attributes you've specified in the validation are set to values which are the same as those of the pre-existing record (thereby failing the uniqueness check).

class Post < ActiveRecord::Base
  validates :permalink, uniqueness: true
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:permalink) }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:permalink)
end

Caveat

This matcher works a bit differently than other matchers. As noted before, it will create an instance of your model if one doesn't already exist. Sometimes this step fails, especially if you have database-level restrictions on any attributes other than the one which is unique. In this case, the solution is to populate these attributes with values before you call validate_uniqueness_of.

For example, say you have the following migration and model:

class CreatePosts < ActiveRecord::Migration
  def change
    create_table :posts do |t|
      t.string :title
      t.text :content, null: false
    end
  end
end

class Post < ActiveRecord::Base
  validates :title, uniqueness: true
end

You may be tempted to test the model like this:

RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:title) }
end

However, running this test will fail with an exception such as:

Shoulda::Matchers::ActiveRecord::ValidateUniquenessOfMatcher::ExistingRecordInvalid:
  validate_uniqueness_of works by matching a new record against an
  existing record. If there is no existing record, it will create one
  using the record you provide.

  While doing this, the following error was raised:

    PG::NotNullViolation: ERROR:  null value in column "content" violates not-null constraint
    DETAIL:  Failing row contains (1, null, null).
    : INSERT INTO "posts" DEFAULT VALUES RETURNING "id"

  The best way to fix this is to provide the matcher with a record where
  any required attributes are filled in with valid values beforehand.

(The exact error message will differ depending on which database you're using, but you get the idea.)

This happens because validate_uniqueness_of tries to create a new post but cannot do so because of the content attribute: though unrelated to this test, it nevertheless needs to be filled in. As indicated at the end of the error message, the solution is to build a custom Post object ahead of time with content filled in:

RSpec.describe Post, type: :model do
  describe "validations" do
    subject { Post.new(content: "Here is the content") }
    it { should validate_uniqueness_of(:title) }
  end
end

Or, if you're using FactoryBot and you have a post factory defined which automatically fills in content, you can say:

RSpec.describe Post, type: :model do
  describe "validations" do
    subject { FactoryBot.build(:post) }
    it { should validate_uniqueness_of(:title) }
  end
end

Qualifiers

Use on if your validation applies only under a certain context.

class Post < ActiveRecord::Base
  validates :title, uniqueness: true, on: :create
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:title).on(:create) }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:title).on(:create)
end
with_message

Use with_message if you are using a custom validation message.

class Post < ActiveRecord::Base
  validates :title, uniqueness: true, message: 'Please choose another title'
end

# RSpec
RSpec.describe Post, type: :model do
  it do
    should validate_uniqueness_of(:title).
      with_message('Please choose another title')
  end
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:title).
    with_message('Please choose another title')
end
scoped_to

Use scoped_to to test usage of the :scope option. This asserts that a new record fails validation if not only the primary attribute is not unique, but the scoped attributes are not unique either.

class Post < ActiveRecord::Base
  validates :slug, uniqueness: { scope: :journal_id }
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:slug).scoped_to(:journal_id) }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:slug).scoped_to(:journal_id)
end

NOTE: Support for testing uniqueness validation scoped to an array of associations is not available.

For more information, please refer to https://github.com/thoughtbot/shoulda-matchers/issues/814

case_insensitive

Use case_insensitive to test usage of the :case_sensitive option with a false value. This asserts that the uniquable attributes fail validation even if their values are a different case than corresponding attributes in the pre-existing record.

class Post < ActiveRecord::Base
  validates :key, uniqueness: { case_sensitive: false }
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:key).case_insensitive }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:key).case_insensitive
end
ignoring_case_sensitivity

By default, validate_uniqueness_of will check that the validation is case sensitive: it asserts that uniquable attributes pass validation when their values are in a different case than corresponding attributes in the pre-existing record.

Use ignoring_case_sensitivity to skip this check. This qualifier is particularly handy if your model has somehow changed the behavior of attribute you're testing so that it modifies the case of incoming values as they are set. For instance, perhaps you've overridden the writer method or added a before_validation callback to normalize the attribute.

class User < ActiveRecord::Base
  validates :email, uniqueness: true

  def email=(value)
    super(value.downcase)
  end
end

# RSpec
RSpec.describe Post, type: :model do
  it do
    should validate_uniqueness_of(:email).ignoring_case_sensitivity
  end
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:email).ignoring_case_sensitivity
end
allow_nil

Use allow_nil to assert that the attribute allows nil.

class Post < ActiveRecord::Base
  validates :author_id, uniqueness: true, allow_nil: true
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:author_id).allow_nil }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:author_id).allow_nil
end
allow_blank

Use allow_blank to assert that the attribute allows a blank value.

class Post < ActiveRecord::Base
  validates :author_id, uniqueness: true, allow_blank: true
end

# RSpec
RSpec.describe Post, type: :model do
  it { should validate_uniqueness_of(:author_id).allow_blank }
end

# Minitest (Shoulda)
class PostTest < ActiveSupport::TestCase
  should validate_uniqueness_of(:author_id).allow_blank
end


261
262
263
# File 'lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb', line 261

def validate_uniqueness_of(attr)
  ValidateUniquenessOfMatcher.new(attr)
end