Ruby on Rails:如何从显示的子资源中获取错误消息?

时间:2010-04-21 02:57:53

标签: ruby-on-rails ruby activerecord rest

我很难理解如何让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

现在验证工作正常,因为电子邮件与Student类中的validates_format_of方法中设置的正则表达不匹配而死亡。但是我获得的输出如下:

<?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 个答案:

答案 0 :(得分:73)

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}"

答案 1 :(得分:8)

更新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

答案 2 :(得分:6)

这还不是公共API,但Rails 5 stable似乎有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课之前偶然发现了这个),我不确定它会不会。

所以它取决于你。

答案 3 :(得分:0)

您应该在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

答案 4 :(得分:0)

我发现发布的代码存在问题。 add_student是类School的类方法,因此self将指向类对象School,而不是类School的实例对象。由于此原因,students << s行不会将记录s添加到记录school

我不知道这是否会导致您的错误消息问题,但我认为这会使代码无法正常工作。

答案 5 :(得分:0)

我不确定这是否是最好的(或正确的)答案......我还在学习,但我觉得这很有效。我没有对它进行过广泛的测试,但它似乎与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

答案 6 :(得分:0)

这是一个可以阻止一些干扰的例子:

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

答案 7 :(得分:0)

我有同样的问题。到目前为止没有好的答案。 所以我自己解决了。通过将关联错误消息替换为详细错误消息:

创建关注文件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