Ruby on Rails: как отображать сообщения об ошибках из детского ресурса?
Мне сложно понять, как получить Rails, чтобы показать явное сообщение об ошибке для дочернего ресурса, который отказывает в проверке при визуализации XML-шаблона. Гипотетически, у меня есть следующие классы:
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
Теперь, в контроллере, скажем, мы хотим создать тривиальный API, чтобы мы могли добавить новую школу со студентом в нее (опять же, я сказал, это ужасный пример, но играет свою роль с целью вопрос)
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
Теперь проверка работает очень хорошо, все умирает, потому что письмо не соответствует регулярному выражению, установленному в методе validates_format_of в классе Student. Однако вывод, который я получаю, следующий:
<?xml version="1.0" encoding="UTF-8"?>
<errors>
<error>Students is invalid</error>
</errors>
Я хочу, чтобы появилось более содержательное сообщение об ошибке, которое я установил выше, с помощью validates_format_of. Смысл, я хочу сказать:
<error>You must supply a valid email</error>
Что я делаю неправильно, чтобы это не появилось?
Ответы
Ответ 1
Добавить блок проверки в модели School
для объединения ошибок:
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
Теперь @school.errors
будет содержать правильные ошибки:
format.xml { render :xml => @school.errors, :status => :unprocessable_entity }
Примечание:
Вам не нужен отдельный метод для добавления нового ученика в школу, используйте следующий синтаксис:
school.students.build(:email => email)
Обновление для Rails 3.0 +
errors.add_to_base
был удален из Rails 3.0 и выше и должен быть заменен на:
errors[:base] << "Student Error: #{msg}"
Ответ 2
Обновить Rails 5.0.1
Вы можете использовать Ассоциацию автосохранения Active Record
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']
ссылка: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
Ответ 3
Это еще не открытый API, но у Rails 5 stable есть ActiveModel::Errors#copy!
, чтобы объединить errors
между двумя моделями.
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" ]
Опять же, это еще не официально опубликовано (я случайно обнаружил это раньше, чем класс обезьяны-patching errors
), и я не уверен, что это будет.
Так это вам.
Ответ 4
Вы должны использовать следующее в rhtml.
<%= error_messages_for :school, :student %>
Чтобы пропустить " Неверное использование учащихся], используйте следующую команду в 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
EDITED
Sorry after_validation must be in a school.rb
Ответ 5
Я вижу проблему в размещенном коде. add_student
- метод класса класса School
, поэтому self
будет указывать на объект класса School
вместо объекта экземпляра класса School
. Строка students << s
не добавит запись s
к записи School
из-за этого.
Я не знаю, вызвала ли эта проблема сообщение об ошибке, но я думаю, что это приведет к правильной работе кода.
Ответ 6
Я не уверен, что это лучший (или правильный) ответ... Я все еще участвую, но я нашел, что это работает очень хорошо. Я не тестировал его широко, но он работает с 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
Ответ 7
Вот пример, который может выдержать некоторое сушение:
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