Je passe une période difficile à comprendre comment obtenir Rails Pour afficher un message d'erreur explicite pour une ressource enfant qui échoue à la validation lorsque je rends un modèle XML. Hypothétiquement, j'ai les classes suivantes:
class School < ActiveRecord::Base
has_many :students
validates_associated :students
def self.add_student(bad_email)
s = Student.new(bad_email)
students << s
end
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
Maintenant, dans le contrôleur, disons que nous voulons construire une API triviale pour nous permettre d'ajouter une nouvelle école avec un étudiant (à nouveau, j'ai dit que c'est un exemple terrible, mais joue son rôle dans le but de la question)
class SchoolsController < ApplicationController
def create
@school = School.new
@school.add_student(params[:bad_email])
respond_to do |format|
if @school.save
# some code
else
format.xml { render :xml => @school.errors, :status => :unprocessable_entity }
end
end
end
end
Maintenant, la validation fonctionne simplement bien, les choses meurent car l'email ne correspond pas à la regex définie dans la méthode validates_format_of dans la classe d'étudiants. Cependant, la sortie que je reçois est la suivante:
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<error>Students is invalid</error>
</errors>
Je veux le message d'erreur le plus significatif que j'ai défini ci-dessus avec Validates_Format_of pour apparaître. Signification, je veux qu'il dise:
<error>You must supply a valid email</error>
Qu'est-ce que je fais mal pour ça ne pas se présenter?
Ajouter un bloc de validation dans le modèle School
pour fusionner les erreurs:
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
Maintenant @school.errors
contiendra les erreurs correctes:
format.xml { render :xml => @school.errors, :status => :unprocessable_entity }
NOTE :
Vous n'avez pas besoin d'une méthode distincte pour ajouter un nouvel élève à l'école, utilisez la syntaxe suivante:
school.students.build(:email => email)
errors.add_to_base
a été supprimé de Rails 3.0 et plus et doit être remplacé par:
errors[:base] << "Student Error: #{msg}"
Mise à jour Rails 5.0.1
Vous pouvez utiliser 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']
référence: http://api.rubyonRails.org/classes/acterecord/autosavèssociation.html
Ce n'est pas encore une API publique, mais Rails 5 stable semble avoir ActiveModel::Errors#copy!
Pour fusionner errors
entre deux modèles.
user = User.new(name: "foo", email: nil)
other = User.new(name: nil, email:"[email protected]")
user.errors.copy!(other.errors)
user.full_messages #=> [ "name is blank", "email is blank" ]
Encore une fois, cela n'est pas encore officiellement publié (je trouve accidentellement celui-ci avant le correcteur de singe Errors
classe), et je ne suis pas sûr que ce sera.
C'est à vous de répondre.
Je ne sais pas si c'est la meilleure réponse (ou une bonne) réponse ... J'apprends toujours, mais j'ai trouvé cela pour travailler assez bien. Je ne l'ai pas testé beaucoup, mais il semble fonctionner avec des 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
Vous devez utiliser la suite dans le RHTML.
<%= error_messages_for :school, :student %>
Pour sauter "Les étudiants sont invalides" Message Utilisez-le suivant dans l'étudiant.rb
def after_validation
# Skip errors that won't be useful to the end user
filtered_errors = self.errors.reject{ |err| %w{ student}.include?(err.first) }
self.errors.clear
filtered_errors.each { |err| self.errors.add(*err) }
end
[~ # ~] édité [~ # ~ ~]
Sorry after_validation must be in a school.rb
Voici un exemple qui pourrait résister à un séchage:
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
J'ai le même problème. Pas de bonne réponse jusqu'à présent. Alors j'ai résolu moi-même par moi-même. En remplaçant le message d'erreur d'association avec le message d'erreur de détail:
créer un fichier de préoccupation 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
dans votre modèle:
class School < ApplicationRecord
include AssociationErrorDetailConcern
has_many :students
...
end
ensuite, vous obtiendrez you must supply a valid email
Message d'erreur sur students
attribut de school
enregistrez. au lieu d'un message inutile is invalid
Je vois un problème dans le code posté. add_student
est une méthode de classe de classe School
, alors self
_ _ indiquera à l'objet de la classe School
au lieu d'un objet d'instance de classe School
. La ligne students << s
n'ajoutera pas l'enregistrement s
à l'enregistrement school
à cause de cela.
Je ne sais pas si cela provoque un problème de message d'erreur, mais je pense que cela gardera le code de fonctionner correctement.