aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG.md2
-rw-r--r--activemodel/README.rdoc11
-rw-r--r--activemodel/lib/active_model/configuration.rb6
-rw-r--r--activemodel/lib/active_model/dirty.rb7
-rw-r--r--activemodel/lib/active_model/errors.rb20
-rw-r--r--activemodel/lib/active_model/lint.rb8
-rw-r--r--activemodel/lib/active_model/locale/en.yml2
-rw-r--r--activemodel/lib/active_model/model.rb16
-rw-r--r--activemodel/lib/active_model/naming.rb42
-rw-r--r--activemodel/lib/active_model/observing.rb35
-rw-r--r--activemodel/lib/active_model/secure_password.rb11
-rw-r--r--activemodel/lib/active_model/serialization.rb19
-rw-r--r--activemodel/lib/active_model/translation.rb17
-rw-r--r--activemodel/lib/active_model/validations.rb17
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb3
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb31
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb3
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/with.rb6
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb10
-rw-r--r--activemodel/test/cases/naming_test.rb24
-rw-r--r--activemodel/test/cases/observing_test.rb47
-rw-r--r--activemodel/test/cases/secure_password_test.rb6
-rw-r--r--activemodel/test/cases/translation_test.rb10
-rw-r--r--activemodel/test/cases/validations/confirmation_validation_test.rb21
-rw-r--r--activemodel/test/cases/validations/i18n_generate_message_validation_test.rb2
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb17
-rw-r--r--activemodel/test/cases/validations/validates_test.rb2
29 files changed, 253 insertions, 194 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index c6d8eae46c..789cff0673 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,7 @@
## Rails 4.0.0 (unreleased) ##
+* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
+
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev*
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index 4861b0a002..9b05384792 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -14,7 +14,7 @@ Model solves this by defining an explicit API. You can read more about the
API in ActiveModel::Lint::Tests.
Active Model provides a default module that implements the basic API required
-to integrate with Action Pack out of the box: +ActiveModel::Model+.
+to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>.
class Person
include ActiveModel::Model
@@ -26,11 +26,11 @@ to integrate with Action Pack out of the box: +ActiveModel::Model+.
person = Person.new(:name => 'bob', :age => '18')
person.name # => 'bob'
person.age # => 18
- person.valid? # => false
+ person.valid? # => true
It includes model name introspections, conversions, translations and
validations, resulting in a class suitable to be used with Action Pack.
-See +ActiveModel::Model+ for more examples.
+See <tt>ActiveModel::Model</tt> for more examples.
Active Model also provides the following functionality to have ORM-like
behavior out of the box:
@@ -71,7 +71,7 @@ behavior out of the box:
This generates +before_create+, +around_create+ and +after_create+
class methods that wrap your create method.
- {Learn more}[link:classes/ActiveModel/CallBacks.html]
+ {Learn more}[link:classes/ActiveModel/Callbacks.html]
* Tracking value changes
@@ -116,9 +116,6 @@ behavior out of the box:
person.errors.full_messages
# => ["Name can not be nil"]
- person.errors.full_messages
- # => ["Name can not be nil"]
-
{Learn more}[link:classes/ActiveModel/Errors.html]
* Model name introspection
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
index 1757c12ebf..ba5a6a2075 100644
--- a/activemodel/lib/active_model/configuration.rb
+++ b/activemodel/lib/active_model/configuration.rb
@@ -95,7 +95,7 @@ module ActiveModel
end
def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__
+ host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
attr_accessor :#{name}
def #{name}?; !!#{name}; end
CODE
@@ -107,7 +107,7 @@ module ActiveModel
define_method("#{name}?") { !!send(name) }
end
- host.class_eval <<-CODE
+ host.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
def #{name}?; !!#{name}; end
CODE
@@ -117,7 +117,7 @@ module ActiveModel
define_method("#{name}=") { |val| host.send("#{name}=", val) }
end
else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__
+ class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
def #{name}=(val)
singleton_class.class_eval do
remove_possible_method(:#{name})
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 026f077ee7..7f7fb90d87 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,6 +1,7 @@
require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Dirty
@@ -98,7 +99,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- changed_attributes.any?
+ changed_attributes.present?
end
# List of attributes with unsaved changes.
@@ -150,13 +151,15 @@ module ActiveModel
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
+ return if attribute_changed?(attr)
+
begin
value = __send__(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value unless changed_attributes.include?(attr)
+ changed_attributes[attr] = value
end
# Handle <tt>reset_*!</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 042f9cd7e2..aba6618b56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -3,7 +3,6 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/reverse_merge'
module ActiveModel
# == Active Model Errors
@@ -130,12 +129,12 @@ module ActiveModel
# has more than one error message, yields once for each error message.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
# p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -202,12 +201,12 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
+ to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
# Returns an Hash that can be used as the JSON representation for this object.
# Options:
- # * <tt>:full_messages</tt> - determines if json object should contain
+ # * <tt>:full_messages</tt> - determines if json object should contain
# full messages or not. Default: <tt>false</tt>.
def as_json(options=nil)
to_hash(options && options[:full_messages])
@@ -217,7 +216,7 @@ module ActiveModel
if full_messages
messages = {}
self.messages.each do |attribute, array|
- messages[attribute] = array.map{|message| full_message(attribute, message) }
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
end
messages
else
@@ -286,7 +285,7 @@ module ActiveModel
# "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
- attr_name = attribute.to_s.gsub('.', '_').humanize
+ 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}",
@@ -347,7 +346,7 @@ module ActiveModel
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value
- }.merge(options)
+ }.merge!(options)
I18n.translate(key, options)
end
@@ -356,9 +355,10 @@ module ActiveModel
def normalize_message(attribute, message, options)
message ||= :invalid
- if message.is_a?(Symbol)
+ case message
+ when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
+ when Proc
message.call
else
message
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index a10fdefd1a..88b730626c 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -78,10 +78,10 @@ module ActiveModel
def test_model_naming
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
model_name = model.class.model_name
- assert_kind_of String, model_name
- assert_kind_of String, model_name.human
- assert_kind_of String, model_name.singular
- assert_kind_of String, model_name.plural
+ assert model_name.respond_to?(:to_str)
+ assert model_name.human.respond_to?(:to_str)
+ assert model_name.singular.respond_to?(:to_str)
+ assert model_name.plural.respond_to?(:to_str)
end
# == Errors Testing
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index ba49c6beaa..d17848c861 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -9,7 +9,7 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
- confirmation: "doesn't match confirmation"
+ confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index 6825fdc653..3af95b09b0 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -2,11 +2,11 @@ module ActiveModel
# == Active Model Basic Model
#
- # Includes the required interface for an object to interact with +ActionPack+,
- # using different +ActiveModel+ modules. It includes model name introspections,
+ # Includes the required interface for an object to interact with <tt>ActionPack</tt>,
+ # using different <tt>ActiveModel</tt> modules. It includes model name introspections,
# conversions, translations and validations. Besides that, it allows you to
# initialize the object with a hash of attributes, pretty much like
- # +ActiveRecord+ does.
+ # <tt>ActiveRecord</tt> does.
#
# A minimal implementation could be:
#
@@ -19,8 +19,8 @@ module ActiveModel
# person.name # => 'bob'
# person.age # => 18
#
- # Note that, by default, +ActiveModel::Model+ implements +persisted?+ to
- # return +false+, which is the most common case. You may want to override it
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to
+ # return <tt>false</tt>, which is the most common case. You may want to override it
# in your class to simulate a different scenario:
#
# class Person
@@ -35,14 +35,14 @@ module ActiveModel
# person = Person.new(:id => 1, :name => 'bob')
# person.persisted? # => true
#
- # Also, if for some reason you need to run code on +initialize+, make sure you
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you
# call super if you want the attributes hash initialization to happen.
#
# class Person
# include ActiveModel::Model
# attr_accessor :id, :name, :omg
#
- # def initialize(attributes)
+ # def initialize(attributes={})
# super
# @omg ||= true
# end
@@ -52,7 +52,7 @@ module ActiveModel
# person.omg # => true
#
# For more detailed information on other functionalities available, please refer
- # to the specific modules included in +ActiveModel::Model+ (see below).
+ # to the specific modules included in <tt>ActiveModel::Model</tt> (see below).
module Model
def self.included(base)
base.class_eval do
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index fd0bc4e8e9..2b5fc57a3a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,40 +1,40 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/core_ext/module/delegation'
require 'active_support/core_ext/object/blank'
module ActiveModel
- class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path,
- :singular_route_key, :route_key, :param_key, :i18n_key
+ class Name
+ include Comparable
+
+ attr_reader :singular, :plural, :element, :collection,
+ :singular_route_key, :route_key, :param_key, :i18n_key,
+ :name
alias_method :cache_key, :collection
- deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
+ :to_str, :to => :name
def initialize(klass, namespace = nil, name = nil)
- name ||= klass.name
-
- raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
+ @name = name || klass.name
- super(name)
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
- @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
@klass = klass
- @singular = _singularize(self).freeze
- @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
- @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
- @human = ActiveSupport::Inflector.humanize(@element).freeze
- @collection = ActiveSupport::Inflector.tableize(self).freeze
- @partial_path = "#{@collection}/#{@element}".freeze
- @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
- @i18n_key = self.underscore.to_sym
+ @singular = _singularize(@name)
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
+ @human = ActiveSupport::Inflector.humanize(@element)
+ @collection = ActiveSupport::Inflector.tableize(@name)
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
+ @i18n_key = @name.underscore.to_sym
@route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
- @singular_route_key = ActiveSupport::Inflector.singularize(@route_key).freeze
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
@route_key << "_index" if @plural == @singular
- @route_key.freeze
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -54,7 +54,7 @@ module ActiveModel
defaults << options[:default] if options[:default]
defaults << @human
- options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
+ options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
I18n.translate(defaults.shift, options)
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index 32f2aa46bd..f3781f7a68 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -4,6 +4,8 @@ require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/enumerable'
+require 'active_support/deprecation'
+require 'active_support/core_ext/object/try'
require 'active_support/descendants_tracker'
module ActiveModel
@@ -69,27 +71,34 @@ module ActiveModel
end
# Notify list of observers of a change.
- def notify_observers(*arg)
- observer_instances.each { |observer| observer.update(*arg) }
+ def notify_observers(*args)
+ observer_instances.each { |observer| observer.update(*args) }
end
# Total number of observers.
- def count_observers
+ def observers_count
observer_instances.size
end
+ def count_observers
+ msg = "count_observers is deprecated in favor of observers_count"
+ ActiveSupport::Deprecation.warn(msg)
+ observers_count
+ end
+
protected
def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
- observer.to_s.camelize.constantize.instance
- elsif observer.respond_to?(:instance)
+ observer = observer.to_s.camelize.constantize
+ end
+ if observer.respond_to?(:instance)
observer.instance
else
raise ArgumentError,
- "#{observer} must be a lowercase, underscored class name (or an " +
- "instance of the class itself) responding to the instance " +
- "method. Example: Person.observers = :big_brother # calls " +
+ "#{observer} must be a lowercase, underscored class name (or " +
+ "the class itself) responding to the method :instance. " +
+ "Example: Person.observers = :big_brother # calls " +
"BigBrother.instance"
end
end
@@ -186,7 +195,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- redefine_method(:observed_classes) { models }
+ singleton_class.redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
@@ -205,15 +214,12 @@ module ActiveModel
# The class observed by default is inferred from the observer's class name:
# assert_equal Person, PersonObserver.observed_class
def observed_class
- if observed_class_name = name[/(.*)Observer/, 1]
- observed_class_name.constantize
- else
- nil
- end
+ name[/(.*)Observer/, 1].try :constantize
end
end
# Start observing the declared classes and their subclasses.
+ # Called automatically by the instance method.
def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -242,6 +248,7 @@ module ActiveModel
klass.add_observer(self)
end
+ # Returns true if notifications are disabled for this object.
def disabled_for?(object)
klass = object.class
return false unless klass.respond_to?(:observers)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index e7a57cf691..8711b24124 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -55,17 +55,14 @@ module ActiveModel
module InstanceMethodsOnActivation
# Returns self if the password is correct, otherwise false.
def authenticate(unencrypted_password)
- if BCrypt::Password.new(password_digest) == unencrypted_password
- self
- else
- false
- end
+ BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute.
+ # Encrypts the password into the password_digest attribute, only if the
+ # new password is not blank.
def password=(unencrypted_password)
- @password = unencrypted_password
unless unencrypted_password.blank?
+ @password = unencrypted_password
self.password_digest = BCrypt::Password.create(unencrypted_password)
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 4323ee1e09..4403ef060b 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -26,17 +26,18 @@ module ActiveModel
# person.serializable_hash # => {"name"=>"Bob"}
#
# You need to declare an attributes hash which contains the attributes
- # you want to serialize. When called, serializable hash will use
+ # you want to serialize. Attributes must be strings, not symbols.
+ # When called, serializable hash will use
# instance methods that match the name of the attributes hash's keys.
# In order to override this behavior, take a look at the private
- # method read_attribute_for_serialization.
+ # method +read_attribute_for_serialization+.
#
# Most of the time though, you will want to include the JSON or XML
# serializations. Both of these modules automatically include the
- # ActiveModel::Serialization module, so there is no need to explicitly
+ # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly
# include it.
#
- # So a minimal implementation including XML and JSON would be:
+ # A minimal implementation including XML and JSON would be:
#
# class Person
# include ActiveModel::Serializers::JSON
@@ -63,7 +64,12 @@ module ActiveModel
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
- # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
+ # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>include</tt>.
+ # The following are all valid examples:
+ #
+ # person.serializable_hash(:only => 'name')
+ # person.serializable_hash(:include => :address)
+ # person.serializable_hash(:include => { :address => { :only => 'city' }})
module Serialization
def serializable_hash(options = nil)
options ||= {}
@@ -78,8 +84,7 @@ module ActiveModel
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- method_names = Array(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n.to_s] = send(n) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.is_a?(Enumerable)
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 02b7c54d61..6f0ca92e2a 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
-
module ActiveModel
# == Active Model Translation
@@ -43,19 +41,20 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- defaults = []
+ options = { :count => 1 }.merge!(options)
parts = attribute.to_s.split(".", 2)
attribute = parts.pop
namespace = parts.pop
+ attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
- lookup_ancestors.each do |klass|
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
end
- defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}"
+ defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
else
- lookup_ancestors.each do |klass|
- defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
end
end
@@ -63,7 +62,7 @@ module ActiveModel
defaults << options.delete(:default) if options[:default]
defaults << attribute.humanize
- options.reverse_merge! :count => 1, :default => defaults
+ options[:default] = defaults
I18n.translate(defaults.shift, options)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 0e15155b85..3ed72bae3b 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -65,7 +65,7 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name do |record, attr, value|
+ # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
@@ -128,6 +128,19 @@ module ActiveModel
# end
# end
#
+ # Options:
+ # * <tt>:on</tt> - Specifies the context where this validation is active
+ # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
@@ -145,7 +158,7 @@ module ActiveModel
_validators.values.flatten.uniq
end
- # List all validators that being used to validate a specific attribute.
+ # List all validators that are being used to validate a specific attribute.
def validators_on(*attributes)
attributes.map do |attribute|
_validators[attribute.to_sym]
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index c39c85e1af..dbafd0bd1a 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -8,7 +8,8 @@ module ActiveModel
# Provides an interface for any class to have <tt>before_validation</tt> and
# <tt>after_validation</tt> callbacks.
#
- # First, extend ActiveModel::Callbacks from the class you are creating:
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
#
# class MyModel
# include ActiveModel::Validations::Callbacks
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
new file mode 100644
index 0000000000..b632a2bd6b
--- /dev/null
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -0,0 +1,31 @@
+require 'active_support/core_ext/range.rb'
+
+module ActiveModel
+ module Validations
+ module Clusivity
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
+ def check_validity!
+ unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
+ end
+
+ private
+
+ def include?(record, value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ exclusions.send(inclusion_method(exclusions), value)
+ end
+
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
+ # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
+ # uses the previous logic of comparing a value with the range endpoints.
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index e8526303e2..69ab74734d 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -5,7 +5,8 @@ module ActiveModel
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
- record.errors.add(attribute, :confirmation, options)
+ human_attribute_name = record.class.human_attribute_name(attribute)
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 644cc814a7..5fedb1978b 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,35 +1,17 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
module ActiveModel
# == Active Model Exclusion Validator
module Validations
class ExclusionValidator < EachValidator
- ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
- "and must be supplied as the :in option of the configuration hash"
-
- def check_validity!
- unless [:include?, :call].any? { |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- if exclusions.send(inclusion_method(exclusions), value)
+ if include?(record, value)
record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
end
end
-
- private
-
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
- # uses the previous logic of comparing a value with the range endpoints.
- def inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
- end
end
module HelperMethods
@@ -59,7 +41,7 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 147e2ecb69..15ae7b1959 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,35 +1,17 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
module ActiveModel
# == Active Model Inclusion Validator
module Validations
class InclusionValidator < EachValidator
- ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
- "and must be supplied as the :in option of the configuration hash"
-
- def check_validity!
- unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- unless exclusions.send(inclusion_method(exclusions), value)
+ unless include?(record, value)
record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
end
end
-
- private
-
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
- # uses the previous logic of comparing a value with the range endpoints.
- def inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
- end
end
module HelperMethods
@@ -59,7 +41,7 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 72b8562b93..991c5f7b82 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -126,12 +126,12 @@ module ActiveModel
# end
#
# Standard configuration options (:on, :if and :unless), which are
- # available on the class version of validates_with, should instead be
- # placed on the <tt>validates</tt> method as these are applied and tested
+ # available on the class version of +validates_with+, should instead be
+ # placed on the +validates+ method as these are applied and tested
# in the callback
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>, please refer to the
+ # to the class and available as +options+, please refer to the
# class version of this method for more information
#
def validates_with(*args, &block)
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 9406328d3e..34298d31c2 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -188,6 +188,12 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_raises(NoMethodError) { m.protected_method }
end
+ class ClassWithProtected
+ protected
+ def protected_method
+ end
+ end
+
test 'should not interfere with respond_to? if the attribute has a private/protected method' do
m = ModelWithAttributes2.new
m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
@@ -195,9 +201,11 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert !m.respond_to?(:private_method)
assert m.respond_to?(:private_method, true)
+ c = ClassWithProtected.new
+
# This is messed up, but it's how Ruby works at the moment. Apparently it will be changed
# in the future.
- assert m.respond_to?(:protected_method)
+ assert_equal c.respond_to?(:protected_method), m.respond_to?(:protected_method)
assert m.respond_to?(:protected_method, true)
end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index 1e14d83bcb..49d8706ac2 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -25,12 +25,6 @@ class NamingTest < ActiveModel::TestCase
assert_equal 'post/track_backs', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'post/track_backs/track_back', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Track back', @model_name.human
end
@@ -61,12 +55,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
assert_equal 'blog/posts', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'blog/posts/post', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Post', @model_name.human
end
@@ -105,12 +93,6 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
assert_equal 'blog/posts', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'blog/posts/post', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Post', @model_name.human
end
@@ -149,12 +131,6 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
assert_equal 'articles', @model_name.collection
end
- def test_partial_path
- assert_deprecated(/#partial_path.*#to_partial_path/) do
- assert_equal 'articles/article', @model_name.partial_path
- end
- end
-
def test_human
assert_equal 'Article', @model_name.human
end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index f6ec24ae57..3b636a6768 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -70,23 +70,38 @@ class ObservingTest < ActiveModel::TestCase
ObservedModel.instantiate_observers
end
+ test "raises an appropriate error when a developer accidentally adds the wrong class (i.e. Widget instead of WidgetObserver)" do
+ assert_raise ArgumentError do
+ ObservedModel.observers = ['string']
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [:string]
+ ObservedModel.instantiate_observers
+ end
+ assert_raise ArgumentError do
+ ObservedModel.observers = [String]
+ ObservedModel.instantiate_observers
+ end
+ end
+
test "passes observers to subclasses" do
FooObserver.instance
bar = Class.new(Foo)
- assert_equal Foo.count_observers, bar.count_observers
+ assert_equal Foo.observers_count, bar.observers_count
end
end
class ObserverTest < ActiveModel::TestCase
def setup
ObservedModel.observers = :foo_observer
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
alias_method :original_observed_classes, :observed_classes
end
end
def teardown
- FooObserver.instance_eval do
+ FooObserver.singleton_class.instance_eval do
undef_method :observed_classes
alias_method :observed_classes, :original_observed_classes
end
@@ -98,32 +113,25 @@ class ObserverTest < ActiveModel::TestCase
test "tracks implicit observable models" do
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(Foo), "Foo not in #{instance.send(:observed_classes).inspect}"
- assert !instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{instance.send(:observed_classes).inspect}"
+ assert_equal [Foo], old_instance.observed_classes
end
test "tracks explicit observed model class" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe ObservedModel
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as string" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe 'observed_model'
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "tracks explicit observed model as symbol" do
- old_instance = FooObserver.new
- assert !old_instance.send(:observed_classes).include?(ObservedModel), "ObservedModel in #{old_instance.send(:observed_classes).inspect}"
FooObserver.observe :observed_model
instance = FooObserver.new
- assert instance.send(:observed_classes).include?(ObservedModel), "ObservedModel not in #{instance.send(:observed_classes).inspect}"
+ assert_equal [ObservedModel], instance.observed_classes
end
test "calls existing observer event" do
@@ -145,4 +153,15 @@ class ObserverTest < ActiveModel::TestCase
end
assert_equal :in_around_save, yielded_value
end
+
+ test "observe redefines observed_classes class method" do
+ class BarObserver < ActiveModel::Observer
+ observe :foo
+ end
+
+ assert_equal [Foo], BarObserver.observed_classes
+
+ BarObserver.observe(ObservedModel)
+ assert_equal [ObservedModel], BarObserver.observed_classes
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 4338a3fc53..c451cc1aca 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -19,6 +19,12 @@ class SecurePasswordTest < ActiveModel::TestCase
assert !@user.valid?, 'user should be invalid'
end
+ test "blank password doesn't override previous password" do
+ @user.password = 'test'
+ @user.password = ''
+ assert_equal @user.password, 'test'
+ end
+
test "password must be present" do
assert !@user.valid?
assert_equal 1, @user.errors.size
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 54e86d48db..4999583802 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -82,9 +82,15 @@ class ActiveModelI18nTests < ActiveModel::TestCase
end
def test_human_does_not_modify_options
- options = {:default => 'person model'}
+ options = { :default => 'person model' }
Person.model_name.human(options)
- assert_equal({:default => 'person model'}, options)
+ assert_equal({ :default => 'person model' }, options)
+ end
+
+ def test_human_attribute_name_does_not_modify_options
+ options = { :default => 'Cool gender' }
+ Person.human_attribute_name('gender', options)
+ assert_equal({ :default => 'Cool gender' }, options)
end
end
diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb
index d0418170fa..f7556a249f 100644
--- a/activemodel/test/cases/validations/confirmation_validation_test.rb
+++ b/activemodel/test/cases/validations/confirmation_validation_test.rb
@@ -44,7 +44,7 @@ class ConfirmationValidationTest < ActiveModel::TestCase
p.karma_confirmation = "None"
assert p.invalid?
- assert_equal ["doesn't match confirmation"], p.errors[:karma]
+ assert_equal ["doesn't match Karma"], p.errors[:karma_confirmation]
p.karma = "None"
assert p.valid?
@@ -52,4 +52,23 @@ class ConfirmationValidationTest < ActiveModel::TestCase
Person.reset_callbacks(:validate)
end
+ def test_title_confirmation_with_i18n_attribute
+ @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend
+ I18n.load_path.clear
+ I18n.backend = I18n::Backend::Simple.new
+ I18n.backend.store_translations('en', {
+ :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}},
+ :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}}
+ })
+
+ Topic.validates_confirmation_of(:title)
+
+ t = Topic.new("title" => "We should be confirmed","title_confirmation" => "")
+ assert t.invalid?
+ assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation]
+
+ I18n.load_path.replace @old_load_path
+ I18n.backend = @old_backend
+ end
+
end
diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
index 0679e67f84..df0fcd243a 100644
--- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb
@@ -37,7 +37,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase
# validates_confirmation_of: generate_message(attr_name, :confirmation, :message => custom_message)
def test_generate_message_confirmation_with_default_message
- assert_equal "doesn't match confirmation", @person.errors.generate_message(:title, :confirmation)
+ assert_equal "doesn't match Title", @person.errors.generate_message(:title, :confirmation)
end
def test_generate_message_confirmation_with_custom_message
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index e9f0e430fe..6b6aad3bd1 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -81,7 +81,7 @@ class I18nValidationTest < ActiveModel::TestCase
test "validates_confirmation_of on generated message #{name}" do
Person.validates_confirmation_of :title, validation_options
@person.title_confirmation = 'foo'
- @person.errors.expects(:generate_message).with(:title, :confirmation, generate_message_options)
+ @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title'))
@person.valid?
end
end
@@ -217,24 +217,29 @@ class I18nValidationTest < ActiveModel::TestCase
# To make things DRY this macro is defined to define 3 tests for every validation case.
def self.set_expectations_for_validation(validation, error_type, &block_that_sets_validation)
+ if error_type == :confirmation
+ attribute = :title_confirmation
+ else
+ attribute = :title
+ end
# test "validates_confirmation_of finds custom model key translation when blank"
test "#{validation} finds custom model key translation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {})
@person.valid?
- assert_equal ['custom message'], @person.errors[:title]
+ assert_equal ['custom message'], @person.errors[attribute]
end
# test "validates_confirmation_of finds custom model key translation with interpolation when blank"
test "#{validation} finds custom model key translation with interpolation when #{error_type}" do
- I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {error_type => 'custom message with %{extra}'}}}}}}
+ I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}}
I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}}
yield(@person, {:extra => "extra information"})
@person.valid?
- assert_equal ['custom message with extra information'], @person.errors[:title]
+ assert_equal ['custom message with extra information'], @person.errors[attribute]
end
# test "validates_confirmation_of finds global default key translation when blank"
@@ -243,7 +248,7 @@ class I18nValidationTest < ActiveModel::TestCase
yield(@person, {})
@person.valid?
- assert_equal ['global message'], @person.errors[:title]
+ assert_equal ['global message'], @person.errors[attribute]
end
end
diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb
index 575154ffbd..90bc018ae1 100644
--- a/activemodel/test/cases/validations/validates_test.rb
+++ b/activemodel/test/cases/validations/validates_test.rb
@@ -154,6 +154,6 @@ class ValidatesTest < ActiveModel::TestCase
topic.title = "What's happening"
topic.title_confirmation = "Not this"
assert !topic.valid?
- assert_equal ['Y U NO CONFIRM'], topic.errors[:title]
+ assert_equal ['Y U NO CONFIRM'], topic.errors[:title_confirmation]
end
end