aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib/active_model
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib/active_model')
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb100
-rw-r--r--activemodel/lib/active_model/callbacks.rb45
-rw-r--r--activemodel/lib/active_model/conversion.rb4
-rw-r--r--activemodel/lib/active_model/deprecated_mass_assignment_security.rb19
-rw-r--r--activemodel/lib/active_model/dirty.rb70
-rw-r--r--activemodel/lib/active_model/errors.rb86
-rw-r--r--activemodel/lib/active_model/forbidden_attributes_protection.rb2
-rw-r--r--activemodel/lib/active_model/lint.rb10
-rw-r--r--activemodel/lib/active_model/locale/en.yml1
-rw-r--r--activemodel/lib/active_model/model.rb4
-rw-r--r--activemodel/lib/active_model/naming.rb11
-rw-r--r--activemodel/lib/active_model/observer_array.rb152
-rw-r--r--activemodel/lib/active_model/observing.rb374
-rw-r--r--activemodel/lib/active_model/railtie.rb8
-rw-r--r--activemodel/lib/active_model/secure_password.rb63
-rw-r--r--activemodel/lib/active_model/serialization.rb4
-rw-r--r--activemodel/lib/active_model/serializers/json.rb4
-rw-r--r--[-rwxr-xr-x]activemodel/lib/active_model/serializers/xml.rb32
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb75
-rw-r--r--activemodel/lib/active_model/validations/absence.rb31
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb11
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb17
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb33
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb19
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb5
-rw-r--r--activemodel/lib/active_model/validations/format.rb5
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb7
-rw-r--r--activemodel/lib/active_model/validations/length.rb26
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb19
-rw-r--r--activemodel/lib/active_model/validations/presence.rb7
-rw-r--r--activemodel/lib/active_model/validations/validates.rb17
-rw-r--r--activemodel/lib/active_model/validations/with.rb5
-rw-r--r--activemodel/lib/active_model/validator.rb47
-rw-r--r--activemodel/lib/active_model/version.rb13
35 files changed, 489 insertions, 841 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ef04f1fa49..f336c759d2 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,3 +1,5 @@
+require 'thread_safe'
+require 'mutex_m'
module ActiveModel
# Raised when an attribute is not defined.
@@ -8,22 +10,24 @@ module ActiveModel
#
# user = User.first
# user.pets.select(:id).first.user_id
- # # => ActiveModel::MissingAttributeError: missing attribute: user_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
- # == Active Model Attribute Methods
+
+ # == Active \Model Attribute Methods
#
# <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and
- # suffixes to your methods as well as handling the creation of Active Record
- # like class methods such as +table_name+.
+ # suffixes to your methods as well as handling the creation of
+ # <tt>ActiveRecord::Base</tt>-like class methods such as +table_name+.
#
- # The requirements to implement ActiveModel::AttributeMethods are to:
+ # The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to:
#
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object.
- # * Call each Attribute Method module method you want to add, such as
- # +attribute_method_suffix+ or +attribute_method_prefix+.
+ # * <tt>include ActiveModel::AttributeMethods</tt> in your class.
+ # * Call each of its method you want to add, such as +attribute_method_suffix+
+ # or +attribute_method_prefix+.
# * Call +define_attribute_methods+ after the other methods are called.
# * Define the various generic +_attribute+ methods that you have declared.
+ # * Define an +attributes+ method, see below.
#
# A minimal implementation could be:
#
@@ -37,6 +41,10 @@ module ActiveModel
#
# attr_accessor :name
#
+ # def attributes
+ # {'name' => @name}
+ # end
+ #
# private
#
# def attribute_contrived?(attr)
@@ -52,10 +60,10 @@ module ActiveModel
# end
# end
#
- # Note that whenever you include ActiveModel::AttributeMethods in your class,
- # it requires you to implement an +attributes+ method which returns a hash
- # with each attribute name in your model as hash key and the attribute value as
- # hash value.
+ # Note that whenever you include <tt>ActiveModel::AttributeMethods</tt> in
+ # your class, it requires you to implement an +attributes+ method which
+ # returns a hash with each attribute name in your model as hash key and the
+ # attribute value as hash value.
#
# Hash keys must be strings.
module AttributeMethods
@@ -178,7 +186,6 @@ module ActiveModel
undefine_attribute_methods
end
-
# Allows you to make aliases for attributes.
#
# class Person
@@ -202,7 +209,7 @@ module ActiveModel
# person.name # => "Bob"
# person.nickname # => "Bob"
# person.name_short? # => true
- # person.nickname_short? # => true
+ # person.nickname_short? # => true
def alias_attribute(new_name, old_name)
self.attribute_aliases = attribute_aliases.merge(new_name.to_s => old_name.to_s)
attribute_method_matchers.each do |matcher|
@@ -212,6 +219,16 @@ module ActiveModel
end
end
+ # Is +new_name+ an alias?
+ def attribute_alias?(new_name)
+ attribute_aliases.key? new_name.to_s
+ end
+
+ # Returns the original name for the alias +name+
+ def attribute_alias(name)
+ attribute_aliases[name.to_s]
+ end
+
# Declares the attributes that should be prefixed and suffixed by
# ActiveModel::AttributeMethods.
#
@@ -316,9 +333,10 @@ module ActiveModel
attribute_method_matchers_cache.clear
end
- # Returns true if the attribute methods defined have been generated.
def generated_attribute_methods #:nodoc:
- @generated_attribute_methods ||= Module.new.tap { |mod| include mod }
+ @generated_attribute_methods ||= Module.new {
+ extend Mutex_m
+ }.tap { |mod| include mod }
end
protected
@@ -337,17 +355,17 @@ module ActiveModel
# significantly (in our case our test suite finishes 10% faster with
# this cache).
def attribute_method_matchers_cache #:nodoc:
- @attribute_method_matchers_cache ||= {}
+ @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
def attribute_method_matcher(method_name) #:nodoc:
- attribute_method_matchers_cache.fetch(method_name) do |name|
+ attribute_method_matchers_cache.compute_if_absent(method_name) do
# Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix
# will match every time.
matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1)
match = nil
- matchers.detect { |method| match = method.match(name) }
- attribute_method_matchers_cache[name] = match
+ matchers.detect { |method| match = method.match(method_name) }
+ match
end
end
@@ -382,15 +400,6 @@ module ActiveModel
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
- if options[:prefix] == '' || options[:suffix] == ''
- ActiveSupport::Deprecation.warn(
- "Specifying an empty prefix/suffix for an attribute method is no longer " \
- "necessary. If the un-prefixed/suffixed version of the method has not been " \
- "defined when `define_attribute_methods` is called, it will be defined " \
- "automatically."
- )
- end
-
@prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@@ -413,17 +422,16 @@ module ActiveModel
end
end
- # Allows access to the object attributes, which are held in the
- # <tt>@attributes</tt> hash, as though they were first-class methods. So a
- # Person class with a name attribute can use Person#name and Person#name=
- # and never directly use the attributes hash -- except for multiple assigns
- # with ActiveRecord#attributes=. A Milestone class can also ask
- # Milestone#completed? to test that the completed attribute is not +nil+
- # or 0.
+ # Allows access to the object attributes, which are held in the hash
+ # returned by <tt>attributes</tt>, as though they were first-class
+ # methods. So a +Person+ class with a +name+ attribute can for example use
+ # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use
+ # the attributes hash -- except for multiple assigns with
+ # <tt>ActiveRecord::Base#attributes=</tt>.
#
- # It's also possible to instantiate related objects, so a Client class
- # belonging to the clients table with a +master_id+ foreign key can
- # instantiate master through Client#master.
+ # It's also possible to instantiate related objects, so a <tt>Client</tt>
+ # class belonging to the +clients+ table with a +master_id+ foreign key
+ # can instantiate master through <tt>Client#master</tt>.
def method_missing(method, *args, &block)
if respond_to_without_attributes?(method, true)
super
@@ -433,17 +441,17 @@ module ActiveModel
end
end
- # attribute_missing is like method_missing, but for attributes. When method_missing is
- # called we check to see if there is a matching attribute method. If so, we call
- # attribute_missing to dispatch the attribute. This method can be overloaded to
- # customise the behaviour.
+ # +attribute_missing+ is like +method_missing+, but for attributes. When
+ # +method_missing+ is called we check to see if there is a matching
+ # attribute method. If so, we tell +attribute_missing+ to dispatch the
+ # attribute. This method can be overloaded to customize the behavior.
def attribute_missing(match, *args, &block)
__send__(match.target, match.attr_name, *args, &block)
end
- # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
- # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt>
- # which will all return +true+.
+ # A +Person+ instance with a +name+ attribute can ask
+ # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>,
+ # and <tt>person.respond_to?(:name?)</tt> which will all return +true+.
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_private_methods = false)
if super
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index e442455a53..377aa6ee27 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,7 +1,7 @@
-require 'active_support/callbacks'
+require 'active_support/core_ext/array/extract_options'
module ActiveModel
- # == Active Model Callbacks
+ # == Active \Model \Callbacks
#
# Provides an interface for any class to have Active Record like callbacks.
#
@@ -100,10 +100,10 @@ module ActiveModel
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- :terminator => "result == false",
- :skip_after_callbacks_if_terminated => true,
- :scope => [:kind, :name],
- :only => [:before, :around, :after]
+ terminator: ->(_,result) { result == false },
+ skip_after_callbacks_if_terminated: true,
+ scope: [:kind, :name],
+ only: [:before, :around, :after]
}.merge!(options)
types = Array(options.delete(:only))
@@ -120,30 +120,27 @@ module ActiveModel
private
def _define_before_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.before_#{callback}(*args, &block)
- set_callback(:#{callback}, :before, *args, &block)
- end
- CALLBACK
+ klass.define_singleton_method("before_#{callback}") do |*args, &block|
+ set_callback(:"#{callback}", :before, *args, &block)
+ end
end
def _define_around_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.around_#{callback}(*args, &block)
- set_callback(:#{callback}, :around, *args, &block)
- end
- CALLBACK
+ klass.define_singleton_method("around_#{callback}") do |*args, &block|
+ set_callback(:"#{callback}", :around, *args, &block)
+ end
end
def _define_after_model_callback(klass, callback) #:nodoc:
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
- def self.after_#{callback}(*args, &block)
- options = args.extract_options!
- options[:prepend] = true
- options[:if] = Array(options[:if]) << "value != false"
- set_callback(:#{callback}, :after, *(args << options), &block)
- end
- CALLBACK
+ klass.define_singleton_method("after_#{callback}") do |*args, &block|
+ options = args.extract_options!
+ options[:prepend] = true
+ conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v|
+ v != false
+ }
+ options[:if] = Array(options[:if]) << conditional
+ set_callback(:"#{callback}", :after, *(args << options), &block)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 48c53f0789..21e4eb3c86 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -1,7 +1,5 @@
-require 'active_support/inflector'
-
module ActiveModel
- # == Active Model Conversions
+ # == Active \Model Conversion
#
# Handles default conversions: to_model, to_key, to_param, and to_partial_path.
#
diff --git a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb
deleted file mode 100644
index 2ea69991fc..0000000000
--- a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-module ActiveModel
- module DeprecatedMassAssignmentSecurity # :nodoc:
- extend ActiveSupport::Concern
-
- module ClassMethods # :nodoc:
- def attr_protected(*args)
- raise "`attr_protected` is extracted out of Rails into a gem. " \
- "Please use new recommended protection model for params " \
- "or add `protected_attributes` to your Gemfile to use old one."
- end
-
- def attr_accessible(*args)
- raise "`attr_accessible` is extracted out of Rails into a gem. " \
- "Please use new recommended protection model for params " \
- "or add `protected_attributes` to your Gemfile to use old one."
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index c0b268fa4d..c5f1b3f11a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,9 +1,8 @@
-require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
module ActiveModel
- # == Active Model Dirty
+ # == Active \Model \Dirty
#
# Provides a way to track changes in your object in the same way as
# Active Record does.
@@ -15,13 +14,9 @@ module ActiveModel
# track.
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
# attribute.
- #
- # If you wish to also track previous changes on save or update, you need to
- # add:
- #
- # @previously_changed = changes
- #
- # inside of your save or update method.
+ # * Call <tt>changes_applied</tt> after the changes are persisted.
+ # * Call <tt>reset_changes</tt> when you want to reset the changes
+ # information.
#
# A minimal implementation could be:
#
@@ -40,14 +35,18 @@ module ActiveModel
# end
#
# def save
- # @previously_changed = changes
- # @changed_attributes.clear
+ # # do persistence work
+ # changes_applied
+ # end
+ #
+ # def reload!
+ # reset_changes
# end
# end
#
# A newly instantiated object is unchanged:
#
- # person = Person.find_by_name('Uncle Bob')
+ # person = Person.find_by(name: 'Uncle Bob')
# person.changed? # => false
#
# Change the name:
@@ -66,6 +65,12 @@ module ActiveModel
# person.changed? # => false
# person.name_changed? # => false
#
+ # Reset the changes:
+ #
+ # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]}
+ # person.reload!
+ # person.previous_changes # => {}
+ #
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
@@ -92,7 +97,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
- attribute_method_affix :prefix => 'reset_', :suffix => '!'
+ attribute_method_affix prefix: 'reset_', suffix: '!'
end
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
@@ -120,7 +125,7 @@ module ActiveModel
# person.name = 'bob'
# person.changes # => { "name" => ["bill", "bob"] }
def changes
- HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
+ ActiveSupport::HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
# Returns a hash of attributes that were changed before the model was saved.
@@ -130,7 +135,7 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- @previously_changed
+ @previously_changed ||= {}
end
# Returns a hash of the attributes with unsaved changes indicating their original
@@ -140,14 +145,31 @@ module ActiveModel
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
- @changed_attributes ||= {}
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
+ end
+
+ # Handle <tt>*_changed?</tt> for +method_missing+.
+ def attribute_changed?(attr)
+ changed_attributes.include?(attr)
+ end
+
+ # Handle <tt>*_was</tt> for +method_missing+.
+ def attribute_was(attr)
+ attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
private
- # Handle <tt>*_changed?</tt> for +method_missing+.
- def attribute_changed?(attr)
- changed_attributes.include?(attr)
+ # Removes current changes and makes them accessible through +previous_changes+.
+ def changes_applied
+ @previously_changed = changes
+ @changed_attributes = {}
+ end
+
+ # Removes all dirty data: current changes and previous changes
+ def reset_changes
+ @previously_changed = {}
+ @changed_attributes = {}
end
# Handle <tt>*_change</tt> for +method_missing+.
@@ -155,11 +177,6 @@ module ActiveModel
[changed_attributes[attr], __send__(attr)] if attribute_changed?(attr)
end
- # Handle <tt>*_was</tt> for +method_missing+.
- def attribute_was(attr)
- attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
- end
-
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
return if attribute_changed?(attr)
@@ -175,7 +192,10 @@ module ActiveModel
# Handle <tt>reset_*!</tt> for +method_missing+.
def reset_attribute!(attr)
- __send__("#{attr}=", changed_attributes[attr]) if attribute_changed?(attr)
+ if attribute_changed?(attr)
+ __send__("#{attr}=", changed_attributes[attr])
+ changed_attributes.delete(attr)
+ end
end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index b3b9ba8e56..cf7551e4f4 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
module ActiveModel
- # == Active Model Errors
+ # == Active \Model \Errors
#
# Provides a modified +Hash+ that you can include in your object
# for handling error messages and interacting with Action Pack helpers.
@@ -12,7 +12,6 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# # Required dependency for ActiveModel::Errors
# extend ActiveModel::Naming
#
@@ -40,7 +39,6 @@ module ActiveModel
# def Person.lookup_ancestors
# [self]
# end
- #
# end
#
# The last three methods are required in your object for Errors to be
@@ -52,7 +50,7 @@ module ActiveModel
#
# The above allows you to do:
#
- # p = Person.new
+ # person = Person.new
# person.validate! # => ["can not be nil"]
# person.errors.full_messages # => ["name can not be nil"]
# # etc..
@@ -75,14 +73,14 @@ module ActiveModel
@messages = {}
end
- def initialize_dup(other) #:nodoc:
+ def initialize_dup(other) # :nodoc:
@messages = other.messages.dup
super
end
# Clear the error messages.
#
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.full_messages # => ["name can not be nil"]
# person.errors.clear
# person.errors.full_messages # => []
def clear
@@ -92,9 +90,9 @@ module ActiveModel
# Returns +true+ if the error messages include an error for the given key
# +attribute+, +false+ otherwise.
#
- # person.errors.messages # => { :name => ["can not be nil"] }
+ # person.errors.messages # => {:name=>["can not be nil"]}
# person.errors.include?(:name) # => true
- # person.errors.include?(:age) # => false
+ # person.errors.include?(:age) # => false
def include?(attribute)
(v = messages[attribute]) && v.any?
end
@@ -103,7 +101,7 @@ module ActiveModel
# Get messages for +key+.
#
- # person.errors.messages # => { :name => ["can not be nil"] }
+ # person.errors.messages # => {:name=>["can not be nil"]}
# person.errors.get(:name) # => ["can not be nil"]
# person.errors.get(:age) # => nil
def get(key)
@@ -122,7 +120,7 @@ module ActiveModel
# Delete messages for +key+. Returns the deleted messages.
#
# person.errors.get(:name) # => ["can not be nil"]
- # person.errors.delete(:name) # => ["can not be nil"]
+ # person.errors.delete(:name) # => ["can not be nil"]
# person.errors.get(:name) # => nil
def delete(key)
messages.delete(key)
@@ -177,7 +175,7 @@ module ActiveModel
# Returns all message values.
#
- # person.errors.messages # => { :name => ["can not be nil", "must be specified"] }
+ # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
# person.errors.values # => [["can not be nil", "must be specified"]]
def values
messages.values
@@ -185,7 +183,7 @@ module ActiveModel
# Returns all message keys.
#
- # person.errors.messages # => { :name => ["can not be nil", "must be specified"] }
+ # person.errors.messages # => {:name=>["can not be nil", "must be specified"]}
# person.errors.keys # => [:name]
def keys
messages.keys
@@ -213,7 +211,7 @@ module ActiveModel
# Returns +true+ if no errors are found, +false+ otherwise.
# If the error message is a string it can be empty.
#
- # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.full_messages # => ["name can not be nil"]
# person.errors.empty? # => false
def empty?
all? { |k, v| v && v.empty? && !v.is_a?(String) }
@@ -233,24 +231,24 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
+ to_a.to_xml({ root: "errors", skip_types: true }.merge!(options))
end
# Returns a Hash that can be used as the JSON representation for this
# object. You can pass the <tt>:full_messages</tt> option. This determines
# if the json object should contain full messages or not (false by default).
#
- # person.as_json # => { :name => ["can not be nil"] }
- # person.as_json(full_messages: true) # => { :name => ["name can not be nil"] }
+ # person.errors.as_json # => {:name=>["can not be nil"]}
+ # person.errors.as_json(full_messages: true) # => {:name=>["name can not be nil"]}
def as_json(options=nil)
to_hash(options && options[:full_messages])
end
- # Returns a Hash of attributes with their error messages. If +full_messages+
+ # Returns a Hash of attributes with their error messages. If +full_messages+
# is +true+, it will contain full messages (see +full_message+).
#
- # person.to_hash # => { :name => ["can not be nil"] }
- # person.to_hash(true) # => { :name => ["name can not be nil"] }
+ # person.errors.to_hash # => {:name=>["can not be nil"]}
+ # person.errors.to_hash(true) # => {:name=>["name can not be nil"]}
def to_hash(full_messages = false)
if full_messages
messages = {}
@@ -273,7 +271,7 @@ module ActiveModel
# # => ["is invalid", "must be implemented"]
#
# person.errors.messages
- # # => { :name => ["must be implemented", "is invalid"] }
+ # # => {:name=>["must be implemented", "is invalid"]}
#
# If +message+ is a symbol, it will be translated using the appropriate
# scope (see +generate_message+).
@@ -286,12 +284,12 @@ module ActiveModel
# <tt>:strict</tt> option can also be set to any other exception.
#
# person.errors.add(:name, nil, strict: true)
- # # => ActiveModel::StrictValidationFailed: name is invalid
+ # # => ActiveModel::StrictValidationFailed: name is invalid
# person.errors.add(:name, nil, strict: NameIsInvalid)
- # # => NameIsInvalid: name is invalid
+ # # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
- def add(attribute, message = nil, options = {})
+ def add(attribute, message = :invalid, options = {})
message = normalize_message(attribute, message, options)
if exception = options[:strict]
exception = ActiveModel::StrictValidationFailed if exception == true
@@ -306,9 +304,9 @@ module ActiveModel
#
# person.errors.add_on_empty(:name)
# person.errors.messages
- # # => { :name => ["can't be empty"] }
+ # # => {:name=>["can't be empty"]}
def add_on_empty(attributes, options = {})
- [attributes].flatten.each do |attribute|
+ Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
is_empty = value.respond_to?(:empty?) ? value.empty? : false
add(attribute, :empty, options) if value.nil? || is_empty
@@ -320,9 +318,9 @@ module ActiveModel
#
# person.errors.add_on_blank(:name)
# person.errors.messages
- # # => { :name => ["can't be blank"] }
+ # # => {:name=>["can't be blank"]}
def add_on_blank(attributes, options = {})
- [attributes].flatten.each do |attribute|
+ Array(attributes).each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
add(attribute, :blank, options) if value.blank?
end
@@ -333,7 +331,7 @@ module ActiveModel
#
# person.errors.add :name, :blank
# person.errors.added? :name, :blank # => true
- def added?(attribute, message = nil, options = {})
+ def added?(attribute, message = :invalid, options = {})
message = normalize_message(attribute, message, options)
self[attribute].include? message
end
@@ -352,17 +350,31 @@ module ActiveModel
map { |attribute, message| full_message(attribute, message) }
end
+ # Returns all the full error messages for a given attribute in an array.
+ #
+ # class Person
+ # validates_presence_of :name, :email
+ # validates_length_of :name, in: 5..30
+ # end
+ #
+ # person = Person.create()
+ # person.errors.full_messages_for(:name)
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"]
+ def full_messages_for(attribute)
+ (get(attribute) || []).map { |message| full_message(attribute, message) }
+ end
+
# Returns a full message for a given attribute.
#
# person.errors.full_message(:name, 'is invalid') # => "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
attr_name = attribute.to_s.tr('.', '_').humanize
- attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ attr_name = @base.class.human_attribute_name(attribute, default: attr_name)
I18n.t(:"errors.format", {
- :default => "%{attribute} %{message}",
- :attribute => attr_name,
- :message => message
+ default: "%{attribute} %{message}",
+ attribute: attr_name,
+ message: message
})
end
@@ -414,10 +426,10 @@ module ActiveModel
value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil)
options = {
- :default => defaults,
- :model => @base.class.model_name.human,
- :attribute => @base.class.human_attribute_name(attribute),
- :value => value
+ default: defaults,
+ model: @base.class.model_name.human,
+ attribute: @base.class.human_attribute_name(attribute),
+ value: value
}.merge!(options)
I18n.translate(key, options)
@@ -425,8 +437,6 @@ module ActiveModel
private
def normalize_message(attribute, message, options)
- message ||= :invalid
-
case message
when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb
index 4c05b19cba..7468f95548 100644
--- a/activemodel/lib/active_model/forbidden_attributes_protection.rb
+++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb
@@ -16,7 +16,7 @@ module ActiveModel
module ForbiddenAttributesProtection # :nodoc:
protected
- def sanitize_for_mass_assignment(attributes, options = {})
+ def sanitize_for_mass_assignment(attributes)
if attributes.respond_to?(:permitted?) && !attributes.permitted?
raise ActiveModel::ForbiddenAttributesError
else
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 550fa474ea..46b446dc08 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -1,8 +1,8 @@
module ActiveModel
module Lint
- # == Active Model Lint Tests
+ # == Active \Model \Lint \Tests
#
- # You can test whether an object is compliant with the Active Model API by
+ # You can test whether an object is compliant with the Active \Model API by
# including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
# include tests that tell you whether your object is fully compliant,
# or if not, which aspects of the API are not implemented.
@@ -71,7 +71,7 @@ module ActiveModel
assert_boolean model.persisted?, "persisted?"
end
- # == Naming
+ # == \Naming
#
# Model.model_name must return a string with some convenience methods:
# <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. Check
@@ -85,7 +85,7 @@ module ActiveModel
assert model_name.plural.respond_to?(:to_str)
end
- # == Errors Testing
+ # == \Errors Testing
#
# Returns an object that implements [](attribute) defined which returns an
# Array of Strings that are the errors for the attribute in question.
@@ -98,7 +98,7 @@ module ActiveModel
private
def model
- assert @model.respond_to?(:to_model), "The object should respond_to to_model"
+ assert @model.respond_to?(:to_model), "The object should respond to to_model"
@model.to_model
end
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index d17848c861..540e8132d3 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -13,6 +13,7 @@ en:
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
+ present: "must be blank"
too_long: "is too long (maximum is %{count} characters)"
too_short: "is too short (minimum is %{count} characters)"
wrong_length: "is the wrong length (should be %{count} characters)"
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index 33a530e6bd..f048dda5c6 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -1,6 +1,6 @@
module ActiveModel
- # == Active Model Basic Model
+ # == Active \Model Basic \Model
#
# Includes the required interface for an object to interact with
# <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
@@ -79,6 +79,8 @@ module ActiveModel
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
+
+ super()
end
# Indicates if the model is persisted. Default is +false+.
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 4322592a7e..198efc5088 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,4 +1,3 @@
-require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
@@ -56,8 +55,8 @@ module ActiveModel
# end
#
# BlogPost.model_name <=> 'BlogPost' # => 0
- # BlogPost.model_name <=> 'Blog' # => 1
- # BlogPost.model_name <=> 'BlogPosts' # => -1
+ # BlogPost.model_name <=> 'Blog' # => 1
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
##
# :method: =~
@@ -130,7 +129,7 @@ module ActiveModel
#
# Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
- :to_str, :to => :name
+ :to_str, to: :name
# Returns a new ActiveModel::Name instance. By default, the +namespace+
# and +name+ option will take the namespace and name of the given class
@@ -184,7 +183,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
@@ -195,7 +194,7 @@ module ActiveModel
end
end
- # == Active Model Naming
+ # == Active \Model \Naming
#
# Creates a +model_name+ method on your object.
#
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
deleted file mode 100644
index 77bc0f71e3..0000000000
--- a/activemodel/lib/active_model/observer_array.rb
+++ /dev/null
@@ -1,152 +0,0 @@
-require 'set'
-
-module ActiveModel
- # Stores the enabled/disabled state of individual observers for
- # a particular model class.
- class ObserverArray < Array
- attr_reader :model_class
- def initialize(model_class, *args) #:nodoc:
- @model_class = model_class
- super(*args)
- end
-
- # Returns +true+ if the given observer is disabled for the model class,
- # +false+ otherwise.
- def disabled_for?(observer) #:nodoc:
- disabled_observers.include?(observer.class)
- end
-
- # Disables one or more observers. This supports multiple forms:
- #
- # ORM.observers.disable :all
- # # => disables all observers for all models subclassed from
- # # an ORM base class that includes ActiveModel::Observing
- # # e.g. ActiveRecord::Base
- #
- # ORM.observers.disable :user_observer
- # # => disables the UserObserver
- #
- # User.observers.disable AuditTrail
- # # => disables the AuditTrail observer for User notifications.
- # # Other models will still notify the AuditTrail observer.
- #
- # ORM.observers.disable :observer_1, :observer_2
- # # => disables Observer1 and Observer2 for all models.
- #
- # User.observers.disable :all do
- # # all user observers are disabled for
- # # just the duration of the block
- # end
- def disable(*observers, &block)
- set_enablement(false, observers, &block)
- end
-
- # Enables one or more observers. This supports multiple forms:
- #
- # ORM.observers.enable :all
- # # => enables all observers for all models subclassed from
- # # an ORM base class that includes ActiveModel::Observing
- # # e.g. ActiveRecord::Base
- #
- # ORM.observers.enable :user_observer
- # # => enables the UserObserver
- #
- # User.observers.enable AuditTrail
- # # => enables the AuditTrail observer for User notifications.
- # # Other models will not be affected (i.e. they will not
- # # trigger notifications to AuditTrail if previously disabled)
- #
- # ORM.observers.enable :observer_1, :observer_2
- # # => enables Observer1 and Observer2 for all models.
- #
- # User.observers.enable :all do
- # # all user observers are enabled for
- # # just the duration of the block
- # end
- #
- # Note: all observers are enabled by default. This method is only
- # useful when you have previously disabled one or more observers.
- def enable(*observers, &block)
- set_enablement(true, observers, &block)
- end
-
- protected
-
- def disabled_observers #:nodoc:
- @disabled_observers ||= Set.new
- end
-
- def observer_class_for(observer) #:nodoc:
- return observer if observer.is_a?(Class)
-
- if observer.respond_to?(:to_sym) # string/symbol
- observer.to_s.camelize.constantize
- else
- raise ArgumentError, "#{observer} was not a class or a " +
- "lowercase, underscored class name as expected."
- end
- end
-
- def start_transaction #:nodoc:
- disabled_observer_stack.push(disabled_observers.dup)
- each_subclass_array do |array|
- array.start_transaction
- end
- end
-
- def disabled_observer_stack #:nodoc:
- @disabled_observer_stack ||= []
- end
-
- def end_transaction #:nodoc:
- @disabled_observers = disabled_observer_stack.pop
- each_subclass_array do |array|
- array.end_transaction
- end
- end
-
- def transaction #:nodoc:
- start_transaction
-
- begin
- yield
- ensure
- end_transaction
- end
- end
-
- def each_subclass_array #:nodoc:
- model_class.descendants.each do |subclass|
- yield subclass.observers
- end
- end
-
- def set_enablement(enabled, observers) #:nodoc:
- if block_given?
- transaction do
- set_enablement(enabled, observers)
- yield
- end
- else
- observers = ActiveModel::Observer.descendants if observers == [:all]
- observers.each do |obs|
- klass = observer_class_for(obs)
-
- unless klass < ActiveModel::Observer
- raise ArgumentError.new("#{obs} does not refer to a valid observer")
- end
-
- if enabled
- disabled_observers.delete(klass)
- else
- disabled_observers << klass
- end
- end
-
- each_subclass_array do |array|
- array.set_enablement(enabled, observers)
- end
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
deleted file mode 100644
index 9db7639ea3..0000000000
--- a/activemodel/lib/active_model/observing.rb
+++ /dev/null
@@ -1,374 +0,0 @@
-require 'singleton'
-require 'active_model/observer_array'
-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/core_ext/object/try'
-require 'active_support/descendants_tracker'
-
-module ActiveModel
- # == Active Model Observers Activation
- module Observing
- extend ActiveSupport::Concern
-
- included do
- extend ActiveSupport::DescendantsTracker
- end
-
- module ClassMethods
- # Activates the observers assigned.
- #
- # class ORM
- # include ActiveModel::Observing
- # end
- #
- # # Calls PersonObserver.instance
- # ORM.observers = :person_observer
- #
- # # Calls Cacher.instance and GarbageCollector.instance
- # ORM.observers = :cacher, :garbage_collector
- #
- # # Same as above, just using explicit class references
- # ORM.observers = Cacher, GarbageCollector
- #
- # Note: Setting this does not instantiate the observers yet.
- # <tt>instantiate_observers</tt> is called during startup, and before
- # each development request.
- def observers=(*values)
- observers.replace(values.flatten)
- end
-
- # Gets an array of observers observing this model. The array also provides
- # +enable+ and +disable+ methods that allow you to selectively enable and
- # disable observers (see ActiveModel::ObserverArray.enable and
- # ActiveModel::ObserverArray.disable for more on this).
- #
- # class ORM
- # include ActiveModel::Observing
- # end
- #
- # ORM.observers = :cacher, :garbage_collector
- # ORM.observers # => [:cacher, :garbage_collector]
- # ORM.observers.class # => ActiveModel::ObserverArray
- def observers
- @observers ||= ObserverArray.new(self)
- end
-
- # Returns the current observer instances.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.instantiate_observers
- #
- # Foo.observer_instances # => [#<FooObserver:0x007fc212c40820>]
- def observer_instances
- @observer_instances ||= []
- end
-
- # Instantiate the global observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => false
- #
- # Foo.instantiate_observers # => [FooObserver]
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => true
- def instantiate_observers
- observers.each { |o| instantiate_observer(o) }
- end
-
- # Add a new observer to the pool. The new observer needs to respond to
- # <tt>update</tt>, otherwise it raises an +ArgumentError+ exception.
- #
- # class Foo
- # include ActiveModel::Observing
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # end
- #
- # Foo.add_observer(FooObserver.instance)
- #
- # Foo.observers_instance
- # # => [#<FooObserver:0x007fccf55d9390>]
- def add_observer(observer)
- unless observer.respond_to? :update
- raise ArgumentError, "observer needs to respond to 'update'"
- end
- observer_instances << observer
- end
-
- # Fires notifications to model's observers.
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- #
- # Custom notifications can be sent in a similar fashion:
- #
- # notify_observers(:custom_notification, :foo)
- #
- # This will call <tt>custom_notification</tt>, passing as arguments
- # the current object and <tt>:foo</tt>.
- def notify_observers(*args)
- observer_instances.each { |observer| observer.update(*args) }
- end
-
- # Returns the total number of instantiated observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.observers_count # => 0
- # Foo.instantiate_observers
- # Foo.observers_count # => 1
- def observers_count
- observer_instances.size
- end
-
- # <tt>count_observers</tt> is deprecated. Use #observers_count.
- 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 = 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 " +
- "the class itself) responding to the method :instance. " +
- "Example: Person.observers = :big_brother # calls " +
- "BigBrother.instance"
- end
- end
-
- # Notify observers when the observed class is subclassed.
- def inherited(subclass) #:nodoc:
- super
- notify_observers :observed_class_inherited, subclass
- end
- end
-
- # Notify a change to the list of observers.
- #
- # class Foo
- # include ActiveModel::Observing
- #
- # attr_accessor :status
- # end
- #
- # class FooObserver < ActiveModel::Observer
- # def on_spec(record, *args)
- # record.status = true
- # end
- # end
- #
- # Foo.observers = FooObserver
- # Foo.instantiate_observers # => [FooObserver]
- #
- # foo = Foo.new
- # foo.status = false
- # foo.notify_observers(:on_spec)
- # foo.status # => true
- #
- # See ActiveModel::Observing::ClassMethods.notify_observers for more
- # information.
- def notify_observers(method, *extra_args)
- self.class.notify_observers(method, self, *extra_args)
- end
- end
-
- # == Active Model Observers
- #
- # Observer classes respond to life cycle callbacks to implement trigger-like
- # behavior outside the original class. This is a great way to reduce the
- # clutter that normally comes when the model class is burdened with
- # functionality that doesn't pertain to the core responsibility of the
- # class.
- #
- # class CommentObserver < ActiveModel::Observer
- # def after_save(comment)
- # Notifications.comment('admin@do.com', 'New comment was posted', comment).deliver
- # end
- # end
- #
- # This Observer sends an email when a <tt>Comment#save</tt> is finished.
- #
- # class ContactObserver < ActiveModel::Observer
- # def after_create(contact)
- # contact.logger.info('New contact added!')
- # end
- #
- # def after_destroy(contact)
- # contact.logger.warn("Contact with an id of #{contact.id} was destroyed!")
- # end
- # end
- #
- # This Observer uses logger to log when specific callbacks are triggered.
- #
- # == Observing a class that can't be inferred
- #
- # Observers will by default be mapped to the class with which they share a
- # name. So <tt>CommentObserver</tt> will be tied to observing <tt>Comment</tt>,
- # <tt>ProductManagerObserver</tt> to <tt>ProductManager</tt>, and so on. If
- # you want to name your observer differently than the class you're interested
- # in observing, you can use the <tt>Observer.observe</tt> class method which
- # takes either the concrete class (<tt>Product</tt>) or a symbol for that
- # class (<tt>:product</tt>):
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account
- #
- # def after_update(account)
- # AuditTrail.new(account, 'UPDATED')
- # end
- # end
- #
- # If the audit observer needs to watch more than one kind of object, this can
- # be specified with multiple arguments:
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account, :balance
- #
- # def after_update(record)
- # AuditTrail.new(record, 'UPDATED')
- # end
- # end
- #
- # The <tt>AuditObserver</tt> will now act on both updates to <tt>Account</tt>
- # and <tt>Balance</tt> by treating them both as records.
- #
- # If you're using an Observer in a Rails application with Active Record, be
- # sure to read about the necessary configuration in the documentation for
- # ActiveRecord::Observer.
- class Observer
- include Singleton
- extend ActiveSupport::DescendantsTracker
-
- class << self
- # Attaches the observer to the supplied model classes.
- #
- # class AuditObserver < ActiveModel::Observer
- # observe :account, :balance
- # end
- #
- # AuditObserver.observed_classes # => [Account, Balance]
- def observe(*models)
- models.flatten!
- models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- singleton_class.redefine_method(:observed_classes) { models }
- end
-
- # Returns an array of Classes to observe.
- #
- # AccountObserver.observed_classes # => [Account]
- #
- # You can override this instead of using the +observe+ helper.
- #
- # class AuditObserver < ActiveModel::Observer
- # def self.observed_classes
- # [Account, Balance]
- # end
- # end
- def observed_classes
- Array(observed_class)
- end
-
- # Returns the class observed by default. It's inferred from the observer's
- # class name.
- #
- # PersonObserver.observed_class # => Person
- # AccountObserver.observed_class # => Account
- def observed_class
- name[/(.*)Observer/, 1].try :constantize
- end
- end
-
- # Start observing the declared classes and their subclasses.
- # Called automatically by the instance method.
- def initialize #:nodoc:
- observed_classes.each { |klass| add_observer!(klass) }
- end
-
- def observed_classes #:nodoc:
- self.class.observed_classes
- end
-
- # Send observed_method(object) if the method exists and
- # the observer is enabled for the given object's class.
- def update(observed_method, object, *extra_args, &block) #:nodoc:
- return if !respond_to?(observed_method) || disabled_for?(object)
- send(observed_method, object, *extra_args, &block)
- end
-
- # Special method sent by the observed class when it is inherited.
- # Passes the new subclass.
- def observed_class_inherited(subclass) #:nodoc:
- self.class.observe(observed_classes + [subclass])
- add_observer!(subclass)
- end
-
- protected
- def add_observer!(klass) #:nodoc:
- klass.add_observer(self)
- end
-
- # Returns true if notifications are disabled for this object.
- def disabled_for?(object) #:nodoc:
- klass = object.class
- return false unless klass.respond_to?(:observers)
- klass.observers.disabled_for?(self)
- end
- end
-end
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
index f239758b35..1671eb7bd4 100644
--- a/activemodel/lib/active_model/railtie.rb
+++ b/activemodel/lib/active_model/railtie.rb
@@ -2,7 +2,11 @@ require "active_model"
require "rails"
module ActiveModel
- class Railtie < Rails::Railtie
+ class Railtie < Rails::Railtie # :nodoc:
config.eager_load_namespaces << ActiveModel
+
+ initializer "active_model.secure_password" do
+ ActiveModel::SecurePassword.min_cost = Rails.env.test?
+ end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index d011402081..7e694b5c50 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -2,6 +2,11 @@ module ActiveModel
module SecurePassword
extend ActiveSupport::Concern
+ class << self
+ attr_accessor :min_cost # :nodoc:
+ end
+ self.min_cost = false
+
module ClassMethods
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a password_digest attribute.
@@ -11,9 +16,13 @@ module ActiveModel
# you wish to turn off validations, pass <tt>validations: false</tt> as an
# argument. You can add more validations by hand if need be.
#
- # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password:
+ # If you don't need the confirmation validation, just don't set any
+ # value to the password_confirmation attribute and the validation
+ # will not be triggered.
#
- # gem 'bcrypt-ruby', '~> 3.0.0'
+ # You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
+ #
+ # gem 'bcrypt-ruby', '~> 3.1.2'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
@@ -23,33 +32,38 @@ module ActiveModel
# end
#
# user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
- # user.save # => false, password required
+ # user.save # => false, password required
# user.password = 'mUc3m00RsqyRe'
- # user.save # => false, confirmation doesn't match
+ # user.save # => false, confirmation doesn't match
# user.password_confirmation = 'mUc3m00RsqyRe'
- # user.save # => true
- # user.authenticate('notright') # => false
- # user.authenticate('mUc3m00RsqyRe') # => user
- # User.find_by_name('david').try(:authenticate, 'notright') # => false
- # User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user
+ # user.save # => true
+ # user.authenticate('notright') # => false
+ # user.authenticate('mUc3m00RsqyRe') # => user
+ # User.find_by(name: 'david').try(:authenticate, 'notright') # => false
+ # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user
def has_secure_password(options = {})
# Load bcrypt-ruby only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
- gem 'bcrypt-ruby', '~> 3.0.0'
- require 'bcrypt'
+ begin
+ require 'bcrypt'
+ rescue LoadError
+ $stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
+ raise
+ end
attr_reader :password
+ include InstanceMethodsOnActivation
+
if options.fetch(:validations, true)
- validates_confirmation_of :password
- validates_presence_of :password, :on => :create
-
+ validates_confirmation_of :password, if: :should_confirm_password?
+ validates_presence_of :password, on: :create
+ validates_presence_of :password_confirmation, if: :should_confirm_password?
+
before_create { raise "Password digest missing on new record" if password_digest.blank? }
end
- include InstanceMethodsOnActivation
-
if respond_to?(:attributes_protected_by_default)
def self.attributes_protected_by_default #:nodoc:
super + ['password_digest']
@@ -68,7 +82,7 @@ module ActiveModel
# user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
# user.save
# user.authenticate('notright') # => false
- #  user.authenticate('mUc3m00RsqyRe') # => user
+ # user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
BCrypt::Password.new(password_digest) == unencrypted_password && self
end
@@ -84,13 +98,24 @@ module ActiveModel
# user.password = nil
# user.password_digest # => nil
# user.password = 'mUc3m00RsqyRe'
- # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
+ # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
- self.password_digest = BCrypt::Password.create(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
+
+ private
+
+ def should_confirm_password?
+ password_confirmation && password.present?
+ end
end
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 8a63014ffb..fdb06aebb9 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -2,7 +2,7 @@ require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
module ActiveModel
- # == Active Model Serialization
+ # == Active \Model \Serialization
#
# Provides a basic serialization to a serializable_hash for your object.
#
@@ -90,7 +90,7 @@ module ActiveModel
# person.name = 'bob'
# person.age = 22
# person.serializable_hash # => {"name"=>"bob", "age"=>22}
- # person.serializable_hash(only: :name) # => {"name"=>"bob"}
+ # person.serializable_hash(only: :name) # => {"name"=>"bob"}
# person.serializable_hash(except: :name) # => {"age"=>22}
# person.serializable_hash(methods: :capitalized_name)
# # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index a4252b995d..05e2e089e5 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -1,8 +1,8 @@
require 'active_support/json'
module ActiveModel
- # == Active Model JSON Serializer
module Serializers
+ # == Active Model JSON Serializer
module JSON
extend ActiveSupport::Concern
include ActiveModel::Serialization
@@ -109,7 +109,7 @@ module ActiveModel
#
# def attributes=(hash)
# hash.each do |key, value|
- # instance_variable_set("@#{key}", value)
+ # send("#{key}=", value)
# end
# end
#
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index cf742d0569..2864c2ba11 100755..100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -2,21 +2,30 @@ require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/hash/slice'
+require 'active_support/core_ext/time/acts_like'
module ActiveModel
- # == Active Model XML Serializer
module Serializers
+ # == Active Model XML Serializer
module Xml
extend ActiveSupport::Concern
include ActiveModel::Serialization
+ included do
+ extend ActiveModel::Naming
+ end
+
class Serializer #:nodoc:
class Attribute #:nodoc:
attr_reader :name, :value, :type
def initialize(name, serializable, value)
@name, @serializable = name, serializable
- value = value.in_time_zone if value.respond_to?(:in_time_zone)
+
+ if value.acts_like?(:time) && value.respond_to?(:in_time_zone)
+ value = value.in_time_zone
+ end
+
@value = value
@type = compute_type
end
@@ -70,7 +79,7 @@ module ActiveModel
require 'builder' unless defined? ::Builder
options[:indent] ||= 2
- options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent])
+ options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent])
@builder = options[:builder]
@builder.instruct! unless options[:skip_instruct]
@@ -79,8 +88,8 @@ module ActiveModel
root = ActiveSupport::XmlMini.rename_key(root, options)
args = [root]
- args << {:xmlns => options[:namespace]} if options[:namespace]
- args << {:type => options[:type]} if options[:type] && !options[:skip_types]
+ args << { xmlns: options[:namespace] } if options[:namespace]
+ args << { type: options[:type] } if options[:type] && !options[:skip_types]
@builder.tag!(*args) do
add_attributes_and_methods
@@ -123,7 +132,7 @@ module ActiveModel
records = records.to_ary
tag = ActiveSupport::XmlMini.rename_key(association.to_s, options)
- type = options[:skip_types] ? { } : {:type => "array"}
+ type = options[:skip_types] ? { } : { type: "array" }
association_name = association.to_s.singularize
merged_options[:root] = association_name
@@ -136,7 +145,7 @@ module ActiveModel
record_type = {}
else
record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name
- record_type = {:type => record_class}
+ record_type = { type: record_class }
end
record.to_xml merged_options.merge(record_type)
@@ -145,7 +154,12 @@ module ActiveModel
end
else
merged_options[:root] = association.to_s
- records.to_xml(merged_options)
+
+ unless records.class.to_s.underscore == association.to_s
+ merged_options[:type] = records.class.name
+ end
+
+ records.to_xml merged_options
end
end
@@ -191,7 +205,7 @@ module ActiveModel
Serializer.new(self, options).serialize(&block)
end
- # Sets the model +attributes+ from a JSON string. Returns +self+.
+ # Sets the model +attributes+ from an XML string. Returns +self+.
#
# class Person
# include ActiveModel::Serializers::Xml
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 7a86701f73..8470915abb 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -1,6 +1,6 @@
module ActiveModel
- # == Active Model Translation
+ # == Active \Model \Translation
#
# Provides integration between your object and the Rails internationalization
# (i18n) framework.
@@ -41,7 +41,7 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- options = { :count => 1 }.merge!(options)
+ options = { count: 1 }.merge!(options)
parts = attribute.to_s.split(".")
attribute = parts.pop
namespace = parts.join("/") unless parts.empty?
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 243d911f71..31c2245265 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,12 +1,10 @@
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'
-require 'active_model/errors'
-require 'active_model/validations/callbacks'
module ActiveModel
- # == Active Model Validations
+ # == Active \Model Validations
#
# Provides a full validation framework to your objects.
#
@@ -32,7 +30,7 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors.messages # => {:first_name=>["starts with z."]}
+ # person.errors.messages # => {first_name:["starts with z."]}
#
# Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+
# method to your instances initialized with a new <tt>ActiveModel::Errors</tt>
@@ -48,7 +46,7 @@ module ActiveModel
include HelperMethods
attr_accessor :validation_context
- define_callbacks :validate, :scope => :name
+ define_callbacks :validate, scope: :name
class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
@@ -144,7 +142,9 @@ module ActiveModel
if options.key?(:on)
options = options.dup
options[:if] = Array(options[:if])
- options[:if].unshift("validation_context == :#{options[:on]}")
+ options[:if].unshift lambda { |o|
+ o.validation_context == options[:on]
+ }
end
args << options
set_callback(:validate, *args, &block)
@@ -164,13 +164,56 @@ module ActiveModel
# Person.validators
# # => [
# # #<MyValidator:0x007fbff403e808 @options={}>,
- # # #<OtherValidator:0x007fbff403d930 @options={:on=>:create}>,
- # # #<StrictValidator:0x007fbff3204a30 @options={:strict=>true}>
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
def validators
_validators.values.flatten.uniq
end
+ # Clears all of the validators and validations.
+ #
+ # Note that this will clear anything that is being used to validate
+ # the model for both the +validates_with+ and +validate+ methods.
+ # It clears the validators that are created with an invocation of
+ # +validates_with+ and the callbacks that are set by an invocation
+ # of +validate+.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # validates_with MyValidator
+ # validates_with OtherValidator, on: :create
+ # validates_with StrictValidator, strict: true
+ # validate :cannot_be_robot
+ #
+ # def cannot_be_robot
+ # errors.add(:base, 'A person cannot be a robot') if person_is_robot
+ # end
+ # end
+ #
+ # Person.validators
+ # # => [
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
+ # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>,
+ # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
+ # # ]
+ #
+ # If one runs Person.clear_validators! and then checks to see what
+ # validators this class has, you would obtain:
+ #
+ # Person.validators # => []
+ #
+ # Also, the callback set by +validate :cannot_be_robot+ will be erased
+ # so that:
+ #
+ # Person._validate_callbacks.empty? # => true
+ #
+ def clear_validators!
+ reset_callbacks(:validate)
+ _validators.clear
+ end
+
# List all validators that are being used to validate a specific attribute.
#
# class Person
@@ -185,7 +228,6 @@ module ActiveModel
# Person.validators_on(:name)
# # => [
# # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>,
- # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={:in=>0..99}>
# # ]
def validators_on(*attributes)
attributes.flat_map do |attribute|
@@ -233,7 +275,7 @@ module ActiveModel
#
# person = Person.new
# person.valid? # => false
- # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={:name=>["can't be blank"]}>
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={name:["can't be blank"]}>
def errors
@errors ||= Errors.new(self)
end
@@ -250,7 +292,7 @@ module ActiveModel
#
# person = Person.new
# person.name = ''
- # person.valid? # => false
+ # person.valid? # => false
# person.name = 'david'
# person.valid? # => true
#
@@ -265,7 +307,7 @@ module ActiveModel
# end
#
# person = Person.new
- # person.valid? # => true
+ # person.valid? # => true
# person.valid?(:new) # => false
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
@@ -287,7 +329,7 @@ module ActiveModel
#
# person = Person.new
# person.name = ''
- # person.invalid? # => true
+ # person.invalid? # => true
# person.name = 'david'
# person.invalid? # => false
#
@@ -302,7 +344,7 @@ module ActiveModel
# end
#
# person = Person.new
- # person.invalid? # => false
+ # person.invalid? # => false
# person.invalid?(:new) # => true
def invalid?(context = nil)
!valid?(context)
@@ -335,7 +377,4 @@ module ActiveModel
end
end
-Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path|
- filename = File.basename(path)
- require "active_model/validations/#{filename}"
-end
+Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file }
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
new file mode 100644
index 0000000000..1a1863370b
--- /dev/null
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -0,0 +1,31 @@
+module ActiveModel
+ module Validations
+ # == Active Model Absence Validator
+ class AbsenceValidator < EachValidator #:nodoc:
+ def validate_each(record, attr_name, value)
+ record.errors.add(attr_name, :present, options) if value.present?
+ end
+ end
+
+ module HelperMethods
+ # Validates that the specified attributes are blank (as defined by
+ # Object#blank?). Happens by default on save.
+ #
+ # class Person < ActiveRecord::Base
+ # validates_absence_of :first_name
+ # end
+ #
+ # The first_name attribute must be in the object and it must be blank.
+ #
+ # Configuration options:
+ # * <tt>:message</tt> - A custom error message (default is: "must be blank").
+ #
+ # There is also a list of default options supported by every validator:
+ # +:if+, +:unless+, +:on+ and +:strict+.
+ # See <tt>ActiveModel::Validation#validates</tt> for more information
+ def validates_absence_of(*attr_names)
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 8d5ebf527f..139de16326 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -1,10 +1,10 @@
module ActiveModel
- # == Active Model Acceptance Validator
module Validations
- class AcceptanceValidator < EachValidator #:nodoc:
+ class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
- super({ :allow_nil => true, :accept => "1" }.merge!(options))
+ super({ allow_nil: true, accept: "1" }.merge!(options))
+ setup!(options[:class])
end
def validate_each(record, attribute, value)
@@ -13,7 +13,8 @@ module ActiveModel
end
end
- def setup(klass)
+ private
+ def setup!(klass)
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
klass.send(:attr_reader, *attr_readers)
@@ -27,7 +28,7 @@ module ActiveModel
#
# class Person < ActiveRecord::Base
# validates_acceptance_of :terms_of_service
- # validates_acceptance_of :eula, message: "must be abided"
+ # validates_acceptance_of :eula, message: 'must be abided'
# end
#
# If the database column does not exist, the +terms_of_service+ attribute
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index c153ef4309..fde53b9f89 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -1,8 +1,6 @@
-require 'active_support/callbacks'
-
module ActiveModel
module Validations
- # == Active Model Validation callbacks
+ # == Active \Model Validation Callbacks
#
# Provides an interface for any class to have +before_validation+ and
# +after_validation+ callbacks.
@@ -24,7 +22,10 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
- define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
+ define_callbacks :validation,
+ terminator: ->(_,result) { result == false },
+ skip_after_callbacks_if_terminated: true,
+ scope: [:kind, :name]
end
module ClassMethods
@@ -57,7 +58,9 @@ module ActiveModel
if options.is_a?(Hash) && options[:on]
options[:if] = Array(options[:if])
options[:on] = Array(options[:on])
- options[:if].unshift("#{options[:on]}.include? self.validation_context")
+ options[:if].unshift lambda { |o|
+ options[:on].include? o.validation_context
+ }
end
set_callback(:validation, :before, *args, &block)
end
@@ -85,8 +88,8 @@ module ActiveModel
# person = Person.new
# person.name = ''
# person.valid? # => false
- # person.status # => false
- #  person.name = 'bob'
+ # person.status # => false
+ # person.name = 'bob'
# person.valid? # => true
# person.status # => true
def after_validation(*args, &block)
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index 3d7067fbcb..fd6cc1edb4 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/range'
module ActiveModel
module Validations
module Clusivity #:nodoc:
- ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " <<
+ ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
"and must be supplied as the :in (or :within) option of the configuration hash"
def check_validity!
@@ -15,26 +15,33 @@ module ActiveModel
private
def include?(record, value)
- exclusions = if delimiter.respond_to?(:call)
- delimiter.call(record)
- elsif delimiter.respond_to?(:to_sym)
- record.send(delimiter)
- else
- delimiter
- end
+ members = if delimiter.respond_to?(:call)
+ delimiter.call(record)
+ elsif delimiter.respond_to?(:to_sym)
+ record.send(delimiter)
+ else
+ delimiter
+ end
- exclusions.send(inclusion_method(exclusions), value)
+ members.send(inclusion_method(members), value)
end
def delimiter
@delimiter ||= options[:in] || options[:within]
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.
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
+ # possible values in the range for equality, which is slower but more accurate.
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
+ # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
def inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
+ return :include? unless enumerable.is_a?(Range)
+ case enumerable.first
+ when Numeric, Time, DateTime
+ :cover?
+ else
+ :include?
+ end
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index baa034eca6..b0542661af 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -1,19 +1,28 @@
module ActiveModel
- # == Active Model Confirmation Validator
module Validations
- class ConfirmationValidator < EachValidator #:nodoc:
+ class ConfirmationValidator < EachValidator # :nodoc:
+ def initialize(options)
+ super
+ setup!(options[:class])
+ end
+
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
- record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name))
end
end
- def setup(klass)
- klass.send(:attr_accessor, *attributes.map do |attribute|
+ private
+ def setup!(klass)
+ klass.send(:attr_reader, *attributes.map do |attribute|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
end.compact)
+
+ klass.send(:attr_writer, *attributes.map do |attribute|
+ :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
+ end.compact)
end
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 3ec552c372..48bf5cd802 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -2,14 +2,13 @@ require "active_model/validations/clusivity"
module ActiveModel
- # == Active Model Exclusion Validator
module Validations
- class ExclusionValidator < EachValidator #:nodoc:
+ class ExclusionValidator < EachValidator # :nodoc:
include Clusivity
def validate_each(record, attribute, value)
if include?(record, value)
- record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value))
+ record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value))
end
end
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index 80150229a0..be7cae588f 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -1,8 +1,7 @@
module ActiveModel
- # == Active Model Format Validator
module Validations
- class FormatValidator < EachValidator #:nodoc:
+ class FormatValidator < EachValidator # :nodoc:
def validate_each(record, attribute, value)
if options[:with]
regexp = option_call(record, :with)
@@ -30,7 +29,7 @@ module ActiveModel
end
def record_error(record, attribute, name, value)
- record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
+ record.errors.add(attribute, :invalid, options.except(name).merge!(value: value))
end
def regexp_using_multiline_anchors?(regexp)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index babc8982da..24337614c5 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -2,14 +2,13 @@ require "active_model/validations/clusivity"
module ActiveModel
- # == Active Model Inclusion Validator
module Validations
- class InclusionValidator < EachValidator #:nodoc:
+ class InclusionValidator < EachValidator # :nodoc:
include Clusivity
def validate_each(record, attribute, value)
unless include?(record, value)
- record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value))
+ record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value))
end
end
end
@@ -29,7 +28,7 @@ module ActiveModel
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
# supplied as a proc, lambda or symbol which returns an enumerable. If the
- # enumerable is a range the test is performed with <tt>Range#cover?</tt>,
+ # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>,
# otherwise with <tt>include?</tt>.
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index e4a1f9e80a..ddfd8a342e 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -1,10 +1,10 @@
module ActiveModel
- # == Active Model Length Validator
+ # == Active \Model Length \Validator
module Validations
- class LengthValidator < EachValidator #:nodoc:
- MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
- CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
+ class LengthValidator < EachValidator # :nodoc:
+ MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
+ CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
@@ -14,6 +14,10 @@ module ActiveModel
options[:minimum], options[:maximum] = range.min, range.max
end
+ if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
+ options[:minimum] = 1
+ end
+
super
end
@@ -37,10 +41,13 @@ module ActiveModel
value = tokenize(value)
value_length = value.respond_to?(:length) ? value.length : value.to_s.length
errors_options = options.except(*RESERVED_OPTIONS)
-
+
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
- next if value_length.send(validity_check, check_value)
+
+ if !value.nil? || skip_nil_check?(key)
+ next if value_length.send(validity_check, check_value)
+ end
errors_options[:count] = check_value
@@ -58,6 +65,10 @@ module ActiveModel
options[:tokenizer].call(value)
end || value
end
+
+ def skip_nil_check?(key)
+ key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
+ end
end
module HelperMethods
@@ -79,7 +90,8 @@ module ActiveModel
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute.
- # * <tt>:maximum</tt> - The maximum size of the attribute.
+ # * <tt>:maximum</tt> - The maximum size of the attribute. Allows +nil+ by
+ # default if not used with :minimum.
# * <tt>:is</tt> - The exact size of the attribute.
# * <tt>:within</tt> - A range specifying the minimum and maximum size of
# the attribute.
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index edebca94a8..c6abe45f4a 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -1,11 +1,10 @@
module ActiveModel
- # == Active Model Numericality Validator
module Validations
- class NumericalityValidator < EachValidator #:nodoc:
- CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
- :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
- :odd => :odd?, :even => :even?, :other_than => :!= }.freeze
+ class NumericalityValidator < EachValidator # :nodoc:
+ CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
+ equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
+ odd: :odd?, even: :even?, other_than: :!= }.freeze
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
@@ -18,9 +17,9 @@ module ActiveModel
end
def validate_each(record, attr_name, value)
- before_type_cast = "#{attr_name}_before_type_cast"
+ before_type_cast = :"#{attr_name}_before_type_cast"
- raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast.to_sym)
+ raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
raw_value ||= value
return if options[:allow_nil] && raw_value.nil?
@@ -48,7 +47,7 @@ module ActiveModel
option_value = record.send(option_value) if option_value.is_a?(Symbol)
unless value.send(CHECKS[option], option_value)
- record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value))
+ record.errors.add(attr_name, option, filtered_options(value).merge(count: option_value))
end
end
end
@@ -74,7 +73,7 @@ module ActiveModel
end
def filtered_options(value)
- options.except(*RESERVED_OPTIONS).merge!(:value => value)
+ options.except(*RESERVED_OPTIONS).merge!(value: value)
end
end
@@ -126,7 +125,7 @@ module ActiveModel
# For example:
#
# class Person < ActiveRecord::Base
- # validates_numericality_of :width, less_than: Proc.new { |person| person.height }
+ # validates_numericality_of :width, less_than: ->(person) { person.height }
# validates_numericality_of :width, greater_than: :minimum_weight
# end
def validates_numericality_of(*attr_names)
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index f159e40858..ab8c8359fc 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,11 +1,10 @@
module ActiveModel
- # == Active Model Presence Validator
module Validations
- class PresenceValidator < EachValidator #:nodoc:
- def validate(record)
- record.errors.add_on_blank(attributes, options)
+ class PresenceValidator < EachValidator # :nodoc:
+ def validate_each(record, attr_name, value)
+ record.errors.add(attr_name, :blank, options) if value.blank?
end
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 03046a543a..9a1ff2ad39 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
module ActiveModel
- # == Active Model validates method
module Validations
module ClassMethods
# This method is a shortcut to all default validators and any custom
@@ -105,7 +104,7 @@ module ActiveModel
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
raise ArgumentError, "You need to supply at least one validation" if validations.empty?
- defaults.merge!(:attributes => attributes)
+ defaults[:attributes] = attributes
validations.each do |key, options|
next unless options
@@ -129,15 +128,15 @@ module ActiveModel
# the validation itself.
#
# class Person
- #  include ActiveModel::Validations
+ # include ActiveModel::Validations
#
# attr_accessor :name
# validates! :name, presence: true
# end
#
# person = Person.new
- #  person.name = ''
- #  person.valid?
+ # person.name = ''
+ # person.valid?
# # => ActiveModel::StrictValidationFailed: Name can't be blank
def validates!(*attributes)
options = attributes.extract_options!
@@ -149,20 +148,20 @@ module ActiveModel
# When creating custom validators, it might be useful to be able to specify
# additional default keys. This can be done by overwriting this method.
- def _validates_default_keys #:nodoc:
+ def _validates_default_keys # :nodoc:
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
- def _parse_validates_options(options) #:nodoc:
+ def _parse_validates_options(options) # :nodoc:
case options
when TrueClass
{}
when Hash
options
when Range, Array
- { :in => options }
+ { in: options }
else
- { :with => options }
+ { with: options }
end
end
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 869591cd9e..16bd6670d1 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -10,7 +10,7 @@ module ActiveModel
end
end
- class WithValidator < EachValidator #:nodoc:
+ class WithValidator < EachValidator # :nodoc:
def validate_each(record, attr, val)
method_name = options[:with]
@@ -83,9 +83,10 @@ module ActiveModel
# end
def validates_with(*args, &block)
options = args.extract_options!
+ options[:class] = self
+
args.each do |klass|
validator = klass.new(options, &block)
- validator.setup(self) if validator.respond_to?(:setup)
if validator.respond_to?(:attributes) && !validator.attributes.empty?
validator.attributes.each do |attribute|
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 85aec00f25..690856aee1 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -2,7 +2,7 @@ require "active_support/core_ext/module/anonymous"
module ActiveModel
- # == Active Model Validator
+ # == Active \Model \Validator
#
# A simple base class that can be used along with
# ActiveModel::Validations::ClassMethods.validates_with
@@ -60,6 +60,9 @@ module ActiveModel
# end
# end
#
+ # Note that the validator is initialized only once for the whole application
+ # lifecycle, and not on each validation run.
+ #
# The easiest way to add custom validators for validating individual attributes
# is with the convenient <tt>ActiveModel::EachValidator</tt>.
#
@@ -76,21 +79,19 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :title
#
- # validates :title, :presence => true
+ # validates :title, presence: true
# end
#
- # Validator may also define a +setup+ instance method which will get called
- # with the class that using that validator as its argument. This can be
- # useful when there are prerequisites such as an +attr_accessor+ being present.
+ # It can be useful to access the class that is using that validator when there are prerequisites such
+ # as an +attr_accessor+ being present. This class is accessable via +options[:class]+ in the constructor.
+ # To setup your validator override the constructor.
#
# class MyValidator < ActiveModel::Validator
- # def setup(klass)
- # klass.send :attr_accessor, :custom_attribute
+ # def initialize(options={})
+ # super
+ # options[:class].send :attr_accessor, :custom_attribute
# end
# end
- #
- # This setup method is only called when used with validation macros or the
- # class level <tt>validates_with</tt> method.
class Validator
attr_reader :options
@@ -103,14 +104,15 @@ module ActiveModel
end
# Accepts options that will be made available through the +options+ reader.
- def initialize(options)
- @options = options.freeze
+ def initialize(options = {})
+ @options = options.except(:class).freeze
+ deprecated_setup(options)
end
# Return the kind for this validator.
#
- # PresenceValidator.new.kind # => :presence
- # UniquenessValidator.new.kind # => :uniqueness 
+ # PresenceValidator.new.kind # => :presence
+ # UniquenessValidator.new.kind # => :uniqueness
def kind
self.class.kind
end
@@ -120,6 +122,21 @@ module ActiveModel
def validate(record)
raise NotImplementedError, "Subclasses must implement a validate(record) method."
end
+
+ private
+ def deprecated_setup(options) # TODO: remove me in 4.2.
+ return unless respond_to?(:setup)
+ ActiveSupport::Deprecation.warn "The `Validator#setup` instance method is deprecated and will be removed on Rails 4.2. Do your setup in the constructor instead:
+
+class MyValidator < ActiveModel::Validator
+ def initialize(options={})
+ super
+ options[:class].send :attr_accessor, :custom_attribute
+ end
+end
+"
+ setup(options[:class])
+ end
end
# +EachValidator+ is a validator which iterates through the attributes given
@@ -135,7 +152,7 @@ module ActiveModel
# and instead be made available through the +attributes+ reader.
def initialize(options)
@attributes = Array(options.delete(:attributes))
- raise ":attributes cannot be blank" if @attributes.empty?
+ raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
end
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index e195c12a4d..86340bba37 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,10 +1,11 @@
module ActiveModel
- module VERSION #:nodoc:
- MAJOR = 4
- MINOR = 0
- TINY = 0
- PRE = "beta"
+ # Returns the version of the currently loaded ActiveModel as a Gem::Version
+ def self.version
+ Gem::Version.new "4.1.0.beta"
+ end
- STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
+ module VERSION #:nodoc:
+ MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments
+ STRING = ActiveModel.version.to_s
end
end