aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG.md24
-rw-r--r--activemodel/lib/active_model/attributes.rb2
-rw-r--r--activemodel/lib/active_model/errors.rb48
-rw-r--r--activemodel/lib/active_model/railtie.rb6
-rw-r--r--activemodel/lib/active_model/secure_password.rb103
-rw-r--r--activemodel/lib/active_model/type/boolean.rb4
-rw-r--r--activemodel/lib/active_model/type/helpers/time_value.rb1
-rw-r--r--activemodel/lib/active_model/type/time.rb2
-rw-r--r--activemodel/lib/active_model/type/value.rb4
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb6
-rw-r--r--activemodel/test/cases/naming_test.rb2
-rw-r--r--activemodel/test/cases/railtie_test.rb20
-rw-r--r--activemodel/test/cases/secure_password_test.rb26
-rw-r--r--activemodel/test/cases/type/time_test.rb15
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb59
-rw-r--r--activemodel/test/cases/validations/validates_test.rb2
-rw-r--r--activemodel/test/models/user.rb3
-rw-r--r--activemodel/test/models/visitor.rb3
18 files changed, 255 insertions, 75 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 6b557a7cb1..1a464c2ffd 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,27 @@
+* Allows configurable attribute name for `#has_secure_password`. This
+ still defaults to an attribute named 'password', causing no breaking
+ change. There is a new method `#authenticate_XXX` where XXX is the
+ configured attribute name, making the existing `#authenticate` now an
+ alias for this when the attribute is the default 'password'.
+ Example:
+
+ class User < ActiveRecord::Base
+ has_secure_password :activation_token, validations: false
+ end
+
+ user = User.new()
+ user.activation_token = "a_new_token"
+ user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3Be..."
+ user.authenticate_activation_token('a_new_token') # => user
+
+ *Unathi Chonco*
+
+* Add `config.active_model.i18n_full_message` in order to control whether
+ the `full_message` error format can be overridden at the attribute or model
+ level in the locale files. This is `false` by default.
+
+ *Martin Larochelle*
+
* Rails 6 requires Ruby 2.4.1 or newer.
*Jeremy Daer*
diff --git a/activemodel/lib/active_model/attributes.rb b/activemodel/lib/active_model/attributes.rb
index 7d44f7f2a3..5bf213d593 100644
--- a/activemodel/lib/active_model/attributes.rb
+++ b/activemodel/lib/active_model/attributes.rb
@@ -103,7 +103,7 @@ module ActiveModel
def self.set_name_cache(name, value)
const_name = "ATTR_#{name}"
unless const_defined? const_name
- const_set const_name, value.dup.freeze
+ const_set const_name, -value
end
end
}
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 275e3f1313..56404a036c 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -62,6 +62,11 @@ module ActiveModel
CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
MESSAGE_OPTIONS = [:message]
+ class << self
+ attr_accessor :i18n_full_message # :nodoc:
+ end
+ self.i18n_full_message = false
+
attr_reader :messages, :details
# Pass in the instance of the object that is using the errors object.
@@ -364,12 +369,51 @@ module ActiveModel
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
+ #
+ # The `"%{attribute} %{message}"` error format can be overridden with either
+ #
+ # * <tt>activemodel.errors.models.person/contacts/addresses.attributes.street.format</tt>
+ # * <tt>activemodel.errors.models.person/contacts/addresses.format</tt>
+ # * <tt>activemodel.errors.models.person.attributes.name.format</tt>
+ # * <tt>activemodel.errors.models.person.format</tt>
+ # * <tt>errors.format</tt>
def full_message(attribute, message)
return message if attribute == :base
+
+ if self.class.i18n_full_message && @base.class.respond_to?(:i18n_scope)
+ parts = attribute.to_s.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.to_s.tr(".", "_").humanize
attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
- I18n.t(:"errors.format",
- default: "%{attribute} %{message}",
+ I18n.t(defaults.shift,
+ default: defaults,
attribute: attr_name,
message: message)
end
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
index a9cdabba00..0ed70bd473 100644
--- a/activemodel/lib/active_model/railtie.rb
+++ b/activemodel/lib/active_model/railtie.rb
@@ -7,8 +7,14 @@ module ActiveModel
class Railtie < Rails::Railtie # :nodoc:
config.eager_load_namespaces << ActiveModel
+ config.active_model = ActiveSupport::OrderedOptions.new
+
initializer "active_model.secure_password" do
ActiveModel::SecurePassword.min_cost = Rails.env.test?
end
+
+ initializer "active_model.i18n_full_message" do
+ ActiveModel::Errors.i18n_full_message = config.active_model.delete(:i18n_full_message) || false
+ end
end
end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 86f051f5ce..7f3763fa56 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -16,15 +16,16 @@ module ActiveModel
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
- # This mechanism requires you to have a +password_digest+ attribute.
+ # This mechanism requires you to have a +XXX_digest+ attribute.
+ # Where +XXX+ is the attribute name of your desired password/token or defaults to +password+
#
# The following validations are added automatically:
# * Password must be present on creation
# * Password length should be less than or equal to 72 bytes
- # * Confirmation of password (using a +password_confirmation+ attribute)
+ # * Confirmation of password (using a +XXX_confirmation+ attribute)
#
- # If password confirmation validation is not needed, simply leave out the
- # value for +password_confirmation+ (i.e. don't provide a form field for
+ # If confirmation validation is not needed, simply leave out the
+ # value for +XXX_confirmation+ (i.e. don't provide a form field for
# it). When this attribute has a +nil+ value, the validation will not be
# triggered.
#
@@ -37,9 +38,10 @@ module ActiveModel
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
- # # Schema: User(name:string, password_digest:string)
+ # # Schema: User(name:string, password_digest:string, activation_token_digest:string)
# class User < ActiveRecord::Base
# has_secure_password
+ # has_secure_password :activation_token, validations: false
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
@@ -48,11 +50,15 @@ module ActiveModel
# user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
# user.save # => true
+ # user.activation_token = "a_new_token"
+ # user.activation_token_digest # => "$2a$10$0Budk0Fi/k2CDm2PEwa3BeXO5tPOA85b6xazE9rp8nF2MIJlsUik."
+ # user.save # => true
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
+ # user.authenticate_activation_token('a_new_token') # => user
# User.find_by(name: 'david').try(:authenticate, 'notright') # => false
# User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
- def has_secure_password(options = {})
+ def has_secure_password(attribute = :password, validations: true)
# Load bcrypt gem only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
@@ -63,9 +69,40 @@ module ActiveModel
raise
end
- include InstanceMethodsOnActivation
+ attr_reader attribute
+
+ define_method("#{attribute}=") do |unencrypted_password|
+ if unencrypted_password.nil?
+ self.send("#{attribute}_digest=", nil)
+ elsif !unencrypted_password.empty?
+ instance_variable_set("@#{attribute}", unencrypted_password)
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
+ self.send("#{attribute}_digest=", BCrypt::Password.create(unencrypted_password, cost: cost))
+ end
+ end
+
+ define_method("#{attribute}_confirmation=") do |unencrypted_password|
+ instance_variable_set("@#{attribute}_confirmation", unencrypted_password)
+ end
+
+ # Returns +self+ if the password is correct, otherwise +false+.
+ #
+ # class User < ActiveRecord::Base
+ # has_secure_password validations: false
+ # end
+ #
+ # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
+ # user.save
+ # user.authenticate_password('notright') # => false
+ # user.authenticate_password('mUc3m00RsqyRe') # => user
+ define_method("authenticate_#{attribute}") do |unencrypted_password|
+ attribute_digest = send("#{attribute}_digest")
+ BCrypt::Password.new(attribute_digest).is_password?(unencrypted_password) && self
+ end
+
+ alias_method :authenticate, :authenticate_password if attribute == :password
- if options.fetch(:validations, true)
+ if validations
include ActiveModel::Validations
# This ensures the model has a password by checking whether the password_digest
@@ -73,57 +110,13 @@ module ActiveModel
# when there is an error, the message is added to the password attribute instead
# so that the error message will make sense to the end-user.
validate do |record|
- record.errors.add(:password, :blank) unless record.password_digest.present?
+ record.errors.add(attribute, :blank) unless record.send("#{attribute}_digest").present?
end
- validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
- validates_confirmation_of :password, allow_blank: true
+ validates_length_of attribute, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
+ validates_confirmation_of attribute, allow_blank: true
end
end
end
-
- module InstanceMethodsOnActivation
- # Returns +self+ if the password is correct, otherwise +false+.
- #
- # class User < ActiveRecord::Base
- # has_secure_password validations: false
- # end
- #
- # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
- # user.save
- # user.authenticate('notright') # => false
- # user.authenticate('mUc3m00RsqyRe') # => user
- def authenticate(unencrypted_password)
- BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
- end
-
- attr_reader :password
-
- # Encrypts the password into the +password_digest+ attribute, only if the
- # new password is not empty.
- #
- # class User < ActiveRecord::Base
- # has_secure_password validations: false
- # end
- #
- # user = User.new
- # user.password = nil
- # user.password_digest # => nil
- # user.password = 'mUc3m00RsqyRe'
- # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
- def password=(unencrypted_password)
- if unencrypted_password.nil?
- self.password_digest = nil
- elsif !unencrypted_password.empty?
- @password = unencrypted_password
- cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
- self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
- end
- end
-
- def password_confirmation=(unencrypted_password)
- @password_confirmation = unencrypted_password
- end
- end
end
end
diff --git a/activemodel/lib/active_model/type/boolean.rb b/activemodel/lib/active_model/type/boolean.rb
index bcdbab0343..f6c6efbc87 100644
--- a/activemodel/lib/active_model/type/boolean.rb
+++ b/activemodel/lib/active_model/type/boolean.rb
@@ -20,6 +20,10 @@ module ActiveModel
:boolean
end
+ def serialize(value) # :nodoc:
+ cast(value)
+ end
+
private
def cast_value(value)
diff --git a/activemodel/lib/active_model/type/helpers/time_value.rb b/activemodel/lib/active_model/type/helpers/time_value.rb
index 250c4021c6..cb6aa67a9d 100644
--- a/activemodel/lib/active_model/type/helpers/time_value.rb
+++ b/activemodel/lib/active_model/type/helpers/time_value.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require "active_support/core_ext/string/zones"
require "active_support/core_ext/time/zones"
module ActiveModel
diff --git a/activemodel/lib/active_model/type/time.rb b/activemodel/lib/active_model/type/time.rb
index c094ee0013..b3056b1333 100644
--- a/activemodel/lib/active_model/type/time.rb
+++ b/activemodel/lib/active_model/type/time.rb
@@ -18,6 +18,8 @@ module ActiveModel
case value
when ::String
value = "2000-01-01 #{value}"
+ time_hash = ::Date._parse(value)
+ return if time_hash[:hour].nil?
when ::Time
value = value.change(year: 2000, day: 1, month: 1)
end
diff --git a/activemodel/lib/active_model/type/value.rb b/activemodel/lib/active_model/type/value.rb
index a8ea6a2c22..b6914dd63c 100644
--- a/activemodel/lib/active_model/type/value.rb
+++ b/activemodel/lib/active_model/type/value.rb
@@ -90,6 +90,10 @@ module ActiveModel
false
end
+ def force_equality?(_value) # :nodoc:
+ false
+ end
+
def map(value) # :nodoc:
yield value
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 31750ba78e..0478915be7 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -19,9 +19,11 @@ module ActiveModel
end
def validate_each(record, attr_name, value)
- before_type_cast = :"#{attr_name}_before_type_cast"
+ came_from_user = :"#{attr_name}_came_from_user?"
- raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) && record.send(before_type_cast) != value
+ if record.respond_to?(came_from_user) && record.public_send(came_from_user)
+ raw_value = record.read_attribute_before_type_cast(attr_name)
+ end
raw_value ||= value
if record_attribute_changed_in_place?(record, attr_name)
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 009f1f47af..4693da434c 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -248,7 +248,7 @@ class NamingHelpersTest < ActiveModel::TestCase
def test_uncountable
assert uncountable?(@uncountable), "Expected 'sheep' to be uncountable"
- assert !uncountable?(@klass), "Expected 'contact' to be countable"
+ assert_not uncountable?(@klass), "Expected 'contact' to be countable"
end
def test_uncountable_route_key
diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb
index ff5022e960..ab60285e2a 100644
--- a/activemodel/test/cases/railtie_test.rb
+++ b/activemodel/test/cases/railtie_test.rb
@@ -31,4 +31,24 @@ class RailtieTest < ActiveModel::TestCase
assert_equal true, ActiveModel::SecurePassword.min_cost
end
+
+ test "i18n full message defaults to false" do
+ @app.initialize!
+
+ assert_equal false, ActiveModel::Errors.i18n_full_message
+ end
+
+ test "i18n full message can be disabled" do
+ @app.config.active_model.i18n_full_message = false
+ @app.initialize!
+
+ assert_equal false, ActiveModel::Errors.i18n_full_message
+ end
+
+ test "i18n full message can be enabled" do
+ @app.config.active_model.i18n_full_message = true
+ @app.initialize!
+
+ assert_equal true, ActiveModel::Errors.i18n_full_message
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 912cb6d5ab..bc23316ad5 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -49,14 +49,14 @@ class SecurePasswordTest < ActiveModel::TestCase
test "create a new user with validation and a blank password" do
@user.password = ""
- assert !@user.valid?(:create), "user should be invalid"
+ assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["can't be blank"], @user.errors[:password]
end
test "create a new user with validation and a nil password" do
@user.password = nil
- assert !@user.valid?(:create), "user should be invalid"
+ assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["can't be blank"], @user.errors[:password]
end
@@ -64,7 +64,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "create a new user with validation and password length greater than 72" do
@user.password = "a" * 73
@user.password_confirmation = "a" * 73
- assert !@user.valid?(:create), "user should be invalid"
+ assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["is too long (maximum is 72 characters)"], @user.errors[:password]
end
@@ -72,7 +72,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "create a new user with validation and a blank password confirmation" do
@user.password = "password"
@user.password_confirmation = ""
- assert !@user.valid?(:create), "user should be invalid"
+ assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end
@@ -86,7 +86,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "create a new user with validation and an incorrect password confirmation" do
@user.password = "password"
@user.password_confirmation = "something else"
- assert !@user.valid?(:create), "user should be invalid"
+ assert_not @user.valid?(:create), "user should be invalid"
assert_equal 1, @user.errors.count
assert_equal ["doesn't match Password"], @user.errors[:password_confirmation]
end
@@ -125,7 +125,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "updating an existing user with validation and a nil password" do
@existing_user.password = nil
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
end
@@ -133,7 +133,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "updating an existing user with validation and password length greater than 72" do
@existing_user.password = "a" * 73
@existing_user.password_confirmation = "a" * 73
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["is too long (maximum is 72 characters)"], @existing_user.errors[:password]
end
@@ -141,7 +141,7 @@ class SecurePasswordTest < ActiveModel::TestCase
test "updating an existing user with validation and a blank password confirmation" do
@existing_user.password = "password"
@existing_user.password_confirmation = ""
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end
@@ -155,21 +155,21 @@ class SecurePasswordTest < ActiveModel::TestCase
test "updating an existing user with validation and an incorrect password confirmation" do
@existing_user.password = "password"
@existing_user.password_confirmation = "something else"
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation]
end
test "updating an existing user with validation and a blank password digest" do
@existing_user.password_digest = ""
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
end
test "updating an existing user with validation and a nil password digest" do
@existing_user.password_digest = nil
- assert !@existing_user.valid?(:update), "user should be invalid"
+ assert_not @existing_user.valid?(:update), "user should be invalid"
assert_equal 1, @existing_user.errors.count
assert_equal ["can't be blank"], @existing_user.errors[:password]
end
@@ -186,9 +186,13 @@ class SecurePasswordTest < ActiveModel::TestCase
test "authenticate" do
@user.password = "secret"
+ @user.activation_token = "new_token"
assert_not @user.authenticate("wrong")
assert @user.authenticate("secret")
+
+ assert !@user.authenticate_activation_token("wrong")
+ assert @user.authenticate_activation_token("new_token")
end
test "Password digest cost defaults to bcrypt default cost when min_cost is false" do
diff --git a/activemodel/test/cases/type/time_test.rb b/activemodel/test/cases/type/time_test.rb
index f7102d1e97..3fbae1a169 100644
--- a/activemodel/test/cases/type/time_test.rb
+++ b/activemodel/test/cases/type/time_test.rb
@@ -17,6 +17,21 @@ module ActiveModel
assert_equal ::Time.utc(2000, 1, 1, 16, 45, 54), type.cast("2015-06-13T19:45:54+03:00")
assert_equal ::Time.utc(1999, 12, 31, 21, 7, 8), type.cast("06:07:08+09:00")
end
+
+ def test_user_input_in_time_zone
+ ::Time.use_zone("Pacific Time (US & Canada)") do
+ type = Type::Time.new
+ assert_nil type.user_input_in_time_zone(nil)
+ assert_nil type.user_input_in_time_zone("")
+ assert_nil type.user_input_in_time_zone("ABC")
+
+ offset = ::Time.zone.formatted_offset
+ time_string = "2015-02-09T19:45:54#{offset}"
+
+ assert_equal 19, type.user_input_in_time_zone(time_string).hour
+ assert_equal offset, type.user_input_in_time_zone(time_string).formatted_offset
+ end
+ end
end
end
end
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 9cfe189d0e..1c62e750de 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -12,6 +12,9 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.load_path.clear
I18n.backend = I18n::Backend::Simple.new
I18n.backend.store_translations("en", errors: { messages: { custom: nil } })
+
+ @original_i18n_full_message = ActiveModel::Errors.i18n_full_message
+ ActiveModel::Errors.i18n_full_message = true
end
def teardown
@@ -19,6 +22,7 @@ class I18nValidationTest < ActiveModel::TestCase
I18n.load_path.replace @old_load_path
I18n.backend = @old_backend
I18n.backend.reload!
+ ActiveModel::Errors.i18n_full_message = @original_i18n_full_message
end
def test_full_message_encoding
@@ -42,6 +46,61 @@ class I18nValidationTest < ActiveModel::TestCase
assert_equal ["Field Name empty"], @person.errors.full_messages
end
+ def test_errors_full_messages_doesnt_use_attribute_format_without_config
+ ActiveModel::Errors.i18n_full_message = false
+
+ I18n.backend.store_translations("en", activemodel: {
+ errors: { models: { person: { attributes: { name: { format: "%{message}" } } } } } })
+
+ person = Person.new
+ assert_equal "Name cannot be blank", person.errors.full_message(:name, "cannot be blank")
+ assert_equal "Name test cannot be blank", person.errors.full_message(:name_test, "cannot be blank")
+ end
+
+ def test_errors_full_messages_uses_attribute_format
+ ActiveModel::Errors.i18n_full_message = true
+
+ I18n.backend.store_translations("en", activemodel: {
+ errors: { models: { person: { attributes: { name: { format: "%{message}" } } } } } })
+
+ person = Person.new
+ assert_equal "cannot be blank", person.errors.full_message(:name, "cannot be blank")
+ assert_equal "Name test cannot be blank", person.errors.full_message(:name_test, "cannot be blank")
+ end
+
+ def test_errors_full_messages_uses_model_format
+ ActiveModel::Errors.i18n_full_message = true
+
+ I18n.backend.store_translations("en", activemodel: {
+ errors: { models: { person: { format: "%{message}" } } } })
+
+ person = Person.new
+ assert_equal "cannot be blank", person.errors.full_message(:name, "cannot be blank")
+ assert_equal "cannot be blank", person.errors.full_message(:name_test, "cannot be blank")
+ end
+
+ def test_errors_full_messages_uses_deeply_nested_model_attributes_format
+ ActiveModel::Errors.i18n_full_message = true
+
+ I18n.backend.store_translations("en", activemodel: {
+ errors: { models: { 'person/contacts/addresses': { attributes: { street: { format: "%{message}" } } } } } })
+
+ person = Person.new
+ assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.street', "cannot be blank")
+ assert_equal "Contacts/addresses country cannot be blank", person.errors.full_message(:'contacts/addresses.country', "cannot be blank")
+ end
+
+ def test_errors_full_messages_uses_deeply_nested_model_model_format
+ ActiveModel::Errors.i18n_full_message = true
+
+ I18n.backend.store_translations("en", activemodel: {
+ errors: { models: { 'person/contacts/addresses': { format: "%{message}" } } } })
+
+ person = Person.new
+ assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.street', "cannot be blank")
+ assert_equal "cannot be blank", person.errors.full_message(:'contacts/addresses.country', "cannot be blank")
+ end
+
# ActiveModel::Validations
# A set of common cases for ActiveModel::Validations message generation that
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 80c347703a..ae5a875c24 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -19,7 +19,7 @@ class ValidatesTest < ActiveModel::TestCase
def test_validates_with_messages_empty
Person.validates :title, presence: { message: "" }
person = Person.new
- assert !person.valid?, "person should not be valid."
+ assert_not person.valid?, "person should not be valid."
end
def test_validates_with_built_in_validation
diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb
index e98fd8a0a1..1ff3379153 100644
--- a/activemodel/test/models/user.rb
+++ b/activemodel/test/models/user.rb
@@ -7,6 +7,7 @@ class User
define_model_callbacks :create
has_secure_password
+ has_secure_password :activation_token, validations: false
- attr_accessor :password_digest
+ attr_accessor :password_digest, :activation_token_digest
end
diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb
index 9da004ffcc..96bf3ef10a 100644
--- a/activemodel/test/models/visitor.rb
+++ b/activemodel/test/models/visitor.rb
@@ -8,5 +8,6 @@ class Visitor
has_secure_password(validations: false)
- attr_accessor :password_digest, :password_confirmation
+ attr_accessor :password_digest
+ attr_reader :password_confirmation
end