From b677adede027eefed41f6b37096df7bbc4eb3d38 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Tue, 9 Jul 2019 14:40:57 +0200 Subject: Move the `ActiveModel:Errors#full_message` method to the `Error` class: - One regression introduced by the "AM errors as object" features is about the `full_messages` method. It's currently impossible to call that method if the `base` object passed in the constructor of `AM::Errors` doesn't respond to the `errors` method. That's because `full_messages` now makes a weird back and forth trip `AM::Errors#full_messages` -> `AM::Error#full_message` -> `AM::Errors#full_message` Since `full_message` (singular) isn't needed by AM::Errors, I moved it to the `AM::Error` (singular) class. This way we don't need to grab the `AM::Errors` object from the base. --- activemodel/lib/active_model.rb | 1 + activemodel/lib/active_model/error.rb | 90 ++++++++++++++++++++++++++++++++- activemodel/lib/active_model/errors.rb | 82 +----------------------------- activemodel/lib/active_model/railtie.rb | 2 +- 4 files changed, 92 insertions(+), 83 deletions(-) (limited to 'activemodel/lib') diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index c9140dc582..756473e38d 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -53,6 +53,7 @@ module ActiveModel eager_autoload do autoload :Errors + autoload :Error autoload :RangeError, "active_model/errors" autoload :StrictValidationFailed, "active_model/errors" autoload :UnknownAttributeError, "active_model/errors" diff --git a/activemodel/lib/active_model/error.rb b/activemodel/lib/active_model/error.rb index ea141fe107..aef28b5af0 100644 --- a/activemodel/lib/active_model/error.rb +++ b/activemodel/lib/active_model/error.rb @@ -8,6 +8,92 @@ module ActiveModel CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict] MESSAGE_OPTIONS = [:message] + class << self + attr_accessor :i18n_customize_full_message # :nodoc: + end + self.i18n_customize_full_message = false + + def self.full_message(attribute, message, base_class) # :nodoc: + return message if attribute == :base + attribute = attribute.to_s + + if i18n_customize_full_message && base_class.respond_to?(:i18n_scope) + attribute = attribute.remove(/\[\d\]/) + parts = attribute.split(".") + attribute_name = parts.pop + namespace = parts.join("/") unless parts.empty? + attributes_scope = "#{base_class.i18n_scope}.errors.models" + + if namespace + defaults = base_class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format", + ] + end + else + defaults = base_class.lookup_ancestors.map do |klass| + [ + :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format", + :"#{attributes_scope}.#{klass.model_name.i18n_key}.format", + ] + end + end + + defaults.flatten! + else + defaults = [] + end + + defaults << :"errors.format" + defaults << "%{attribute} %{message}" + + attr_name = attribute.tr(".", "_").humanize + attr_name = base_class.human_attribute_name(attribute, default: attr_name) + + I18n.t(defaults.shift, + default: defaults, + attribute: attr_name, + message: message) + end + + def self.generate_message(attribute, type, base, options) # :nodoc: + type = options.delete(:message) if options[:message].is_a?(Symbol) + value = (attribute != :base ? base.send(:read_attribute_for_validation, attribute) : nil) + + options = { + model: base.model_name.human, + attribute: base.class.human_attribute_name(attribute), + value: value, + object: base + }.merge!(options) + + if base.class.respond_to?(:i18n_scope) + i18n_scope = base.class.i18n_scope.to_s + defaults = base.class.lookup_ancestors.flat_map do |klass| + [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", + :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] + end + defaults << :"#{i18n_scope}.errors.messages.#{type}" + + catch(:exception) do + translation = I18n.translate(defaults.first, options.merge(default: defaults.drop(1), throw: true)) + return translation unless translation.nil? + end unless options[:message] + else + defaults = [] + end + + defaults << :"errors.attributes.#{attribute}.#{type}" + defaults << :"errors.messages.#{type}" + + key = defaults.shift + defaults = options.delete(:message) if options[:message] + options[:default] = defaults + + I18n.translate(key, options) + end + def initialize(base, attribute, type = :invalid, **options) @base = base @attribute = attribute @@ -28,7 +114,7 @@ module ActiveModel def message case raw_type when Symbol - base.errors.generate_message(attribute, raw_type, options.except(*CALLBACKS_OPTIONS)) + self.class.generate_message(attribute, raw_type, @base, options.except(*CALLBACKS_OPTIONS)) else raw_type end @@ -39,7 +125,7 @@ module ActiveModel end def full_message - base.errors.full_message(attribute, message) + self.class.full_message(attribute, message, @base.class) end # See if error matches provided +attribute+, +type+ and +options+. diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index e9f92dba82..22839bd9fb 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -69,11 +69,6 @@ module ActiveModel LEGACY_ATTRIBUTES = [:messages, :details].freeze - class << self - attr_accessor :i18n_customize_full_message # :nodoc: - end - self.i18n_customize_full_message = false - attr_reader :errors alias :objects :errors @@ -467,47 +462,7 @@ module ActiveModel # # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" def full_message(attribute, message) - return message if attribute == :base - attribute = attribute.to_s - - if self.class.i18n_customize_full_message && @base.class.respond_to?(:i18n_scope) - attribute = attribute.remove(/\[\d\]/) - parts = attribute.split(".") - attribute_name = parts.pop - namespace = parts.join("/") unless parts.empty? - attributes_scope = "#{@base.class.i18n_scope}.errors.models" - - if namespace - defaults = @base.class.lookup_ancestors.map do |klass| - [ - :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.attributes.#{attribute_name}.format", - :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.format", - ] - end - else - defaults = @base.class.lookup_ancestors.map do |klass| - [ - :"#{attributes_scope}.#{klass.model_name.i18n_key}.attributes.#{attribute_name}.format", - :"#{attributes_scope}.#{klass.model_name.i18n_key}.format", - ] - end - end - - defaults.flatten! - else - defaults = [] - end - - defaults << :"errors.format" - defaults << "%{attribute} %{message}" - - attr_name = attribute.tr(".", "_").humanize - attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - - I18n.t(defaults.shift, - default: defaults, - attribute: attr_name, - message: message) + Error.full_message(attribute, message, @base.class) end # Translates an error message in its default scope @@ -535,40 +490,7 @@ module ActiveModel # * errors.attributes.title.blank # * errors.messages.blank def generate_message(attribute, type = :invalid, options = {}) - type = options.delete(:message) if options[:message].is_a?(Symbol) - value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil) - - options = { - model: @base.model_name.human, - attribute: @base.class.human_attribute_name(attribute), - value: value, - object: @base - }.merge!(options) - - if @base.class.respond_to?(:i18n_scope) - i18n_scope = @base.class.i18n_scope.to_s - defaults = @base.class.lookup_ancestors.flat_map do |klass| - [ :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}", - :"#{i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ] - end - defaults << :"#{i18n_scope}.errors.messages.#{type}" - - catch(:exception) do - translation = I18n.translate(defaults.first, options.merge(default: defaults.drop(1), throw: true)) - return translation unless translation.nil? - end unless options[:message] - else - defaults = [] - end - - defaults << :"errors.attributes.#{attribute}.#{type}" - defaults << :"errors.messages.#{type}" - - key = defaults.shift - defaults = options.delete(:message) if options[:message] - options[:default] = defaults - - I18n.translate(key, options) + Error.generate_message(attribute, type, @base, options) end def marshal_load(array) # :nodoc: diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb index eb7901c7e9..65e20b9791 100644 --- a/activemodel/lib/active_model/railtie.rb +++ b/activemodel/lib/active_model/railtie.rb @@ -14,7 +14,7 @@ module ActiveModel end initializer "active_model.i18n_customize_full_message" do - ActiveModel::Errors.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false + ActiveModel::Error.i18n_customize_full_message = config.active_model.delete(:i18n_customize_full_message) || false end end end -- cgit v1.2.3