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  

class Color < ActiveRecord::Base
    has_many :item_colors
    has_many :items, :through => item_colors

class ItemColor < ActiveRecord::Base
    belongs_to :item
    belongs_to :color

    validates_uniqueness_of :color, scope: :item


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

    it { should_not be_valid }


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

1 answer

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")
  item.colors |= [color]




All Articles