Search code examples
ruby-on-railspostgresqlsimple-formcocoon-gem

Validate uniqueness of nested association in scope


Rails 4.2, PostgreSQL 9.3

Model relations are:

class Nesting < ActiveRecord::Base
  belongs_to :product
  belongs_to :configurator, touch: true

  validates :product_id, uniqueness: { scope: :configurator }
end

class Configurator < ActiveRecord::Base
  has_many   :nestings, dependent: :destroy
  has_many   :products, through: :nestings

  accepts_nested_attributes_for :nestings, reject_if: :all_blank, allow_destroy: true
end

Situation where I create configurator with product foo and then try to update it to add product foo works fine. I get error has_already_taken.

But when I add two identical products at once validations doesn't work. How do I validate uniqueness of product_id in Nesting model in scope of Configurator?

My views are pretty basic:

= simple_form_for @configurator, remote: true do |f|
  = f.simple_fields_for :nestings do |nesting|
    = render 'nesting_fields', f: nesting
  = link_to_add_association 'add product', f, :nestings, class: 'btn btn-default'
  = f.button :submit

_nesting_fields.html.slim

.nested-fields
  .form-inline
    = f.association :product, collection: @products
    = link_to_remove_association f, class: 'btn btn-default' do
      .glyphicon.glyphicon-remove

One of the quick solutions is to check uniqueness of product_id's in parameters in controllers action. But I don't like the idea that validation happen in controllers action.


Solution

  • Adding validates_associated on Configurator may help, but I would add a uniqueness constraint to Nesting. In a migration:

    class AddUniqueIndexToNesting < ActiveRecord::Migration
      def change
        add_index :nestings, [:configurator_id, :product_id], unique: true
      end
    end
    

    Also see:

    Rails 3: Uniqueness validation for nested fields_for

    Rails - Validate Nested Attributes Uniqueness with scope parent of parent

    https://github.com/rails/rails/issues/1572