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.
精彩评论