has_many and validates_associated

Given the following code:

class MyClass < ActiveRecord::Base

  has_many :technical_contacts   has_many :schedule_contacts

  validates_associated :technical_contacts, :schedule_contacts

  protected

  def validate     validate_contacts(technical_contacts)     validate_contacts(schedule_contacts)   end

  private

  def validate_contacts(contacts)     contacts.each_with_index{|contact, i|       (i+1).upto(contacts.length-1) do |j|         if contacts[j].email == contact.email           contacts[j].errors.add(:email,                                  'You have already provided this email address')         end       end     }   end

end

If my validate_contacts method adds errors to some contact objects, I would expect @myclass.save to fail but it does not. I have tested that the objects are invalid using the "valid?" method and they are so I don't understand what is going wrong. Can anyone please help?

Craig Nicoll wrote:

Given the following code:

class MyClass < ActiveRecord::Base

  has_many :technical_contacts   has_many :schedule_contacts

  validates_associated :technical_contacts, :schedule_contacts

  protected

  def validate     validate_contacts(technical_contacts)     validate_contacts(schedule_contacts)   end

  private

  def validate_contacts(contacts)     contacts.each_with_index{|contact, i|       (i+1).upto(contacts.length-1) do |j|         if contacts[j].email == contact.email           contacts[j].errors.add(:email,                                  'You have already provided this email address')         end       end     }   end

end

If my validate_contacts method adds errors to some contact objects, I would expect @myclass.save to fail but it does not. I have tested that the objects are invalid using the "valid?" method and they are so I don't understand what is going wrong. Can anyone please help?

Custom "validate" methods run after all other validations, so validates_associated will run before the contact errors are added.

You can either get rid of the validates_associated call and add the base errors in the validate_contacts method, or move the validate_contacts calls to a :validates_each call that is placed before the validates_associated call.

Mark Reginald James wrote: You can either get rid of the validates_associated call and add the base errors in the validate_contacts method, or move the validate_contacts calls to a :validates_each call that is placed before the validates_associated call.>

Hi Mark,

I tried your suggestion but the save method is still working when it shouldn't. Here is my code:

class MyClass < ActiveRecord::Base

  has_many :technical_contacts   has_many :schedule_contacts

  validates_each :technical_contacts do |model, attr, value|     value.each_with_index{|contact, i|       (i+1).upto(value.length-1) do |j|         if value[j].email == contact.email           value[j].errors.add(:email,                                  'You have already provided this email address')         end       end     }   end

  validates_associated :technical_contacts, :schedule_contacts

end

Any help would be most appreciated.

Thanks,

Craig

Craig Nicoll wrote:

I tried your suggestion but the save method is still working when it shouldn't. Here is my code:

class MyClass < ActiveRecord::Base

  has_many :technical_contacts   has_many :schedule_contacts

  validates_each :technical_contacts do |model, attr, value|     value.each_with_index{|contact, i|       (i+1).upto(value.length-1) do |j|         if value[j].email == contact.email           value[j].errors.add(:email,                                  'You have already provided this email address')         end       end     }   end

  validates_associated :technical_contacts, :schedule_contacts

end

With what you have the error messages you add to a contact in the validates_each will be cleared before valid? is called on each of them during the validates_associated.

Is something like this possible?

class Contact < ActiveRecord::Base    validates_uniqueness_of :email, :scope => :my_class_id,      :message => '^You have already provided this email address' end

class TechnicalContact < Contact end class ScheduleContact < Contact end

class MyClass < ActiveRecord::Base    has_many :technical_contacts    has_many :schedule_contacts    validates_associated :technical_contacts, :schedule_contacts end

Otherwise, drop the validates_associated:

validates_each :technical_contacts, :schedule_contacts do |m, a, v|    v.each_with_index{ |contact, i|      (i+1).upto(v.length-1) do |j|        if value[j].email == contact.email          value[j].errors.add(:email, 'Email already provided')          m.errors.add_to_base(a, 'Bad contact email address')        end      end    } end