Method: Shoulda::Matchers::ActiveRecord#validate_uniqueness_of

Defined in:
lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb

#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](github.com/thoughtbot/factory_bot) 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 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

##### alternatives

Use ‘alternatives` to specify alternative valid values to use

for testing uniqueness.

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

  # RSpec
  RSpec.describe Post, type: :model do
    it do
      should validate_uniqueness_of(:title).alternatives('Alternative Title')
    end
  end

  # Minitest (Shoulda)
  class PostTest < ActiveSupport::TestCase
    should validate_uniqueness_of(:title).alternatives(['Alternative Title', 'Another Title'])
  end

Examples:

it { should validate_uniqueness_of(:title).alternatives('Alternative Title') }
it { should validate_uniqueness_of(:title).alternatives(['Title 1', 'Title 2']) }

Parameters:

  • values (String, Array<String>)

    Alternative value(s) to use for testing uniqueness instead of using the ‘succ` operator on the existing value.

Returns:



291
292
293
# File 'lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb', line 291

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