eprothro
2/29/2016 - 3:32 PM

Active Model Errors With Types Extensions.md

module ErrorsWithTypesExtensions
  attr_reader :types

  # Pass in the instance of the object that is using the errors object.
  #
  #   class Person
  #     def initialize
  #       @errors = ActiveModel::Errors.new(self)
  #     end
  #   end
  def initialize(base)
    @types = {}
    super
  end

  def initialize_dup(other) # :nodoc:
    @types = other.types.dup
    super
  end

  # Clear the error messages.
  #
  #   person.errors.full_messages # => ["name cannot be nil"]
  #   person.errors.clear
  #   person.errors.full_messages # => []
  def clear
    types.clear
    super
  end

  def set_type(attribute, message, type)
    messsage = full_message(attribute, message)
    types[attribute] ||= {}
    types[attribute][message] = type
  end

  # Returns the type of message for a given attribute's message if
  # that message was set via a type symbol
  #
  #   person.errors.type(:name, 'is invalid') # => :invalid
  def type_for(attribute, message)
    types[attribute].try(:[], message)
  end

  def messages_data
    return nil if empty?
    keys.inject([]) do | hashes, attribute |
      messages[attribute].each do | message |
        hashes << {
          attribute: attribute,
          full_message: full_message(attribute, message),
          type: type_for(attribute, message)
        }.with_indifferent_access
      end
      hashes
    end
  end

  # Translates an error message in its default scope
  # (<tt>activemodel.errors.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 also, it returns the translation of the default message
  # (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
  # name, translated attribute name and the value are available for
  # interpolation.
  #
  # When using inheritance 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:
  #
  # * <tt>activemodel.errors.models.admin.attributes.title.blank</tt>
  # * <tt>activemodel.errors.models.admin.blank</tt>
  # * <tt>activemodel.errors.models.user.attributes.title.blank</tt>
  # * <tt>activemodel.errors.models.user.blank</tt>
  # * any default you provided through the +options+ hash (in the <tt>activemodel.errors</tt> scope)
  # * <tt>activemodel.errors.messages.blank</tt>
  # * <tt>errors.attributes.title.blank</tt>
  # * <tt>errors.messages.blank</tt>
  def generate_message(attribute, type = :invalid, options = {})
    # generate_message is called any time an error message
    # is added to an attribute. This seemed a cleaner place
    # than `add` to monkey patch.

    type = options[:message] if options[:message].is_a?(Symbol)
    generated_message = super

    set_type(attribute, generated_message, type)

    generated_message
  end
end

module ActiveModel
  class Errors
    prepend ErrorsWithTypesExtensions
    # mix in our types extensions
    # 'below' the base (ActiveModel) class definition
    # so we _override_ the extensions instead of
    # _overwriting_ them (e.g. so we can use `super`)
    #
    # http://stackoverflow.com/questions/4470108/when-monkey-patching-a-method-can-you-call-the-overridden-method-from-the-new-i#answer-4471202
  end
end