Navigation Menu

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create full_message & full_message_format validation option [Feature Request] #42708

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 14 additions & 9 deletions activemodel/lib/active_model/error.rb
Expand Up @@ -8,42 +8,42 @@ module ActiveModel
# Represents one single error
class Error
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
MESSAGE_OPTIONS = [:message]
MESSAGE_OPTIONS = [:message, :full_message, :full_message_format]

class_attribute :i18n_customize_full_message, default: false

def self.full_message(attribute, message, base) # :nodoc:
def self.full_message(attribute, message, base, full_message_format = nil) # :nodoc:
return message if attribute == :base

base_class = base.class
attribute = attribute.to_s

if i18n_customize_full_message && base_class.respond_to?(:i18n_scope)
defaults = if full_message_format
[:"errors._hardcoded_format", full_message_format]
elsif 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|
base_class.lookup_ancestors.flat_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|
base_class.lookup_ancestors.flat_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"
Expand Down Expand Up @@ -106,6 +106,11 @@ def initialize(base, attribute, type = :invalid, **options)
@raw_type = type
@type = type || :invalid
@options = options

if full_message = @options.delete(:full_message)
@options[:message] = full_message
@options[:full_message_format] ||= "%{message}"
end
end

def initialize_dup(other) # :nodoc:
Expand Down Expand Up @@ -156,7 +161,7 @@ def details
# error.full_message
# # => "Name is too short (minimum is 5 characters)"
def full_message
self.class.full_message(attribute, message, @base)
self.class.full_message(attribute, message, @base, @options[:full_message_format])
end

# See if error matches provided +attribute+, +type+ and +options+.
Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/errors.rb
Expand Up @@ -518,8 +518,8 @@ def messages_for(attribute)
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
def full_message(attribute, message)
Error.full_message(attribute, message, @base)
def full_message(attribute, message, full_message_format: false)
Error.full_message(attribute, message, @base, full_message_format)
end

# Translates an error message in its default scope
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/absence.rb
Expand Up @@ -21,6 +21,8 @@ module HelperMethods
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be blank").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/acceptance.rb
Expand Up @@ -95,6 +95,8 @@ module HelperMethods
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
# * <tt>:accept</tt> - Specifies a value that is considered accepted.
# Also accepts an array of possible values. The default value is
# an array ["1", true], which makes it easy to relate to an HTML
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/confirmation.rb
Expand Up @@ -66,6 +66,8 @@ module HelperMethods
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
# <tt>%{translated_attribute_name}</tt>").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
# * <tt>:case_sensitive</tt> - Looks for an exact match. Ignored by
# non-text columns (+true+ by default).
#
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/exclusion.rb
Expand Up @@ -37,6 +37,8 @@ module HelperMethods
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/format.rb
Expand Up @@ -91,6 +91,8 @@ module HelperMethods
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
# * <tt>:with</tt> - Regular expression that if the attribute matches will
# result in a successful validation. This can be provided as a proc or
# lambda returning regular expression which will be called at runtime.
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/inclusion.rb
Expand Up @@ -35,6 +35,8 @@ module HelperMethods
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# not included in the list").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/length.rb
Expand Up @@ -115,6 +115,8 @@ module HelperMethods
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+ and +:strict+.
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/numericality.rb
Expand Up @@ -160,6 +160,8 @@ module HelperMethods
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
# * <tt>:only_integer</tt> - Specifies whether the value has to be an
# integer (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
Expand Down
2 changes: 2 additions & 0 deletions activemodel/lib/active_model/validations/presence.rb
Expand Up @@ -27,6 +27,8 @@ module HelperMethods
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
# * <tt>:full_message_format</tt> - Format of full_message (default is: "%{attribute} %{message}").
# * <tt>:full_message</tt> - A custom error message with "%{message}" as a full_message_format
#
# There is also a list of default options supported by every validator:
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
Expand Down
4 changes: 2 additions & 2 deletions activemodel/lib/active_model/validations/validates.rb
Expand Up @@ -99,8 +99,8 @@ module ClassMethods
# validates :token, length: 24, strict: TokenLengthException
#
#
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
# and +:message+ can be given to one specific validator, as a hash:
# Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+,
# +:message+, +:full_message_format+ and +:full_message+ can be given to one specific validator, as a hash:
#
# validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
def validates(*attributes)
Expand Down
33 changes: 32 additions & 1 deletion activemodel/test/cases/error_test.rb
Expand Up @@ -176,6 +176,35 @@ def test_initialize
assert_equal "press the button", error.full_message
end

test "full_message returns the given message when passed full_message option" do
error = ActiveModel::Error.new(Person.new, :name, full_message: "press the button")
assert_equal "press the button", error.full_message
end

test "full_message returns the given message when passed full_message_format option" do
error = ActiveModel::Error.new(Person.new, :name, message: "press the button", full_message_format: "%{message}")
assert_equal "press the button", error.full_message

error = ActiveModel::Error.new(Person.new, :name, message: "should be valid", full_message_format: "%{attribute} %{message}")
assert_equal "name should be valid", error.full_message

error = ActiveModel::Error.new(Person.new, :name, :blank, full_message_format: "%{attribute} %{message}")
assert_equal "name can't be blank", error.full_message

error = ActiveModel::Error.new(Person.new, :name, :blank, full_message_format: "%{message}")
assert_equal "can't be blank", error.full_message

error = ActiveModel::Error.new(Person.new, :name, :blank, full_message_format: "something hardcoded")
assert_equal "something hardcoded", error.full_message

# Use a locale without errors.format
error = ActiveModel::Error.new(Person.new, :name, full_message: "can't be blank")
I18n.with_locale(:unknown) { assert_equal "can't be blank", error.full_message }

error = ActiveModel::Error.new(Person.new, :name, message: "can't be blank", full_message_format: "%{message}")
I18n.with_locale(:unknown) { assert_equal "can't be blank", error.full_message }
end

test "full_message returns the given message with the attribute name included" do
error = ActiveModel::Error.new(Person.new, :name, :blank)
assert_equal "name can't be blank", error.full_message
Expand Down Expand Up @@ -232,7 +261,9 @@ def test_initialize
allow_nil: false,
allow_blank: false,
strict: true,
message: "message"
message: "message",
full_message_format: "%{message}",
full_message: "message",
)

assert_equal(
Expand Down
6 changes: 6 additions & 0 deletions activemodel/test/cases/errors_test.rb
Expand Up @@ -596,6 +596,12 @@ def call
assert_equal "press the button", person.errors.full_message(:base, "press the button")
end

test "full_message returns the given message when full_message_format is passed in" do
person = Person.new
assert_equal "cannot be blank", person.errors.full_message(:name, "cannot be blank", full_message_format: "%{message}")
assert_equal "cannot be blank", person.errors.full_message(:name_test, "cannot be blank", full_message_format: "%{message}")
end

test "full_message returns the given message with the attribute name included" do
person = Person.new
assert_equal "name cannot be blank", person.errors.full_message(:name, "cannot be blank")
Expand Down