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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2680016/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 22:34:07  来源:igfitidea点击:

Ruby on Rails: how to get error messages from a child resource displayed?

ruby-on-railsrubyactiverecordrest

提问by randombits

I'm having a difficult time understanding how to get Rails to show an explicit error message for a child resource that is failing validation when I render an XML template. Hypothetically, I have the following classes:

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

Now, in the controller, let's say we want to build a trivial API to allow us to add a new School with a student in it (again, I said, it's a terrible example, but plays its role for the purpose of the question)

现在,在控制器中,假设我们想要构建一个简单的 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

Now the validation is working just fine, things die because the email doesn't match the regex that's set in the validates_format_of method in the Student class. However the output I get is the following:

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

<?xml version="1.0" encoding="UTF-8"?>
<errors>
  <error>Students is invalid</error>
</errors>

I want the more meaningful error message that I set above with validates_format_of to show up. Meaning, I want it to say:

我希望显示我上面使用 validates_format_of 设置的更有意义的错误消息。意思是,我想说:

 <error>You must supply a valid email</error>

What am I doing wrong for that not to show up?

我做错了什么才没有出现?

回答by Harish Shetty

Add a validation block in the Schoolmodel to merge the errors:

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

Now @school.errorswill contain the correct errors:

现在@school.errors将包含正确的错误:

format.xml  { render :xml => @school.errors, :status => :unprocessable_entity }

Note:

笔记:

You don't need a separate method for adding a new student to school, use the following syntax:

您不需要单独的方法将新学生添加到学校,请使用以下语法:

school.students.build(:email => email)

Update for Rails 3.0+

Rails 3.0+ 的更新

errors.add_to_basehas been dropped from Rails 3.0 and above and should be replaced with:

errors.add_to_base已从 Rails 3.0 及更高版本中删除,应替换为:

errors[:base] << "Student Error: #{msg}"

回答by Quv

This is not a public API yet, but Rails 5 stable seems to have ActiveModel::Errors#copy!to merge errorsbetween two models.

这还不是公共 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" ] 

Again, this is not officially published yet (I accidentally find this one before monkey-patching Errorsclass), and I'm not sure it will be.

同样,这还没有正式发布(我在猴子补丁Errors课之前偶然发现了这个),我不确定它会是。

So it's up to you.

所以这取决于你。

回答by Ashish Agrawal

Update Rails 5.0.1

更新 Rails 5.0.1

You can use Active Record Autosave Association

您可以使用 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']

reference: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

参考:http: //api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

回答by Kubee

I'm not sure if this is the best (or a correct) answer...i'm still learning, but I found this to work pretty well. I haven't tested it extensively, but it does seem to work with rails4:

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

回答by Chris Cashwell

Here's an example that could stand some 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

回答by Salil

You should use following in the rhtml.

您应该在 rhtml 中使用以下内容。

<%= error_messages_for :school, :student %>

To skip "Students is invalid" message use following in the student.rb

要跳过“学生无效”消息,请在 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

回答by Fred

I see a problem in the posted code. add_studentis a class method of class School, so selfwill point to the class object Schoolinstead of an instance object of class School. The line students << swill not add the record sto the record schoolbecause of this.

我在发布的代码中看到了一个问题。 add_student是类的类方法School,所以self会指向类对象School而不是类的实例对象School。因此,该行students << s不会将记录添加s到记录school中。

I don't know if this is causing your error message problem, but I think this will keep the code from working properly.

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

回答by Yi Feng Xie

I have the same issue. no good answer so far. So I solved it by myself. by replacing association error message with detail error message:

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

create a concern file models/concerns/association_error_detail_concern.rb:

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

in your model:

在您的模型中:

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

then you will get you must supply a valid emailerror message on studentsattribute of schoolrecord. instead of useless message is invalid

那么您将收到有关记录属性的you must supply a valid email错误消息。而不是无用的消息studentsschoolis invalid