aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel/lib')
-rw-r--r--activemodel/lib/active_model.rb7
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb10
-rw-r--r--activemodel/lib/active_model/callbacks.rb6
-rw-r--r--activemodel/lib/active_model/conversion.rb29
-rw-r--r--activemodel/lib/active_model/dirty.rb78
-rw-r--r--activemodel/lib/active_model/errors.rb27
-rw-r--r--activemodel/lib/active_model/forbidden_attributes_protection.rb1
-rw-r--r--activemodel/lib/active_model/gem_version.rb15
-rw-r--r--activemodel/lib/active_model/lint.rb11
-rw-r--r--activemodel/lib/active_model/locale/en.yml12
-rw-r--r--activemodel/lib/active_model/model.rb20
-rw-r--r--activemodel/lib/active_model/naming.rb26
-rw-r--r--activemodel/lib/active_model/secure_password.rb68
-rw-r--r--activemodel/lib/active_model/serialization.rb16
-rw-r--r--activemodel/lib/active_model/serializers/json.rb10
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb43
-rw-r--r--activemodel/lib/active_model/validations/absence.rb4
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb14
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb13
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb4
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb6
-rw-r--r--activemodel/lib/active_model/validations/format.rb8
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb6
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb29
-rw-r--r--activemodel/lib/active_model/validations/presence.rb2
-rw-r--r--activemodel/lib/active_model/validations/validates.rb12
-rw-r--r--activemodel/lib/active_model/validations/with.rb9
-rw-r--r--activemodel/lib/active_model/validator.rb20
-rw-r--r--activemodel/lib/active_model/version.rb11
30 files changed, 318 insertions, 203 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index 469f137d03..46d60db756 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2004-2014 David Heinemeier Hansson
+# Copyright (c) 2004-2015 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -48,6 +48,7 @@ module ActiveModel
eager_autoload do
autoload :Errors
+ autoload :StrictValidationFailed, 'active_model/errors'
end
module Serializers
@@ -59,9 +60,9 @@ module ActiveModel
end
end
- def eager_load!
+ def self.eager_load!
super
- ActiveModel::Serializer.eager_load!
+ ActiveModel::Serializers.eager_load!
end
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index ea07c5c039..96be551264 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -353,14 +353,12 @@ module ActiveModel
@attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4)
end
- def attribute_method_matcher(method_name) #:nodoc:
+ def attribute_method_matchers_matching(method_name) #:nodoc:
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(method_name) }
- match
+ matchers.map { |method| method.match(method_name) }.compact
end
end
@@ -469,8 +467,8 @@ module ActiveModel
# Returns a struct representing the matching attribute method.
# The struct's attributes are prefix, base and suffix.
def match_attribute_method?(method_name)
- match = self.class.send(:attribute_method_matcher, method_name)
- match if match && attribute_method?(match.attr_name)
+ matches = self.class.send(:attribute_method_matchers_matching, method_name)
+ matches.detect { |match| attribute_method?(match.attr_name) }
end
def missing_attribute(attr_name, stack)
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index b27a39b787..6214802074 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -6,7 +6,7 @@ module ActiveModel
# Provides an interface for any class to have Active Record like callbacks.
#
# Like the Active Record methods, the callback chain is aborted as soon as
- # one of the methods in the chain returns +false+.
+ # one of the methods throws +:abort+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
@@ -97,10 +97,12 @@ module ActiveModel
# # obj is the MyModel instance that the callback is being called on
# end
# end
+ #
+ # NOTE: +method_name+ passed to `define_model_callbacks` must not end with
+ # `!`, `?` or `=`.
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 6b0a752566..9de6ea65be 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -22,7 +22,7 @@ module ActiveModel
module Conversion
extend ActiveSupport::Concern
- # If your object is already designed to implement all of the Active Model
+ # If your object is already designed to implement all of the \Active \Model
# you can use the default <tt>:to_model</tt> implementation, which simply
# returns +self+.
#
@@ -33,20 +33,22 @@ module ActiveModel
# person = Person.new
# person.to_model == person # => true
#
- # If your model does not act like an Active Model object, then you should
+ # If your model does not act like an \Active \Model object, then you should
# define <tt>:to_model</tt> yourself returning a proxy object that wraps
- # your object with Active Model compliant methods.
+ # your object with \Active \Model compliant methods.
def to_model
self
end
- # Returns an Enumerable of all key attributes if any is set, regardless if
+ # Returns an Array of all key attributes if any is set, regardless if
# the object is persisted or not. Returns +nil+ if there are no key attributes.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Conversion
+ # attr_accessor :id
# end
#
- # person = Person.create
+ # person = Person.create(id: 1)
# person.to_key # => [1]
def to_key
key = respond_to?(:id) && id
@@ -56,13 +58,18 @@ module ActiveModel
# Returns a +string+ representing the object's key suitable for use in URLs,
# or +nil+ if <tt>persisted?</tt> is +false+.
#
- # class Person < ActiveRecord::Base
+ # class Person
+ # include ActiveModel::Conversion
+ # attr_accessor :id
+ # def persisted?
+ # true
+ # end
# end
#
- # person = Person.create
+ # person = Person.create(id: 1)
# person.to_param # => "1"
def to_param
- persisted? ? to_key.join('-') : nil
+ (persisted? && key = to_key) ? key.join('-') : nil
end
# Returns a +string+ identifying the path associated with the object.
@@ -83,8 +90,8 @@ module ActiveModel
# internal method and should not be accessed directly.
def _to_partial_path #:nodoc:
@_to_partial_path ||= begin
- element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
- collection = ActiveSupport::Inflector.tableize(self)
+ element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name))
+ collection = ActiveSupport::Inflector.tableize(name)
"#{collection}/#{element}".freeze
end
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 58d87e6f7f..afba9bab0d 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -12,11 +12,12 @@ module ActiveModel
# * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
# track.
- # * Call <tt>attr_name_will_change!</tt> before each change to the tracked
+ # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked
# attribute.
# * Call <tt>changes_applied</tt> after the changes are persisted.
- # * Call <tt>reset_changes</tt> when you want to reset the changes
+ # * Call <tt>clear_changes_information</tt> when you want to reset the changes
# information.
+ # * Call <tt>restore_attributes</tt> when you want to restore previous data.
#
# A minimal implementation could be:
#
@@ -36,11 +37,18 @@ module ActiveModel
#
# def save
# # do persistence work
+ #
# changes_applied
# end
#
# def reload!
- # reset_changes
+ # # get the values from the persistence layer
+ #
+ # clear_changes_information
+ # end
+ #
+ # def rollback!
+ # restore_attributes
# end
# end
#
@@ -72,6 +80,13 @@ module ActiveModel
# person.reload!
# person.previous_changes # => {}
#
+ # Rollback the changes:
+ #
+ # person.name = "Uncle Bob"
+ # person.rollback!
+ # person.name # => "Bill"
+ # person.name_changed? # => false
+ #
# Assigning the same value leaves the attribute unchanged:
#
# person.name = 'Bill'
@@ -84,9 +99,11 @@ module ActiveModel
# person.changed # => ["name"]
# person.changes # => {"name" => ["Bill", "Bob"]}
#
- # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
- # to mark that the attribute is changing. Otherwise ActiveModel can't track
- # changes to in-place attributes.
+ # If an attribute is modified in-place then make use of
+ # +[attribute_name]_will_change!+ to mark that the attribute is changing.
+ # Otherwise \Active \Model can't track changes to in-place attributes. Note
+ # that Active Record can detect in-place modifications automatically. You do
+ # not need to call +[attribute_name]_will_change!+ on Active Record models.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
@@ -98,7 +115,7 @@ module ActiveModel
included do
attribute_method_suffix '_changed?', '_change', '_will_change!', '_was'
- attribute_method_affix prefix: 'reset_', suffix: '!'
+ attribute_method_affix prefix: 'restore_', suffix: '!'
end
# Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
@@ -136,7 +153,7 @@ module ActiveModel
# person.save
# person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
- @previously_changed ||= {}
+ @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Returns a hash of the attributes with unsaved changes indicating their original
@@ -151,7 +168,7 @@ module ActiveModel
# Handle <tt>*_changed?</tt> for +method_missing+.
def attribute_changed?(attr, options = {}) #:nodoc:
- result = changed_attributes.include?(attr)
+ result = changes_include?(attr)
result &&= options[:to] == __send__(attr) if options.key?(:to)
result &&= options[:from] == changed_attributes[attr] if options.key?(:from)
result
@@ -162,18 +179,27 @@ module ActiveModel
attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr)
end
+ # Restore all previous data of the provided attributes.
+ def restore_attributes(attributes = changed)
+ attributes.each { |attr| restore_attribute! attr }
+ end
+
private
+ def changes_include?(attr_name)
+ attributes_changed_by_setter.include?(attr_name)
+ end
+
# Removes current changes and makes them accessible through +previous_changes+.
- def changes_applied
+ def changes_applied # :doc:
@previously_changed = changes
- @changed_attributes = {}
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
- # Removes all dirty data: current changes and previous changes
- def reset_changes
- @previously_changed = {}
- @changed_attributes = {}
+ # Clear all dirty data: current changes and previous changes.
+ def clear_changes_information # :doc:
+ @previously_changed = ActiveSupport::HashWithIndifferentAccess.new
+ @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
end
# Handle <tt>*_change</tt> for +method_missing+.
@@ -191,15 +217,29 @@ module ActiveModel
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value
+ set_attribute_was(attr, value)
end
- # Handle <tt>reset_*!</tt> for +method_missing+.
- def reset_attribute!(attr)
+ # Handle <tt>restore_*!</tt> for +method_missing+.
+ def restore_attribute!(attr)
if attribute_changed?(attr)
__send__("#{attr}=", changed_attributes[attr])
- changed_attributes.delete(attr)
+ clear_attribute_changes([attr])
end
end
+
+ # This is necessary because `changed_attributes` might be overridden in
+ # other implemntations (e.g. in `ActiveRecord`)
+ alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc:
+
+ # Force an attribute to have a particular "before" value
+ def set_attribute_was(attr, old_value)
+ attributes_changed_by_setter[attr] = old_value
+ end
+
+ # Remove changes information for the provided attributes.
+ def clear_attribute_changes(attributes) # :doc:
+ attributes_changed_by_setter.except!(*attributes)
+ end
end
end
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 010c4bb6f9..477edbd120 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -23,7 +23,7 @@ module ActiveModel
# attr_reader :errors
#
# def validate!
- # errors.add(:name, "cannot be nil") if name == nil
+ # errors.add(:name, "cannot be nil") if name.nil?
# end
#
# # The following methods are needed to be minimally implemented
@@ -94,10 +94,12 @@ module ActiveModel
# person.errors.include?(:name) # => true
# person.errors.include?(:age) # => false
def include?(attribute)
- (v = messages[attribute]) && v.any?
+ messages[attribute].present?
end
# aliases include?
alias :has_key? :include?
+ # aliases include?
+ alias :key? :include?
# Get messages for +key+.
#
@@ -251,11 +253,9 @@ module ActiveModel
# person.errors.to_hash(true) # => {:name=>["name cannot be nil"]}
def to_hash(full_messages = false)
if full_messages
- messages = {}
- self.messages.each do |attribute, array|
+ self.messages.each_with_object({}) do |(attribute, array), messages|
messages[attribute] = array.map { |message| full_message(attribute, message) }
end
- messages
else
self.messages.dup
end
@@ -283,12 +283,19 @@ module ActiveModel
# ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
- # person.errors.add(:name, nil, strict: true)
+ # person.errors.add(:name, :invalid, strict: true)
# # => ActiveModel::StrictValidationFailed: name is invalid
- # person.errors.add(:name, nil, strict: NameIsInvalid)
+ # person.errors.add(:name, :invalid, strict: NameIsInvalid)
# # => NameIsInvalid: name is invalid
#
# person.errors.messages # => {}
+ #
+ # +attribute+ should be set to <tt>:base</tt> if the error is not
+ # directly associated with a single attribute.
+ #
+ # person.errors.add(:base, "either name or email must be present")
+ # person.errors.messages
+ # # => {:base=>["either name or email must be present"]}
def add(attribute, message = :invalid, options = {})
message = normalize_message(attribute, message, options)
if exception = options[:strict]
@@ -381,8 +388,8 @@ module ActiveModel
# Translates an error message in its default scope
# (<tt>activemodel.errors.messages</tt>).
#
- # Error messages are first looked up in <tt>models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
- # if it's not there, it's looked up in <tt>models.MODEL.MESSAGE</tt> and if
+ # Error messages are first looked up in <tt>activemodel.errors.models.MODEL.attributes.ATTRIBUTE.MESSAGE</tt>,
+ # if it's not there, it's looked up in <tt>activemodel.errors.models.MODEL.MESSAGE</tt> and if
# that is not there also, it returns the translation of the default message
# (e.g. <tt>activemodel.errors.messages.MESSAGE</tt>). The translated model
# name, translated attribute name and the value are available for
@@ -427,7 +434,7 @@ module ActiveModel
options = {
default: defaults,
- model: @base.class.model_name.human,
+ model: @base.model_name.human,
attribute: @base.class.human_attribute_name(attribute),
value: value
}.merge!(options)
diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb
index 7468f95548..b4fa378601 100644
--- a/activemodel/lib/active_model/forbidden_attributes_protection.rb
+++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb
@@ -23,5 +23,6 @@ module ActiveModel
attributes
end
end
+ alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment
end
end
diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb
new file mode 100644
index 0000000000..762f4fe939
--- /dev/null
+++ b/activemodel/lib/active_model/gem_version.rb
@@ -0,0 +1,15 @@
+module ActiveModel
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
+ def self.gem_version
+ Gem::Version.new VERSION::STRING
+ end
+
+ module VERSION
+ MAJOR = 5
+ MINOR = 0
+ TINY = 0
+ PRE = "alpha"
+
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
+ end
+end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index c6bc18b008..38087521a2 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -73,16 +73,19 @@ module ActiveModel
# == \Naming
#
- # Model.model_name must return a string with some convenience methods:
- # <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. Check
- # ActiveModel::Naming for more information.
+ # Model.model_name and Model#model_name must return a string with some
+ # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
+ # <tt>:plural</tt>. Check ActiveModel::Naming for more information.
def test_model_naming
- assert model.class.respond_to?(:model_name), "The model should respond to model_name"
+ assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
model_name = model.class.model_name
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)
+
+ assert model.respond_to?(:model_name), "The model instance should respond to model_name"
+ assert_equal model.model_name, model.class.model_name
end
# == \Errors Testing
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 540e8132d3..bf07945fe1 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -14,9 +14,15 @@ en:
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)"
+ too_long:
+ one: "is too long (maximum is 1 character)"
+ other: "is too long (maximum is %{count} characters)"
+ too_short:
+ one: "is too short (minimum is 1 character)"
+ other: "is too short (minimum is %{count} characters)"
+ wrong_length:
+ one: "is the wrong length (should be 1 character)"
+ other: "is the wrong length (should be %{count} characters)"
not_a_number: "is not a number"
not_an_integer: "must be an integer"
greater_than: "must be greater than %{count}"
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index 63716eebb1..d51d6ddcc9 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -16,8 +16,8 @@ module ActiveModel
# end
#
# person = Person.new(name: 'bob', age: '18')
- # person.name # => 'bob'
- # person.age # => 18
+ # person.name # => "bob"
+ # person.age # => "18"
#
# Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
# to return +false+, which is the most common case. You may want to override
@@ -56,13 +56,13 @@ module ActiveModel
# refer to the specific modules included in <tt>ActiveModel::Model</tt>
# (see below).
module Model
- def self.included(base) #:nodoc:
- base.class_eval do
- extend ActiveModel::Naming
- extend ActiveModel::Translation
- include ActiveModel::Validations
- include ActiveModel::Conversion
- end
+ extend ActiveSupport::Concern
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+
+ included do
+ extend ActiveModel::Naming
+ extend ActiveModel::Translation
end
# Initializes a new model with the given +params+.
@@ -74,7 +74,7 @@ module ActiveModel
#
# person = Person.new(name: 'bob', age: '18')
# person.name # => "bob"
- # person.age # => 18
+ # person.age # => "18"
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 11ebfe6cc0..ada1f9a4f3 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -189,8 +189,8 @@ module ActiveModel
private
- def _singularize(string, replacement='_')
- ActiveSupport::Inflector.underscore(string).tr('/', replacement)
+ def _singularize(string)
+ ActiveSupport::Inflector.underscore(string).tr('/', '_')
end
end
@@ -204,24 +204,30 @@ module ActiveModel
# extend ActiveModel::Naming
# end
#
- # BookCover.model_name # => "BookCover"
+ # BookCover.model_name.name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
# BookCover.model_name.i18n_key # => :book_cover
# BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
- # is required to pass the Active Model Lint test. So either extending the
+ # is required to pass the \Active \Model Lint test. So either extending the
# provided method below, or rolling your own is required.
module Naming
+ def self.extended(base) #:nodoc:
+ base.remove_possible_method :model_name
+ base.delegate :model_name, to: :class
+ end
+
# Returns an ActiveModel::Name object for module. It can be
# used to retrieve all kinds of naming-related information
# (See ActiveModel::Name for more information).
#
- # class Person < ActiveModel::Model
+ # class Person
+ # include ActiveModel::Model
# end
#
- # Person.model_name # => Person
+ # Person.model_name.name # => "Person"
# Person.model_name.class # => ActiveModel::Name
# Person.model_name.singular # => "person"
# Person.model_name.plural # => "people"
@@ -298,12 +304,10 @@ module ActiveModel
end
def self.model_name_from_record_or_class(record_or_class) #:nodoc:
- if record_or_class.respond_to?(:model_name)
- record_or_class.model_name
- elsif record_or_class.respond_to?(:to_model)
- record_or_class.to_model.class.model_name
+ if record_or_class.respond_to?(:to_model)
+ record_or_class.to_model.model_name
else
- record_or_class.class.model_name
+ record_or_class.model_name
end
end
private_class_method :model_name_from_record_or_class
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index d824a66784..871031ece4 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
+ # BCrypt hash function can handle maximum 72 characters, and if we pass
+ # password of length more than 72 characters it ignores extra characters.
+ # Hence need to put a restriction on password length.
+ MAX_PASSWORD_LENGTH_ALLOWED = 72
+
class << self
attr_accessor :min_cost # :nodoc:
end
@@ -11,18 +16,22 @@ module ActiveModel
# Adds methods to set and authenticate against a BCrypt password.
# This mechanism requires you to have a +password_digest+ attribute.
#
- # Validations for presence of password on create, confirmation of password
- # (using a +password_confirmation+ attribute) are automatically added. If
- # you wish to turn off validations, pass <tt>validations: false</tt> as an
- # argument. You can add more validations by hand if need be.
+ # The following validations are added automatically:
+ # * Password must be present on creation
+ # * Password length should be less than or equal to 72 characters
+ # * Confirmation of password (using a +password_confirmation+ attribute)
+ #
+ # If password confirmation validation is not needed, simply leave out the
+ # value for +password_confirmation+ (i.e. don't provide a form field for
+ # it). When this attribute has a +nil+ value, the validation will not be
+ # triggered.
#
- # 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.
+ # For further customizability, it is possible to supress the default
+ # validations by passing <tt>validations: false</tt> as an argument.
#
- # You need to add bcrypt-ruby (~> 3.1.2) to Gemfile to use #has_secure_password:
+ # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
#
- # gem 'bcrypt-ruby', '~> 3.1.2'
+ # gem 'bcrypt', '~> 3.1.7'
#
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
@@ -42,32 +51,31 @@ module ActiveModel
# 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.
+ # Load bcrypt gem only when has_secure_password is used.
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
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"
+ $stderr.puts "You don't have bcrypt 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, if: :password_confirmation_required?
- validates_presence_of :password, on: :create
- validates_presence_of :password_confirmation, if: :password_confirmation_required?
-
- before_create { raise "Password digest missing on new record" if password_digest.blank? }
- end
+ include ActiveModel::Validations
- if respond_to?(:attributes_protected_by_default)
- def self.attributes_protected_by_default #:nodoc:
- super + ['password_digest']
+ # This ensures the model has a password by checking whether the password_digest
+ # is present, so that this works with both new and existing records. However,
+ # when there is an error, the message is added to the password attribute instead
+ # so that the error message will make sense to the end-user.
+ validate do |record|
+ record.errors.add(:password, :blank) unless record.password_digest.present?
end
+
+ validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
+ validates_confirmation_of :password, allow_blank: true
end
end
end
@@ -84,11 +92,13 @@ module ActiveModel
# user.authenticate('notright') # => false
# user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
- BCrypt::Password.new(password_digest) == unencrypted_password && self
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
end
+ attr_reader :password
+
# Encrypts the password into the +password_digest+ attribute, only if the
- # new password is not blank.
+ # new password is not empty.
#
# class User < ActiveRecord::Base
# has_secure_password validations: false
@@ -100,7 +110,9 @@ module ActiveModel
# user.password = 'mUc3m00RsqyRe'
# user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
def password=(unencrypted_password)
- unless unencrypted_password.blank?
+ if unencrypted_password.nil?
+ self.password_digest = nil
+ elsif !unencrypted_password.empty?
@password = unencrypted_password
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
@@ -110,12 +122,6 @@ module ActiveModel
def password_confirmation=(unencrypted_password)
@password_confirmation = unencrypted_password
end
-
- private
-
- def password_confirmation_required?
- 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 36a6c00290..976f50b13e 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -4,7 +4,7 @@ require 'active_support/core_ext/hash/slice'
module ActiveModel
# == Active \Model \Serialization
#
- # Provides a basic serialization to a serializable_hash for your object.
+ # Provides a basic serialization to a serializable_hash for your objects.
#
# A minimal implementation could be:
#
@@ -25,14 +25,14 @@ module ActiveModel
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
#
- # You need to declare an attributes hash which contains the attributes 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+.
+ # An +attributes+ hash must be defined and should contain any attributes you
+ # need to be serialized. 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+.
#
- # Most of the time though, you will want to include the JSON or XML
- # serializations. Both of these modules automatically include the
+ # Most of the time though, either the JSON or XML serializations are needed.
+ # Both of these modules automatically include the
# <tt>ActiveModel::Serialization</tt> module, so there is no need to
# explicitly include it.
#
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index c58e73f6a7..b66dbf1afe 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -93,7 +93,7 @@ module ActiveModel
end
if root
- root = self.class.model_name.element if root == true
+ root = model_name.element if root == true
{ root => serializable_hash(options) }
else
serializable_hash(options)
@@ -130,10 +130,10 @@ module ActiveModel
#
# json = { person: { name: 'bob', age: 22, awesome:true } }.to_json
# person = Person.new
- # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
- # person.name # => "bob"
- # person.age # => 22
- # person.awesome # => true
+ # person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_json(json, include_root=include_root_in_json)
hash = ActiveSupport::JSON.decode(json)
hash = hash.values.first if include_root
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 7f99536dbb..e33c766627 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -6,7 +6,7 @@ require 'active_support/core_ext/time/acts_like'
module ActiveModel
module Serializers
- # == Active Model XML Serializer
+ # == \Active \Model XML Serializer
module Xml
extend ActiveSupport::Concern
include ActiveModel::Serialization
@@ -84,7 +84,7 @@ module ActiveModel
@builder = options[:builder]
@builder.instruct! unless options[:skip_instruct]
- root = (options[:root] || @serializable.class.model_name.element).to_s
+ root = (options[:root] || @serializable.model_name.element).to_s
root = ActiveSupport::XmlMini.rename_key(root, options)
args = [root]
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 6be44b5d63..6a2668b8f7 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -39,6 +39,7 @@ module ActiveModel
extend ActiveSupport::Concern
included do
+ extend ActiveModel::Naming
extend ActiveModel::Callbacks
extend ActiveModel::Translation
@@ -66,8 +67,11 @@ module ActiveModel
# 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>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :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
@@ -83,6 +87,8 @@ module ActiveModel
validates_with BlockValidator, _merge_attributes(attr_names), &block
end
+ VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze
+
# Adds a validation method or block to the class. This is useful when
# overriding the +validate+ instance method becomes too unwieldy and
# you're looking for more descriptive declaration of your validations.
@@ -124,10 +130,11 @@ module ActiveModel
# 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>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <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,
@@ -139,13 +146,23 @@ module ActiveModel
# value.
def validate(*args, &block)
options = args.extract_options!
+
+ if args.all? { |arg| arg.is_a?(Symbol) }
+ options.each_key do |k|
+ unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
+ raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
+ end
+ end
+ end
+
if options.key?(:on)
options = options.dup
options[:if] = Array(options[:if])
- options[:if].unshift lambda { |o|
- o.validation_context == options[:on]
+ options[:if].unshift ->(o) {
+ Array(options[:on]).include?(o.validation_context)
}
end
+
args << options
set_callback(:validate, *args, &block)
end
@@ -199,12 +216,12 @@ module ActiveModel
# # #<StrictValidator:0x007fbff3204a30 @options={strict:true}>
# # ]
#
- # If one runs Person.clear_validators! and then checks to see what
+ # If one runs <tt>Person.clear_validators!</tt> 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
+ # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased
# so that:
#
# Person._validate_callbacks.empty? # => true
@@ -283,6 +300,8 @@ module ActiveModel
# Runs all the specified validations and returns +true+ if no errors were
# added otherwise +false+.
#
+ # Aliased as validate.
+ #
# class Person
# include ActiveModel::Validations
#
@@ -317,6 +336,8 @@ module ActiveModel
self.validation_context = current_context
end
+ alias_method :validate, :valid?
+
# Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
# added, +false+ otherwise.
#
@@ -371,7 +392,7 @@ module ActiveModel
protected
def run_validations! #:nodoc:
- run_callbacks :validate
+ _run_validate_callbacks
errors.empty?
end
end
diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb
index 1a1863370b..75bf655578 100644
--- a/activemodel/lib/active_model/validations/absence.rb
+++ b/activemodel/lib/active_model/validations/absence.rb
@@ -1,6 +1,6 @@
module ActiveModel
module Validations
- # == Active Model Absence Validator
+ # == \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?
@@ -21,7 +21,7 @@ module ActiveModel
# * <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+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_absence_of(*attr_names)
validates_with AbsenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 139de16326..ee160fb483 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -3,12 +3,12 @@ module ActiveModel
module Validations
class AcceptanceValidator < EachValidator # :nodoc:
def initialize(options)
- super({ allow_nil: true, accept: "1" }.merge!(options))
+ super({ allow_nil: true, accept: ["1", true] }.merge!(options))
setup!(options[:class])
end
def validate_each(record, attribute, value)
- unless value == options[:accept]
+ unless acceptable_option?(value)
record.errors.add(attribute, :accepted, options.except(:accept, :allow_nil))
end
end
@@ -20,6 +20,10 @@ module ActiveModel
klass.send(:attr_reader, *attr_readers)
klass.send(:attr_writer, *attr_writers)
end
+
+ def acceptable_option?(value)
+ Array(options[:accept]).include?(value)
+ end
end
module HelperMethods
@@ -38,8 +42,6 @@ module ActiveModel
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
- # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
- # is +true+).
# * <tt>:accept</tt> - Specifies value that is considered accepted.
# The default value is a string "1", which makes it easy to relate to
# an HTML checkbox. This should be set to +true+ if you are validating
@@ -47,8 +49,8 @@ module ActiveModel
# before validation.
#
# 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
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
+ # See <tt>ActiveModel::Validation#validates</tt> for more information.
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index edfffdd3ce..4b58ef66e3 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -15,15 +15,14 @@ module ActiveModel
# after_validation :do_stuff_after_validation
# end
#
- # Like other <tt>before_*</tt> callbacks if +before_validation+ returns
- # +false+ then <tt>valid?</tt> will not be called.
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
+ # +:abort+ then <tt>valid?</tt> will not be called.
module Callbacks
extend ActiveSupport::Concern
included do
include ActiveSupport::Callbacks
define_callbacks :validation,
- terminator: ->(_,result) { result == false },
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name]
end
@@ -58,7 +57,7 @@ module ActiveModel
if options.is_a?(Hash) && options[:on]
options[:if] = Array(options[:if])
options[:on] = Array(options[:on])
- options[:if].unshift lambda { |o|
+ options[:if].unshift ->(o) {
options[:on].include? o.validation_context
}
end
@@ -98,7 +97,9 @@ module ActiveModel
options[:if] = Array(options[:if])
if options[:on]
options[:on] = Array(options[:on])
- options[:if].unshift("#{options[:on]}.include? self.validation_context")
+ options[:if].unshift ->(o) {
+ options[:on].include? o.validation_context
+ }
end
set_callback(:validation, :after, *(args << options), &block)
end
@@ -108,7 +109,7 @@ module ActiveModel
# Overwrite run validations to include callbacks.
def run_validations! #:nodoc:
- run_callbacks(:validation) { super }
+ _run_validation_callbacks { super }
end
end
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index b0542661af..1b11c28087 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -54,10 +54,10 @@ module ActiveModel
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
- # confirmation").
+ # <tt>%{translated_attribute_name}</tt>").
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 48bf5cd802..f342d27275 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -34,13 +34,9 @@ module ActiveModel
# <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
- # attribute is blank(default is +false+).
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +: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/format.rb b/activemodel/lib/active_model/validations/format.rb
index f0fe22438f..02478dd5b6 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -54,7 +54,7 @@ module ActiveModel
module HelperMethods
# Validates whether the value of the specified attribute is of the correct
- # form, going by the regular expression provided.You can require that the
+ # form, going by the regular expression provided. You can require that the
# attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
@@ -91,10 +91,6 @@ module ActiveModel
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is invalid").
- # * <tt>:allow_nil</tt> - If set to true, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to true, skips this validation if the
- # attribute is blank (default is +false+).
# * <tt>:with</tt> - Regular expression that if the attribute matches will
# result in a successful validation. This can be provided as a proc or
# lambda returning regular expression which will be called at runtime.
@@ -107,7 +103,7 @@ module ActiveModel
# beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index b344095807..c84025f083 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -34,13 +34,9 @@ module ActiveModel
# * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# not included in the list").
- # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the
- # attribute is +nil+ (default is +false+).
- # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the
- # attribute is blank (default is +false+).
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +: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/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index c8d3236463..4ba4e3e8f7 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -23,6 +23,10 @@ module ActiveModel
raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast)
raw_value ||= value
+ if record_attribute_changed_in_place?(record, attr_name)
+ raw_value = value
+ end
+
return if options[:allow_nil] && raw_value.nil?
unless value = parse_raw_value_as_a_number(raw_value)
@@ -30,7 +34,7 @@ module ActiveModel
return
end
- if options[:only_integer]
+ if allow_only_integer?(record)
unless value = parse_raw_value_as_an_integer(raw_value)
record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value))
return
@@ -67,7 +71,7 @@ module ActiveModel
end
def parse_raw_value_as_an_integer(raw_value)
- raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/
+ raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/
end
def filtered_options(value)
@@ -75,6 +79,24 @@ module ActiveModel
filtered[:value] = value
filtered
end
+
+ def allow_only_integer?(record)
+ case options[:only_integer]
+ when Symbol
+ record.send(options[:only_integer])
+ when Proc
+ options[:only_integer].call(record)
+ else
+ options[:only_integer]
+ end
+ end
+
+ private
+
+ def record_attribute_changed_in_place?(record, attr_name)
+ record.respond_to?(:attribute_changed_in_place?) &&
+ record.attribute_changed_in_place?(attr_name.to_s)
+ end
end
module HelperMethods
@@ -110,7 +132,7 @@ module ActiveModel
# * <tt>:even</tt> - Specifies the value must be an even number.
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+ .
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
# See <tt>ActiveModel::Validation#validates</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which
@@ -121,6 +143,7 @@ module ActiveModel
# * <tt>:equal_to</tt>
# * <tt>:less_than</tt>
# * <tt>:less_than_or_equal_to</tt>
+ # * <tt>:only_integer</tt>
#
# For example:
#
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index ab8c8359fc..5d593274eb 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -29,7 +29,7 @@ module ActiveModel
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
#
# There is also a list of default options supported by every validator:
- # +:if+, +:unless+, +:on+ and +:strict+.
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
# See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index bf588b7bd0..bda436d8d0 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -71,9 +71,11 @@ module ActiveModel
#
# There is also a list of options that could be used along with validators:
#
- # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
- # validation contexts by default (+nil+), other options are <tt>:create</tt>
- # and <tt>:update</tt>.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <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,
@@ -83,7 +85,9 @@ module ActiveModel
# 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> - if the <tt>:strict</tt> option is set to true
+ # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
+ # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
# will raise ActiveModel::StrictValidationFailed instead of adding the error.
# <tt>:strict</tt> option can also be set to any other exception.
#
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 16bd6670d1..a2531327bf 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -52,8 +52,11 @@ module ActiveModel
# end
#
# Configuration options:
- # * <tt>:on</tt> - Specifies when this validation is active
- # (<tt>:create</tt> or <tt>:update</tt>.
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
+ # Runs in all validation contexts by default (nil). You can pass a symbol
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
+ # <tt>on: :custom_validation_context</tt> or
+ # <tt>on: [:create, :custom_validation_context]</tt>)
# * <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>).
@@ -139,6 +142,8 @@ module ActiveModel
# class version of this method for more information.
def validates_with(*args, &block)
options = args.extract_options!
+ options[:class] = self.class
+
args.each do |klass|
validator = klass.new(options, &block)
validator.validate(self)
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index bddacc8c45..ac32750946 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -79,7 +79,7 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :title
#
- # validates :title, presence: true
+ # validates :title, presence: true, title: true
# end
#
# It can be useful to access the class that is using that validator when there are prerequisites such
@@ -106,7 +106,6 @@ module ActiveModel
# Accepts options that will be made available through the +options+ reader.
def initialize(options = {})
@options = options.except(:class).freeze
- deprecated_setup(options)
end
# Returns the kind for this validator.
@@ -122,28 +121,13 @@ 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
# in the options hash invoking the <tt>validate_each</tt> method passing in the
# record, attribute and value.
#
- # All Active Model validations are built on top of this validator.
+ # All \Active \Model validations are built on top of this validator.
class EachValidator < Validator #:nodoc:
attr_reader :attributes
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index 58ba3ab9b2..6da3b4117b 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,11 +1,8 @@
+require_relative 'gem_version'
+
module ActiveModel
- # Returns the version of the currently loaded ActiveModel as a Gem::Version
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
def self.version
- Gem::Version.new "4.1.0.beta1"
- end
-
- module VERSION #:nodoc:
- MAJOR, MINOR, TINY, PRE = ActiveModel.version.segments
- STRING = ActiveModel.version.to_s
+ gem_version
end
end