Rails Rails 中临时把一个 model 的错误消息删除该怎么做?

ibachue · 2012年11月09日 · 最后由 cxh116 回复于 2012年11月10日 · 4079 次阅读

Hi all, 遇到个问题求助下,想把一个 ActiveRecord validate 后的错误信息删除掉一条,该如何实现?Errors 里面好像没有找到类似的方法? 谢谢!

your_model.errors.delete(key) http://apidock.com/rails/v3.2.8/ActiveModel/Errors/delete

key 一般是 field name,可以把 errors 打印看看

#1 楼 @cxh116 你的 Rails 版本太新了,我这里是 2.3 的。API 差不多就是这样:http://ar.rubyonrails.org/classes/ActiveRecord/Errors.html 没有 delete 方法。

class Errors
  include Enumerable
  attr_reader :errors

  delegate :empty?, :to => :errors

  def initialize(base) # :nodoc:
    @base, @errors = base, {}
  end
  ....
 end

从 2.3 的源码可以看出,your_model.errors.errors 将会返回一个 Hash,然后用 delete 方法删除应该就可以了。理论上是这样的,你要可以试试 your_model.errors.errors.delete(key)

实在不行,加入自己定义的一个集合,然后把需要的加入,view 绑定自己的集合,不要绑定 model.errors 了。

#3 楼 @cxh116 版本似乎还是不一样嘛 我代码是这样的

# Active Record validation is reported to and from this object, which is used by Base#save to
# determine whether the object is in a valid state to be saved. See usage example in Validations.
class Errors
  include Enumerable

  class << self
    def default_error_messages
      ActiveSupport::Deprecation.warn("ActiveRecord::Errors.default_error_messages has been deprecated. Please use I18n.translate('activerecord.errors.messages').")
      I18n.translate 'activerecord.errors.messages'
    end
  end

  def initialize(base) # :nodoc:
    @base, @errors = base, {}
  end

  # Adds an error to the base object instead of any particular attribute. This is used
  # to report errors that don't tie to any specific attribute, but rather to the object
  # as a whole. These error messages don't get prepended with any field name when iterating
  # with +each_full+, so they should be complete sentences.
  def add_to_base(msg)
    add(:base, msg)
  end

  # Adds an error message (+messsage+) to the +attribute+, which will be returned on a call to <tt>on(attribute)</tt>
  # for the same attribute and ensure that this error object returns false when asked if <tt>empty?</tt>. More than one
  # error can be added to the same +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
  # If no +messsage+ is supplied, :invalid is assumed.
  # If +message+ is a Symbol, it will be translated, using the appropriate scope (see translate_error).
  def add(attribute, message = nil, options = {})
    message ||= :invalid
    message = generate_message(attribute, message, options) if message.is_a?(Symbol)
    @errors[attribute.to_s] ||= []
    @errors[attribute.to_s] << message
  end

  # Will add an error message to each of the attributes in +attributes+ that is empty.
  def add_on_empty(attributes, custom_message = nil)
    for attr in [attributes].flatten
      value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
      is_empty = value.respond_to?(:empty?) ? value.empty? : false
      add(attr, :empty, :default => custom_message) unless !value.nil? && !is_empty
    end
  end

  # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
  def add_on_blank(attributes, custom_message = nil)
    for attr in [attributes].flatten
      value = @base.respond_to?(attr.to_s) ? @base.send(attr.to_s) : @base[attr.to_s]
      add(attr, :blank, :default => custom_message) if value.blank?
    end
  end

  # Translates an error message in it's default scope (<tt>activerecord.errrors.messages</tt>).
  # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>, if it's not there, 
  # it's looked up in <tt>models.MODEL.MESSAGE</tt> and if that is not there it returns the translation of the 
  # default message (e.g. <tt>activerecord.errors.messages.MESSAGE</tt>). The translated model name, 
  # translated attribute name and the value are available for interpolation.
  #
  # When using inheritence in your models, it will check all the inherited models too, but only if the model itself
  # hasn't been found. Say you have <tt>class Admin < User; end</tt> and you wanted the translation for the <tt>:blank</tt>
  # error +message+ for the <tt>title</tt> +attribute+, it looks for these translations:
  # 
  # <ol>
  # <li><tt>activerecord.errors.models.admin.attributes.title.blank</tt></li>
  # <li><tt>activerecord.errors.models.admin.blank</tt></li>
  # <li><tt>activerecord.errors.models.user.attributes.title.blank</tt></li>
  # <li><tt>activerecord.errors.models.user.blank</tt></li>
  # <li><tt>activerecord.errors.messages.blank</tt></li>
  # <li>any default you provided through the +options+ hash (in the activerecord.errors scope)</li>
  # </ol>
  def generate_message(attribute, message = :invalid, options = {})

    message, options[:default] = options[:default], message if options[:default].is_a?(Symbol)

    defaults = @base.class.self_and_descendants_from_active_record.map do |klass|
      [ :"models.#{klass.name.underscore}.attributes.#{attribute}.#{message}", 
        :"models.#{klass.name.underscore}.#{message}" ]
    end

    defaults << options.delete(:default)
    defaults = defaults.compact.flatten << :"messages.#{message}"

    key = defaults.shift
    value = @base.respond_to?(attribute) ? @base.send(attribute) : nil

    options = { :default => defaults,
      :model => @base.class.human_name,
      :attribute => @base.class.human_attribute_name(attribute.to_s),
      :value => value,
      :scope => [:activerecord, :errors]
    }.merge(options)

    I18n.translate(key, options)
  end

  # Returns true if the specified +attribute+ has errors associated with it.
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.invalid?(:name)      # => true
  #   company.errors.invalid?(:address)   # => false
  def invalid?(attribute)
    !@errors[attribute.to_s].nil?
  end

  # Returns +nil+, if no errors are associated with the specified +attribute+.
  # Returns the error message, if one error is associated with the specified +attribute+.
  # Returns an array of error messages, if more than one error is associated with the specified +attribute+.
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.on(:name)      # => ["is too short (minimum is 5 characters)", "can't be blank"]
  #   company.errors.on(:email)     # => "can't be blank"
  #   company.errors.on(:address)   # => nil
  def on(attribute)
    errors = @errors[attribute.to_s]
    return nil if errors.nil?
    errors.size == 1 ? errors.first : errors
  end

  alias :[] :on

  # Returns errors assigned to the base object through +add_to_base+ according to the normal rules of <tt>on(attribute)</tt>.
  def on_base
    on(:base)
  end

  # Yields each attribute and associated message per error added.
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.each{|attr,msg| puts "#{attr} - #{msg}" }
  #   # => name - is too short (minimum is 5 characters)
  #   #    name - can't be blank
  #   #    address - can't be blank
  def each
    @errors.each_key { |attr| @errors[attr].each { |msg| yield attr, msg } }
  end

  # Yields each full error message added. So <tt>Person.errors.add("first_name", "can't be empty")</tt> will be returned
  # through iteration as "First name can't be empty".
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.each_full{|msg| puts msg }
  #   # => Name is too short (minimum is 5 characters)
  #   #    Name can't be blank
  #   #    Address can't be blank
  def each_full
    full_messages.each { |msg| yield msg }
  end

  # Returns all the full error messages in an array.
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.full_messages # =>
  #     ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Address can't be blank"]
  def full_messages(options = {})
    full_messages = []

    @errors.each_key do |attr|
      @errors[attr].each do |message|
        next unless message

        if attr == "base"
          full_messages << message
        else
          attr_name = @base.class.human_attribute_name(attr)
          full_messages << attr_name + I18n.t('activerecord.errors.format.separator', :default => ' ') + message
        end
      end
    end
    full_messages
  end 

  # Returns true if no errors have been added.
  def empty?
    @errors.empty?
  end

  # Removes all errors that have been added.
  def clear
    @errors = {}
  end

  # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
  def size
    @errors.values.inject(0) { |error_count, attribute| error_count + attribute.size }
  end

  alias_method :count, :size
  alias_method :length, :size

  # Returns an XML representation of this error object.
  #
  #   class Company < ActiveRecord::Base
  #     validates_presence_of :name, :address, :email
  #     validates_length_of :name, :in => 5..30
  #   end
  #
  #   company = Company.create(:address => '123 First St.')
  #   company.errors.to_xml
  #   # =>  <?xml version="1.0" encoding="UTF-8"?>
  #   #     <errors>
  #   #       <error>Name is too short (minimum is 5 characters)</error>
  #   #       <error>Name can't be blank</error>
  #   #       <error>Address can't be blank</error>
  #   #     </errors>
  def to_xml(options={})
    options[:root] ||= "errors"
    options[:indent] ||= 2
    options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])

    options[:builder].instruct! unless options.delete(:skip_instruct)
    options[:builder].errors do |e|
      full_messages.each { |msg| e.error(msg) }
    end
  end

end

现在只能用 class_eval 控制@errors变量了,感觉代码超级丑陋

#4 楼 @woaigithub 这样做 code review 肯定是不会通过的 老外不会买帐的 呵呵

api 不支持,哈哈

#5 楼 @iBachue 用 instance_variable_get 也行

需要 登录 后方可回复, 如果你还没有账号请 注册新账号