Ruby on Rails: как получить сообщения об ошибках из дочернего ресурса? - PullRequest
40 голосов
/ 21 апреля 2010

Мне трудно понять, как заставить 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>

Что я делаю не так, чтобы это не появилось?

Ответы [ 8 ]

73 голосов
/ 27 апреля 2010

Добавьте блок проверки в модель 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}"
8 голосов
/ 26 декабря 2016

Обновление Rails 5.0.1

Вы можете использовать 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']

ссылка: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

6 голосов
/ 18 ноября 2016

Это еще не публичный API, но в стабильной версии Rails 5, похоже, есть ActiveModel::Errors#copy! для объединения errors между двумя моделями.

user  = User.new(name: "foo", email: nil)
other = User.new(name: nil, email:"foo@bar.com")

user.errors.copy!(other.errors)
user.full_messages #=> [ "name is blank", "email is blank" ] 

Опять же, это официально еще не опубликовано (я случайно нашел его до обезьяны Errors class), и я не уверен, что это будет.

Так что решать вам.

1 голос
/ 27 мая 2013

Я не уверен, что это лучший (или правильный) ответ ... я все еще учусь, но я нашел, что это работает довольно хорошо. Я не тестировал его всесторонне, но, похоже, он работает с 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
0 голосов
/ 25 июля 2018

У меня та же проблема. пока нет хорошего ответа. Так что я решил это сам. заменив сообщение об ошибке ассоциации на подробное сообщение об ошибке:

создать файл концерна 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

в вашей модели:

class School < ApplicationRecord
  include AssociationErrorDetailConcern
  has_many :students
  ...
end

, тогда вы получите you must supply a valid email сообщение об ошибке атрибута students записи school. вместо бесполезного сообщения is invalid

0 голосов
/ 21 октября 2014

Вот пример, который может выдержать СУШКУ:

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
0 голосов
/ 21 апреля 2010

Я вижу проблему в размещенном коде. add_student является методом класса School, поэтому self будет указывать на объект класса School вместо объекта экземпляра класса School. Строка students << s не добавит запись s к записи school из-за этого.

Я не знаю, вызывает ли это проблему с сообщением об ошибке, но я думаю, что это помешает правильной работе кода.

0 голосов
/ 21 апреля 2010

Вы должны использовать следующее в 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...