web-dev-qa-db-fra.com

Ruby sur rails: Comment obtenir des messages d'erreur d'une ressource enfant affichée?

Je passe une période difficile à comprendre comment obtenir Rails Pour afficher un message d'erreur explicite pour une ressource enfant qui échoue à la validation lorsque je rends un modèle XML. Hypothétiquement, j'ai les classes suivantes:

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

Maintenant, dans le contrôleur, disons que nous voulons construire une API triviale pour nous permettre d'ajouter une nouvelle école avec un étudiant (à nouveau, j'ai dit que c'est un exemple terrible, mais joue son rôle dans le but de la question)

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

Maintenant, la validation fonctionne simplement bien, les choses meurent car l'email ne correspond pas à la regex définie dans la méthode validates_format_of dans la classe d'étudiants. Cependant, la sortie que je reçois est la suivante:

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

Je veux le message d'erreur le plus significatif que j'ai défini ci-dessus avec Validates_Format_of pour apparaître. Signification, je veux qu'il dise:

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

Qu'est-ce que je fais mal pour ça ne pas se présenter?

43
randombits

Ajouter un bloc de validation dans le modèle School pour fusionner les erreurs:

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

Maintenant @school.errors contiendra les erreurs correctes:

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

NOTE :

Vous n'avez pas besoin d'une méthode distincte pour ajouter un nouvel élève à l'école, utilisez la syntaxe suivante:

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

Mise à jour pour Rails 3.0+

errors.add_to_base a été supprimé de Rails 3.0 et plus et doit être remplacé par:

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

Mise à jour Rails 5.0.1

Vous pouvez utiliser 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']

référence: http://api.rubyonRails.org/classes/acterecord/autosavèssociation.html

8
Ashish Agrawal

Ce n'est pas encore une API publique, mais Rails 5 stable semble avoir ActiveModel::Errors#copy! Pour fusionner errors entre deux modèles.

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

Encore une fois, cela n'est pas encore officiellement publié (je trouve accidentellement celui-ci avant le correcteur de singe Errors classe), et je ne suis pas sûr que ce sera.

C'est à vous de répondre.

7
Quv

Je ne sais pas si c'est la meilleure réponse (ou une bonne) réponse ... J'apprends toujours, mais j'ai trouvé cela pour travailler assez bien. Je ne l'ai pas testé beaucoup, mais il semble fonctionner avec des 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
1
Kubee

Vous devez utiliser la suite dans le RHTML.

<%= error_messages_for :school, :student %>

Pour sauter "Les étudiants sont invalides" Message Utilisez-le suivant dans l'étudiant.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

[~ # ~] édité [~ # ~ ~]

Sorry after_validation must be in a school.rb
0
Salil

Voici un exemple qui pourrait résister à un séchage:

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
Chris Cashwell

J'ai le même problème. Pas de bonne réponse jusqu'à présent. Alors j'ai résolu moi-même par moi-même. En remplaçant le message d'erreur d'association avec le message d'erreur de détail:

créer un fichier de préoccupation 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

dans votre modèle:

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

ensuite, vous obtiendrez you must supply a valid email Message d'erreur sur students attribut de school enregistrez. au lieu d'un message inutile is invalid

0
Yi Feng Xie

Je vois un problème dans le code posté. add_student est une méthode de classe de classe School, alors self _ _ indiquera à l'objet de la classe School au lieu d'un objet d'instance de classe School. La ligne students << s n'ajoutera pas l'enregistrement s à l'enregistrement school à cause de cela.

Je ne sais pas si cela provoque un problème de message d'erreur, mais je pense que cela gardera le code de fonctionner correctement.

0
Fred