Shoulda-matchers: validates_uniqueness_of fails even with case sensitivity

Created on 17 Jul 2018  ·  7Comments  ·  Source: thoughtbot/shoulda-matchers

My shoulda match for validating uniqueness for emails randomly started failing today. The model containing the attribute, driver, also contains a callback to downcase the email before saving, so case sensitivity shouldn't be an issue. However, it still fails, so I add code in the model and spec to account for case sensitivity. That doesn't work. I also made sure that in the spec, I'm calling FactoryBot.create rather than build, but it's still not working. Here's my system info and code:

Ruby: 2.5
Rails: 5.2
shoulda-matchers: 3.1.2

class Driver < ApplicationRecord
  validates_uniqueness_of :email, case_insensitive: true
  ...
  before_save :downcase_email
  ...

  private

  def downcase_email
    email&.downcase!
  end

And in the spec: it { should validate_uniqueness_of(:email).case_insensitive }

Error message: Driver did not properly validate that :email is case-insensitively
unique.
After taking the given Driver, setting its :email to ‹"an arbitrary
value"›, and saving it as the existing record, then making a new
Driver and setting its :email to ‹"an arbitrary value"› as well, the
matcher expected the new Driver to be invalid, but it was valid
instead.

Most helpful comment

You're downcasing the email. That messes with the matcher because it verifies that the uniqueness validation runs case-insensitively, i.e., that uppercase and lowercase input both cause the record to pass validation. Unfortunately if uppercase input gets converted into lowercase then this check cannot take place effectively. To get around this, use ignoring_case_sensitivity on your matcher instead of case_insensitive.

In addition, the option you pass to your validation and the qualifier you pass to the matcher should be flipped. So it works like:

validates_uniqueness_of :email, case_sensitive: false
it { should validate_uniqueness_of(:email).case_insensitive }

All 7 comments

You're downcasing the email. That messes with the matcher because it verifies that the uniqueness validation runs case-insensitively, i.e., that uppercase and lowercase input both cause the record to pass validation. Unfortunately if uppercase input gets converted into lowercase then this check cannot take place effectively. To get around this, use ignoring_case_sensitivity on your matcher instead of case_insensitive.

In addition, the option you pass to your validation and the qualifier you pass to the matcher should be flipped. So it works like:

validates_uniqueness_of :email, case_sensitive: false
it { should validate_uniqueness_of(:email).case_insensitive }

Hmm, that doesn't work - I actually went back and commented out the downcase method, but it was still running into the error. And even after making the changes to the validation options, it still fails with the same error, regardless of whether I'm downcasing the email or not.

Oh sorry, well it really should be:

validates_uniqueness_of :email, case_sensitive: false
it { should validate_uniqueness_of(:email).ignoring_case_insensitivity }

Does that work?

Hmm, I'll try to make that change later. It's really only failing on that one branch, not master or another branch.

Close - last line should be it { should validate_uniqueness_of(:email).ignoring_case_sensitivity }. ignoring_case_insensitivity leads to a NoMethodError.

This doesn't match the examples listed on the website - is it possible to submit a PR to change that? If so, I'd be happy to make the PR this weekend.

Ugh, sorry - that's really what I meant :)

Which examples do you mean? I'm not sure if you saw this, but we do have the qualifier documented here: http://matchers.shoulda.io/docs/v3.1.1/Shoulda/Matchers/ActiveRecord.html#validate_uniqueness_of-instance_method.

I'm also having this issue.

  • Shoulda Matcher version 3.1.2
  • Rails 5.2
  • Rspec Rails 3.7.2

Failure/Error: it { should validate_uniqueness_of(:name).case_insensitive }

   Building did not properly validate that :name is case-insensitively
   unique.
     Given an existing Building whose :name is ‹" ;\u{4891C}K
     Z=\u0001\u{C03D4}厗 `\u{96B46}\u{723BE}\u{45623}d 8*Ⱪ& \n
     \u{320A0}荟$\u{69086}` 돳𪖃㔷# "›, after making a new Building and setting
     its :name to a different value, ‹" ;\u{4891C}k z=\u0001\u{C03D4}厗
     `\u{96B46}\u{723BE}\u{45623}D 8*ⱪ& \n \u{320A0}荟$\u{69086}` 돳𪖃㔷# "›,
     the matcher expected the new Building to be invalid, but it was valid
     instead.
 # ./spec/models/building_spec.rb:9:in `block (3 levels) in <top (required)>'

The validation in the model that is causing the problem.
validates :name, presence: true, length: { within: 7..50 }, uniqueness: { case_sensitive: false }

The shoulda matcher.
it { should validate_uniqueness_of(:name).case_insensitive }

Any help or guidance would be good, sometimes it works, sometimes it fails.

EDIT

To get this working on my CLI parts I changed the database to UTF8MB4 and used a monkey patch to make sure that all strings were varchar(191) in rails. I think this was more of a rails/active-record issue than a shoulda issue.

Was this page helpful?
0 / 5 - 0 ratings