Reputation: 935
I have the following classes for a many to many relationship between "Item" and "Color".
And "Item" should not have duplicated "Colors", for example:- If "Item1" has "Blue" & "Red" then we cannot add another "Red" to "Item1"
Is this the correct way to set this up?
class Item < ActiveRecord::Base
has_many :item_colors
has_many :colors, :through => item_colors
end
class Color < ActiveRecord::Base
has_many :item_colors
has_many :items, :through => item_colors
end
class ItemColor < ActiveRecord::Base
belongs_to :item
belongs_to :color
validates_uniqueness_of :color, scope: :item
end
My Test for duplicated colors. Is it how to test it?
describe "item should not have duplicated colors" do
before do
@item = FactoryGirl.create(:item)
@color1 = FactoryGirl.create(:color)
@item.colors << @color1
@item.colors << @color1
@item.save
end
it { should_not be_valid }
end
When I try this in rails console, it will fail when I add duplcated color to an item
but instead of getting an error message in item.errors.message
, I got an ActiveRecord exception
"ActiveRecord::RecordInvalid: Validation failed: Color has already been taken"
Please advise.
Upvotes: 3
Views: 95
Reputation: 2313
When you add the second color, it is automatically saved because the parent object @item
is already saved, i.e. it is not a new_record
.
Given it is a has_many :through
association, it is always saved with the bang version of save!
, which in turn raises the exception because your join model ItemColor
fails on the validation of uniqueness.
In your case you have two options:
rescue
the exception and manage the error messages manually or;if you're using a join model just to add the validation layer, you could get rid of it, use a HABTM instead and handle the association as a set, e.g.
> item = FactoryGirl.create(:item)
> color = FactoryGirl.create(:color)
> 10.times { item.colors |= [color] } # you can add it n times...
> item.colors.count # => 1 ...still only one is saved b/c it is a union set.
How does that sound to you?
UPDATE: In case you really want to show an error message, you could, e.g.
if item.colors.include?(color)
item.errors.add(:colors, "color already selected")
else
item.colors |= [color]
end
Upvotes: 3