Rails validation error messages: Displaying only one error message per field

后端 未结 13 819
情话喂你
情话喂你 2020-12-07 17:08

Rails displays all validation error messages associated with a given field. If I have three validates_XXXXX_of :email, and I leave the field blank, I get three

相关标签:
13条回答
  • 2020-12-07 17:51

    I would display all the error messages on one line and in a sentence format. You don't want the user to fix one error and end up having another error he was not aware of after submission. Telling them all the rules will save them clicks. With that said, this is how I'd do it:

    flash_message_now("error", 
       @album.errors.keys.map { |k| "#{Album.human_attribute_name(k)} #{@album.errors[k].to_sentence}"}.to_sentence
    )
    

    with flash_message_now defined in ApplicationController (you can add it to a helper)

    def flash_message_now(type, text)
        flash.now[type] ||= []
        flash.now[type] << text
      end
    
    0 讨论(0)
  • 2020-12-07 17:53

    [Update] Jan/2013 to Rails 3.2.x - update syntax; add spec

    Inspired by new validation methods in Rails 3.0 I'm adding this tiny Validator. I call it ReduceValidator.

    lib/reduce_validator.rb:

    # show only one error message per field
    #
    class ReduceValidator < ActiveModel::EachValidator
    
      def validate_each(record, attribute, value)
        return until record.errors.messages.has_key?(attribute)
        record.errors[attribute].slice!(-1) until record.errors[attribute].size <= 1
      end
    end
    

    My Model looking like - notice the :reduce => true:

    validates :title, :presence => true, :inclusion => { :in => %w[ Mr Mrs ] }, :reduce => true
    validates :firstname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true
    validates :lastname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^\D{1}[.]/i }, :reduce => true
    

    Works like a charm in my current Rails Project. The advantageous is, i've put the validator only on a few fields not all.

    spec/lib/reduce_validator_spec.rb:

    require 'spec_helper'
    
    describe ReduceValidator do
    
      let(:reduce_validator) { ReduceValidator.new({ :attributes => {} }) }
    
      let(:item) { mock_model("Item") }
      subject { item }
    
      before(:each) do
        item.errors.add(:name, "message one")
        item.errors.add(:name, "message two")
      end
    
      it { should have(2).error_on(:name) }
    
      it "should reduce error messages" do
        reduce_validator.validate_each(item, :name, '')
        should have(1).error_on(:name)
      end
    
    end
    
    0 讨论(0)
  • 2020-12-07 17:57

    How about this @event.errors[:title].first?

    0 讨论(0)
  • 2020-12-07 17:58

    Similar to olovwia's answer:

    <% @errors.keys.each do |attr| %>
     <%= "#{attr.capitalize} #{@errors[attr].first}."%>
    <% end %>"
    
    0 讨论(0)
  • 2020-12-07 17:58

    My monkey patch of ActiveModel::Errors class lib/core_ext/rails/active_model/errors.rb (I use this code for Ruby on Rails 5.0 release):

    module ActiveModel
      class Errors
    
        # don't add an attribute's error message to details
        # if it already contains at least one message
    
        alias_method :old_add, :add
    
        def add(attribute, message = :invalid, options = {})
          if details[attribute.to_sym].size.zero?
            old_add(attribute, message, options)
          end
        end
    
      end
    end
    

    Create a file config/initializers/core_ext.rb and add a require core_ext/rails/active_model/errors.rb to it.

    0 讨论(0)
  • 2020-12-07 18:02

    I use this code for Ruby on Rails 3.0 release, which I put in lib/core_ext/rails/active_model/errors.rb:

    module ActiveModel
      class Errors
        def full_message_per_field
          messages_per_field = []
          handled_attributes = []
    
          each do |attribute, messages|
            next if handled_attributes.include? attribute
            messages = Array.wrap(messages)
            next if messages.empty?
    
            if attribute == :base
              messages_per_field << messages.first
            else
              attr_name = attribute.to_s.gsub('.', '_').humanize
              attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
              options = { :default => "%{attribute} %{message}", :attribute => attr_name }
    
              messages_per_field << I18n.t(:"errors.format", options.merge(:message => messages.first))
            end
    
            handled_attributes << attribute
          end
    
          messages_per_field
        end
      end
    end
    

    This is essentially the same code as ActiveModel::Errors#full_messages, but won't show more than one error per attribute. Be sure to require the file (say, in an initializer) and now you can call @model.errors.full_message_per_field do |message| ...

    0 讨论(0)
提交回复
热议问题