Configuring and Testing to Avoid Duplication in ActiveRecord for Many Relationships
I have the following classes for the many-to-many relationship between "Item" and "Color".
And "Item" should not duplicate "Colors", for example: - If "Item1" has "blue" and "red", we cannot add another "red" to "Item1"
Is it correct to install it?
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 duplicate colors. How can I check 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 to do this in rails console, it won't work when I add duplicate color to the element but instead of getting an error in item.errors.message
, I got an ActiveRecord exception
"ActiveRecord::RecordInvalid: Validation failed: Color has already been taken"
Please advise.
source to share
When you add a second color, this is automatically saved because the parent is @item
already saved, that is, it is not new_record
.
Considering it is an association has_many :through
, it is always stored with the bang versionsave!
, which in turn throws an exception as your join model ItemColor
fails when checking for uniqueness.
In your case, you have two options:
-
rescue
exclusion and management of error messages manually or; -
If you are using a join model to add a layer of validation, you can get rid of it, use HABTM instead and treat the association as a set, for example
> 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 this sound to you?
UPDATE: If you really want to show the error message, you can for example
if item.colors.include?(color)
item.errors.add(:colors, "color already selected")
else
item.colors |= [color]
end
source to share