ruby-on-rails - new - ruby on rails api rest
Ruby on Rails: ¿cómo se muestran los mensajes de error de un recurso infantil? (8)
Estoy teniendo dificultades para entender cómo hacer que Rails muestre un mensaje de error explícito para un recurso hijo que está fallando la validación cuando presento una plantilla XML. Hipotéticamente, tengo las siguientes clases:
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
Ahora, en el controlador, digamos que queremos construir una API trivial que nos permita agregar una nueva Escuela con un estudiante (de nuevo, dije, es un ejemplo terrible, pero juega su papel para el propósito de la pregunta)
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
Ahora la validación está funcionando bien, las cosas se mueren porque el correo electrónico no coincide con la expresión regular establecida en el método validates_format_of en la clase Estudiante. Sin embargo, el resultado que obtengo es el siguiente:
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<error>Students is invalid</error>
</errors>
Quiero que aparezca el mensaje de error más significativo que configuré arriba con validates_format_of. Es decir, quiero que diga:
<error>You must supply a valid email</error>
¿Qué estoy haciendo mal para que no aparezca?
Actualizar Rails 5.0.1
Puede usar 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'']
referencia: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
Agregue un bloque de validación en el modelo de la School
para combinar los errores:
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
contendrá los errores correctos:
format.xml { render :xml => @school.errors, :status => :unprocessable_entity }
Nota:
No necesita un método diferente para agregar un nuevo alumno a la escuela, use la siguiente sintaxis:
school.students.build(:email => email)
Actualización para Rails 3.0+
errors.add_to_base
ha sido eliminado de Rails 3.0 y superior y debe ser reemplazado por:
errors[:base] << "Student Error: #{msg}"
Aquí hay un ejemplo que podría soportar algunos 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
Deberías usar following en el rhtml.
<%= error_messages_for :school, :student %>
Para omitir el mensaje " Estudiantes no es válido ", utilice el siguiente mensaje en student.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
EDITADO
Sorry after_validation must be in a school.rb
Esta aún no es una API pública, pero Rails 5 stable parece tener ActiveModel::Errors#copy!
para combinar errors
entre dos modelos.
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" ]
Una vez más, esto aún no se ha publicado oficialmente (accidentalmente lo encuentro antes de la clase de Errors
reparación de monos), y no estoy seguro de que así sea.
Eso depende de ti.
No estoy seguro de si esta es la mejor (o la correcta) respuesta ... todavía estoy aprendiendo, pero encontré que esto funciona bastante bien. No lo he probado exhaustivamente, pero parece funcionar con 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
Tengo el mismo problema. no hay una buena respuesta hasta el momento. Así que lo resolví solo. reemplazando el mensaje de error de asociación con el mensaje de error de detalle:
crear un archivo de preocupación 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
en tu modelo:
class School < ApplicationRecord
include AssociationErrorDetailConcern
has_many :students
...
end
luego obtendrá you must supply a valid email
error de you must supply a valid email
sobre el atributo de los students
del registro school
. en lugar de mensaje inútil is invalid
Veo un problema en el código publicado. add_student
es un método de clase de la clase School
, de modo que self
apuntará al objeto de clase School
lugar de a un objeto instancia de la clase School
. Los students << s
línea students << s
no agregarán los registros a la school
discográfica debido a esto.
No sé si esto está causando el mensaje de error, pero creo que esto evitará que el código funcione correctamente.