开发者

Better validates_associated method for Rails 3?

开发者 https://www.devze.com 2023-01-15 12:34 出处:网络
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\"

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:

  • http://rpheath.com/posts/412-a-better-validates-associated
  • http://pivotallabs.com/users/nick/blog/articles/359-al开发者_JS百科ias-method-chain-validates-associated-informative-error-message

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.


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.


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


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).


Most of the time validates_existence_of is all I need.

0

精彩评论

暂无评论...
验证码 换一张
取 消