I have a User
model that has a child association of items
. The :name
of items should be unique for the user, but it should allow different users to have an item with the same name.
The Item model is currently set up as:
class Item < ApplicationRecord
belongs_to :user
validates :name, case_sensitive: false, uniqueness: { scope: :user }
end
And this works to validate intra-user, but still allows other users to save an Item with the same name.
How do I test this with RSpec/Shoulda?
My current test is written as:
describe 'validations' do
it { should validate_uniqueness_of(:name).case_insensitive.scoped_to(:user) }
end
But this test fails because:
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 however, is the behavior that I want (other than the weird part that Shoulda picks nil
for user). When the user is different, the same name should be valid.
It's possible that I'm not using the scope test correctly or that this is impossible with Shoulda, here is the description of scoped tests. In this case, how would you write a model test to test this behavior?
The solution to doing this is three-fold:
Scope to :user_id
instead of :user
in the model
Re-write the validations on the model to include all uniqueness requirements as part of a hash
:user_id
The code in the question will work in that it correctly checks for uniqueness case-insensitively, but it is probably best to include all uniqueness requirements as hash anyway since the example in the docs takes this form even for single declarations (also, it's the only way I can find to make Shoulda tests pass with the correct behavior).
This is what the working code looks like:
model
class Item < ApplicationRecord
belongs_to :user
validates :name, uniqueness: { scope: :user_id, case_sensitive: false }
end
test
RSpec.describe Item, type: :model do
describe 'validations' do
it { should validate_uniqueness_of(:name).scoped_to(:user_id).case_insensitive }
end
end