I\'m having a difficult time understanding how to get Rails to show an explicit error message for a child resource that is failing validation when I render an XML template.
Add a validation block in the School
model to merge the errors:
class School < ActiveRecord::Base
has_many :students
validate do |school|
school.students.each do |student|
next if student.valid?
student.errors.full_messages.each do |msg|
# you can customize the error message here:
errors.add_to_base("Student Error: #{msg}")
end
end
end
end
Now @school.errors
will contain the correct errors:
format.xml { render :xml => @school.errors, :status => :unprocessable_entity }
Note:
You don't need a separate method for adding a new student to school, use the following syntax:
school.students.build(:email => email)
errors.add_to_base
has been dropped from Rails 3.0 and above and should be replaced with:
errors[:base] << "Student Error: #{msg}"
I'm not sure if this is the best (or a correct) answer...i'm still learning, but I found this to work pretty well. I haven't tested it extensively, but it does seem to work with rails4:
validate do |school|
school.errors.delete(:students)
school.students.each do |student|
next if student.valid?
school.errors.add(:students, student.errors)
end
end
Here's an example that could stand some DRYing:
def join_model_and_association_errors!(model)
klass = model.class
has_manys = klass.reflect_on_all_associations(:has_many)
has_ones = klass.reflect_on_all_associations(:has_one)
belong_tos = klass.reflect_on_all_associations(:belongs_to)
habtms = klass.reflect_on_all_associations(:has_and_belongs_to_many)
collection_associations = [has_manys, habtms].flatten
instance_associations = [has_ones, belong_tos].flatten
(collection_associations + instance_associations).each do |association|
model.errors.delete(association.name)
end
collection_associations.each do |association|
model.send(association.name).each do |child|
next if child.valid?
errors = child.errors.full_messages
model.errors[:base] << "#{association.class_name} Invalid: #{errors.to_sentence}"
end
end
instance_associations.each do |association|
next unless child = model.send(association.name)
next if child.valid?
errors = child.errors.full_messages
model.errors[:base] << "#{association.class_name} Invalid: #{errors.to_sentence}"
end
model.errors
end
I have the same issue. no good answer so far. So I solved it by myself. by replacing association error message with detail error message:
create a concern file models/concerns/association_error_detail_concern.rb
:
module AssociationErrorDetailConcern
extend ActiveSupport::Concern
included do
after_validation :replace_association_error_message
end
class_methods do
def association_names
@association_names ||= self.reflect_on_all_associations.map(&:name)
end
end
def replace_association_error_message
self.class.association_names.each do |attr|
next unless errors[attr]
errors.delete(attr)
Array.wrap(public_send(attr)).each do |record|
record.errors.full_messages.each do |message|
errors.add(attr, message)
end
end
end
end
end
in your model:
class School < ApplicationRecord
include AssociationErrorDetailConcern
has_many :students
...
end
then you will get you must supply a valid email
error message on students
attribute of school
record. instead of useless message is invalid
I see a problem in the posted code. add_student
is a class method of class School
, so self
will point to the class object School
instead of an instance object of class School
. The line students << s
will not add the record s
to the record school
because of this.
I don't know if this is causing your error message problem, but I think this will keep the code from working properly.
Update Rails 5.0.1
You can use Active Record Autosave Association
class School < ActiveRecord::Base
has_many :students, autosave: true
validates_associated :students
end
class Student < ActiveRecord::Base
belongs_to :school
validates_format_of :email,
:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
:message => "You must supply a valid email"
end
@school = School.new
@school.build_student(email: 'xyz')
@school.save
@school.errors.full_messages ==> ['You must supply a valid email']
reference: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html