james
james

Reputation: 4049

Rails how to validate uniqueness across child objects with nested form

In a nested form, I want the user to have the ability to create or modify all of a Parent's Childs at one time. So let's so the params that are passed are like this:

{"childs_attributes" => [{attribute:1}, {attribute:2}, {attribute:3}...]}

I would like a validation that says for any one Parent, the attributes of all of its Childs must be unique. In other words, in the above example, that's OK because you'd get:

Parent.childs.pluck(:attribute).uniq.length == Parent.childs.pluck(:attribute).length

However, if the params passed were like below, it'd be a violation of the validation rule:

{"childs_attributes" => [{attribute:1}, {attribute:2}, {attribute:3}...]}

So far the only solution I've come up with to do this validation is in the Controller... which I know is bad practice because we want to push this to the model.

The problem is that if in the model I have something like the below:

class Parent
  validate :unique_attribute_on_child

  def unique_attribute_on_child
    attribute_list = self.childs.pluck(:attribute) 
    if attribute_list.uniq.length != attribute_list.length
      errors[:base] << "Parent contains Child(s) with same Attribute"
    end
  end
end

That won't work because self.childs.pluck(:attribute) won't return the attribute passed in the current update, since the current update won't have saved yet.

I guess I could do something like an after_save but that feels really convoluted since it's going back and reversing db commits (not to mention, the code as written below [non tested, just an example] likely leads to a circular loop if I'm not careful, since Parent validate associated children)

after_save :unique_attribute_on_child

def unique_attribute_on_child
  attribute_list = self.childs.pluck(:attribute) 
    if attribute_list.uniq.length != attribute_list.length
      self.childs.each { |c| c.update_attributes(attribute:nil) }
      errors[:base] << "Parent contains Child(s) with same Attribute"
    end
  end
end

Other ideas?

Upvotes: 1

Views: 1110

Answers (1)

Okomikeruko
Okomikeruko

Reputation: 1173

My first impulse is to suggest that Rails uses smart pluralization and to try using children instead of childs, but I think this was just the example.

I now recommend that you change your strategy. Call the validation on the children like so:

class Child < ActiveRecord::Base 
  belongs_to :parent
  ...
  validates :child_attribute, uniqueness: { scope: :parent }
  ...
end

Upvotes: 3

Related Questions