18

Rails 3 includes the validates_associated which is automatically called when saving a nested model. The problem with the method is the message is terrible - "Model(s) is invalid"

There have been a few posts attacking this issue for Rails 2:

and there are probably more. It would be great to have a better version as described in these posts that is Rails 3 compatible. The main improvement would be to include why the associated model fails.

1
  • 1
    I agree completely, this would be very desired. Anyone with a smart solution to this problem? Commented Nov 4, 2010 at 19:38

4 Answers 4

5

On the relationship, you can use :autosave => true instead which will try to save children models when you save the parent. This will automatically run the validations of the children and they will report with proper error messages.

Moreover, if you add a presence validation on the child that the parent must be set, and you construct the child objects through the association, you don't even need the autosave flag, and you get a beautiful error message. For example:

class Trip < ActiveRecord::Base
  validates :name, :presence => true

  attr_accessible :name

  has_many :places, dependent: :destroy, :inverse_of => :trip
end

class Place < ActiveRecord::Base
  belongs_to :trip

  validates :name, :trip, presence: true

  attr_accessible :name
end

Then you can get an nice error message with the following usage scenario:

> trip = Trip.new(name: "California")
=> #<Trip id: nil, name: "California"> 
> trip.places.build
=> #<Place id: nil, name: nil, trip_id: nil>
> trip.valid?
=> false
> trip.errors
=> #<ActiveModel::Errors:0x00000004d36518 @base=#<Trip id: nil, name: "California">, @messages={:places=>["is invalid"]}>
> trip.errors[:places]
=> ["is invalid"] 

I think validates_associated is a relic of the era before autosaving of children and isn't the best way to do things any more. Of course that's not necessarily documented well. I'm not 100% sure that this also applies to Rails 2.3, but I have a feeling it does. These changes came when the nested attributes feature was added (which was sometime in 2.x).

This is a simplified snippet of code from a training project I posted on github.

Sign up to request clarification or add additional context in comments.

4 Comments

Something interesting is that submitting forms often passes the relationship ID, meaning that building an object happens like this: post.build(category_id: 3, tags_ids: [1, 5, 7]). That means that validating against :category or :tags won't work, as only :category_id and :tag_ids have been set on the model.
That's correct. If that's what you do, you can validate against the presence of the it, e.g. validates :trip_id, presence: true. This is a little glitch in Rails.
There is another issue then that :trip_id could be set but is referencing an invalid trip ID.
Yeah, but that should be caught by the Trip validations. If you're using accepts_nested_attributes_for, Rails will not save the entire structure unless it all validates. That part works well and basically replaces the older validates_associated.
3

I was having this problem, and in the end I used the solution given here by Ben Lee:

validates associated with model's error message

Ben says:

You can write your own custom validator, based on the code for the built-in validator.

Looking up the source code for validates_associated, we see that it uses the "AssociatedValidator". The source code for that is:

module ActiveRecord
  module Validations
    class AssociatedValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
        record.errors.add(attribute, :invalid, options.merge(:value => value))
      end
    end

    module ClassMethods
      def validates_associated(*attr_names)
        validates_with AssociatedValidator, _merge_attributes(attr_names)
      end
    end
  end
end

So you can use this as an example to create a custom validator that bubbles error messages like this:

module ActiveRecord
  module Validations
    class AssociatedBubblingValidator < ActiveModel::EachValidator
      def validate_each(record, attribute, value)
        (value.is_a?(Array) ? value : [value]).each do |v|
          unless v.valid?
            v.errors.full_messages.each do |msg|
              record.errors.add(attribute, msg, options.merge(:value => value))
            end
          end
        end
      end
    end

    module ClassMethods
      def validates_associated_bubbling(*attr_names)
        validates_with AssociatedBubblingValidator, _merge_attributes(attr_names)
      end
    end
  end
end

You can put this code in an initializer, something like /initializers/associated_bubbling_validator.rb.

Finally, you'd validate like so:

class User < ActiveRecord::Base
 validates_associated_bubbling :account
end

NOTE: the above code is completely untested, but if it doesn't work outright, it is hopefully enough to put you on the right track

1 Comment

I'm getting this error using the above code: undefined method `valid?' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_ManagementDetail:0x007fb74e1fb1e0>. Any idea what the issue is?
1

validates_associated runs the validations specified in the associated object's class. Errors at the parent class level simply say 'my child is invalid'. If you want the details, expose the errors on the child object (at the level of the child's form in the view).

Comments

-1

Most of the time validates_existence_of is all I need.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.