Shoulda-matchers: Unexpected failure when using case_insensitive and scoped_to

Created on 18 Mar 2018  Â·  4Comments  Â·  Source: thoughtbot/shoulda-matchers

Summary:

When using case_insensitive with scoped_to Shoulda returns a false failure despite the test output indicating that the desired behavior correctly executed.

Here is a minimal test repo where you can run the test.

Detail:
This is copied from my SO question here

I have a user model set up as:

class Item < ApplicationRecord
  belongs_to :user
  validates :name, case_sensitive: false, uniqueness: { scope: :user }
end

The purpose is to allow different users to create items with the same name, but not to allow the same user to create multiple items with the same name.

I have a test written like this:

describe 'validations' do
  it { should validate_uniqueness_of(:name).case_insensitive.scoped_to(:user) }
end

The output of which is:

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

     Item did not properly validate that :name is case-insensitively
     unique within the scope of :user.
       After taking the given Item, setting its :name to ‹"an
       arbitrary value"›, and saving it as the existing record, then making a
       new Item and setting its :name to a different value, ‹"AN
       ARBITRARY VALUE"› and its :user to a different value, ‹nil›, the
       matcher expected the new Item to be invalid, but it was valid
       instead.

This though is the desired behavior (different users should be able to create items with the same name).

I would be happy to try to write a test case for this. I am a little confused though. The error message seems to be coming from the test at Ln 891, however the param being passed is .case_insensitive not case_sensitive: true.

There is a similar issue in #814 however this doesn't throw the undefined error and it's not passing scope as an array.

Most helpful comment

@nicholasshirley Yeah this is definitely related to #814 -- specifically, scoped_to doesn't really work very well with associations. However, what happens if instead of scoped_to(:user) you try scoped_to(:user_id)?

All 4 comments

@nicholasshirley Yeah this is definitely related to #814 -- specifically, scoped_to doesn't really work very well with associations. However, what happens if instead of scoped_to(:user) you try scoped_to(:user_id)?

@mcmire I had tried that before, but there was another issue I didn't realize until playing with it again.

Both scope and case_sensitive: false need to be passed as part of the hash for uniqueness on the model in order to get the tests to pass:

class Item < ApplicationRecord
  belongs_to :user
  validates :name, uniqueness: { scope: :user_id, case_sensitive: false }
end

Then it's possible to write the test as:

describe 'validations' do
  it { should validate_uniqueness_of(:name).scoped_to(:user_id).case_insensitive }
end

This will pass and testing in the console shows that it blocks records with the same name. The previous code will work even though the test fails, but it's probably better to do it this way anyway since the example in the docs passes uniqueness as a hash even for a single declaration.

Thanks for the great work! I'd be happy to document this somewhere if you think it would helpful, just let me know where and I'll send a PR with a draft.

@nicholasshirley I gotcha, I didn't catch that in your original example. In reality validates has been around for a long time now and so it's probably true that people tend to use it vs. the validates_* methods (e.g. validates_uniqueness_of). So this makes me think that it would be worth it to go through the docs for _all_ of the validation matchers and update them en masse to use the current way of doing things. Is that something you'd be able to do?

@mcmire Yes, I will take a look and gather them all into a PR.

Was this page helpful?
0 / 5 - 0 ratings