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.rb185
-rw-r--r--activemodel/lib/active_model/callbacks.rb75
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/conversion.rb48
-rw-r--r--activemodel/lib/active_model/dirty.rb7
-rw-r--r--activemodel/lib/active_model/errors.rb219
-rw-r--r--activemodel/lib/active_model/lint.rb45
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb257
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb8
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb8
-rw-r--r--activemodel/lib/active_model/model.rb55
-rw-r--r--activemodel/lib/active_model/naming.rb200
-rw-r--r--activemodel/lib/active_model/observer_array.rb39
-rw-r--r--activemodel/lib/active_model/observing.rb207
-rw-r--r--activemodel/lib/active_model/railtie.rb8
-rw-r--r--activemodel/lib/active_model/secure_password.rb58
-rw-r--r--activemodel/lib/active_model/serialization.rb50
-rw-r--r--activemodel/lib/active_model/serializers/json.rb132
-rwxr-xr-x[-rw-r--r--]activemodel/lib/active_model/serializers/xml.rb40
-rw-r--r--activemodel/lib/active_model/validations.rb185
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb28
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb87
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb11
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb29
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb43
-rw-r--r--activemodel/lib/active_model/validations/format.rb56
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb46
-rw-r--r--activemodel/lib/active_model/validations/length.rb60
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb54
-rw-r--r--activemodel/lib/active_model/validations/presence.rb26
-rw-r--r--activemodel/lib/active_model/validations/validates.rb92
-rw-r--r--activemodel/lib/active_model/validations/with.rb44
-rw-r--r--activemodel/lib/active_model/validator.rb30
33 files changed, 1623 insertions, 943 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 846d0d7f86..ef04f1fa49 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,34 +1,39 @@
-require 'active_support/core_ext/hash/keys'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/deprecation'
module ActiveModel
+ # Raised when an attribute is not defined.
+ #
+ # class User < ActiveRecord::Base
+ # has_many :pets
+ # end
+ #
+ # user = User.first
+ # user.pets.select(:id).first.user_id
+ # # => ActiveModel::MissingAttributeError: missing attribute: user_id
class MissingAttributeError < NoMethodError
end
# == 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+.
+ # <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+.
#
# The requirements to implement ActiveModel::AttributeMethods are to:
#
- # * <tt>include ActiveModel::AttributeMethods</tt> in your object
+ # * <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
- # * Call <tt>define_attribute_methods</tt> after the other methods are
- # called.
- # * Define the various generic +_attribute+ methods that you have declared
+ # +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.
#
# A minimal implementation could be:
#
# class Person
# include ActiveModel::AttributeMethods
#
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
+ # attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
# attribute_method_suffix '_contrived?'
# attribute_method_prefix 'clear_'
- # define_attribute_methods 'name'
+ # define_attribute_methods :name
#
# attr_accessor :name
#
@@ -43,17 +48,16 @@ module ActiveModel
# end
#
# def reset_attribute_to_default!(attr)
- # send("#{attr}=", "Default Name")
+ # send("#{attr}=", 'Default Name')
# end
# end
#
# Note that whenever you include ActiveModel::AttributeMethods in your class,
- # it requires you to implement an <tt>attributes</tt> method which returns a hash
+ # 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
extend ActiveSupport::Concern
@@ -61,8 +65,8 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- extend ActiveModel::Configuration
- config_attribute :attribute_method_matchers
+ class_attribute :attribute_aliases, :attribute_method_matchers, instance_writer: false
+ self.attribute_aliases = {}
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
@@ -79,11 +83,9 @@ module ActiveModel
# An instance method <tt>#{prefix}attribute</tt> must exist and accept
# at least the +attr+ argument.
#
- # For example:
- #
# class Person
- #
# include ActiveModel::AttributeMethods
+ #
# attr_accessor :name
# attribute_method_prefix 'clear_'
# define_attribute_methods :name
@@ -96,12 +98,12 @@ module ActiveModel
# end
#
# person = Person.new
- # person.name = "Bob"
+ # person.name = 'Bob'
# person.name # => "Bob"
# person.clear_name
# person.name # => nil
def attribute_method_prefix(*prefixes)
- self.attribute_method_matchers += prefixes.map { |prefix| AttributeMethodMatcher.new :prefix => prefix }
+ self.attribute_method_matchers += prefixes.map! { |prefix| AttributeMethodMatcher.new prefix: prefix }
undefine_attribute_methods
end
@@ -114,14 +116,12 @@ module ActiveModel
#
# attribute#{suffix}(#{attr}, *args, &block)
#
- # An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
- # the +attr+ argument.
- #
- # For example:
+ # An <tt>attribute#{suffix}</tt> instance method must exist and accept at
+ # least the +attr+ argument.
#
# class Person
- #
# include ActiveModel::AttributeMethods
+ #
# attr_accessor :name
# attribute_method_suffix '_short?'
# define_attribute_methods :name
@@ -134,11 +134,11 @@ module ActiveModel
# end
#
# person = Person.new
- # person.name = "Bob"
+ # person.name = 'Bob'
# person.name # => "Bob"
# person.name_short? # => true
def attribute_method_suffix(*suffixes)
- self.attribute_method_matchers += suffixes.map { |suffix| AttributeMethodMatcher.new :suffix => suffix }
+ self.attribute_method_matchers += suffixes.map! { |suffix| AttributeMethodMatcher.new suffix: suffix }
undefine_attribute_methods
end
@@ -155,13 +155,11 @@ module ActiveModel
# An <tt>#{prefix}attribute#{suffix}</tt> instance method must exist and
# accept at least the +attr+ argument.
#
- # For example:
- #
# class Person
- #
# include ActiveModel::AttributeMethods
+ #
# attr_accessor :name
- # attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
+ # attribute_method_affix prefix: 'reset_', suffix: '_to_default!'
# define_attribute_methods :name
#
# private
@@ -176,7 +174,7 @@ module ActiveModel
# person.reset_name_to_default!
# person.name # => 'Gemma'
def attribute_method_affix(*affixes)
- self.attribute_method_matchers += affixes.map { |affix| AttributeMethodMatcher.new :prefix => affix[:prefix], :suffix => affix[:suffix] }
+ self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] }
undefine_attribute_methods
end
@@ -184,15 +182,29 @@ module ActiveModel
# Allows you to make aliases for attributes.
#
# class Person
+ # include ActiveModel::AttributeMethods
+ #
# attr_accessor :name
+ # attribute_method_suffix '_short?'
+ # define_attribute_methods :name
+ #
# alias_attribute :nickname, :name
+ #
+ # private
+ #
+ # def attribute_short?(attr)
+ # send(attr).length < 5
+ # end
# end
#
# person = Person.new
- # person.nickname = "Bob"
- # person.nickname # => "Bob"
- # person.name # => "Bob"
+ # person.name = 'Bob'
+ # person.name # => "Bob"
+ # person.nickname # => "Bob"
+ # person.name_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|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
@@ -203,13 +215,13 @@ module ActiveModel
# Declares the attributes that should be prefixed and suffixed by
# ActiveModel::AttributeMethods.
#
- # To use, pass in an array of attribute names (as strings or symbols),
- # be sure to declare +define_attribute_methods+ after you define any
- # prefix, suffix or affix methods, or they will not hook in.
+ # To use, pass attribute names (as strings or symbols), be sure to declare
+ # +define_attribute_methods+ after you define any prefix, suffix or affix
+ # methods, or they will not hook in.
#
# class Person
- #
# include ActiveModel::AttributeMethods
+ #
# attr_accessor :name, :age, :address
# attribute_method_prefix 'clear_'
#
@@ -228,6 +240,35 @@ module ActiveModel
attr_names.flatten.each { |attr_name| define_attribute_method(attr_name) }
end
+ # Declares an attribute that should be prefixed and suffixed by
+ # ActiveModel::AttributeMethods.
+ #
+ # To use, pass an attribute name (as string or symbol), be sure to declare
+ # +define_attribute_method+ after you define any prefix, suffix or affix
+ # method, or they will not hook in.
+ #
+ # class Person
+ # include ActiveModel::AttributeMethods
+ #
+ # attr_accessor :name
+ # attribute_method_suffix '_short?'
+ #
+ # # Call to define_attribute_method must appear after the
+ # # attribute_method_prefix, attribute_method_suffix or
+ # # attribute_method_affix declares.
+ # define_attribute_method :name
+ #
+ # private
+ #
+ # def attribute_short?(attr)
+ # send(attr).length < 5
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = 'Bob'
+ # person.name # => "Bob"
+ # person.name_short? # => true
def define_attribute_method(attr_name)
attribute_method_matchers.each do |matcher|
method_name = matcher.method_name(attr_name)
@@ -245,7 +286,29 @@ module ActiveModel
attribute_method_matchers_cache.clear
end
- # Removes all the previously dynamically defined methods from the class
+ # Removes all the previously dynamically defined methods from the class.
+ #
+ # class Person
+ # include ActiveModel::AttributeMethods
+ #
+ # attr_accessor :name
+ # attribute_method_suffix '_short?'
+ # define_attribute_method :name
+ #
+ # private
+ #
+ # def attribute_short?(attr)
+ # send(attr).length < 5
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = 'Bob'
+ # person.name_short? # => true
+ #
+ # Person.undefine_attribute_methods
+ #
+ # person.name_short? # => NoMethodError
def undefine_attribute_methods
generated_attribute_methods.module_eval do
instance_methods.each { |m| undef_method(m) }
@@ -259,7 +322,7 @@ module ActiveModel
end
protected
- def instance_method_already_implemented?(method_name)
+ def instance_method_already_implemented?(method_name) #:nodoc:
generated_attribute_methods.method_defined?(method_name)
end
@@ -278,15 +341,13 @@ module ActiveModel
end
def attribute_method_matcher(method_name) #:nodoc:
- if attribute_method_matchers_cache.key?(method_name)
- attribute_method_matchers_cache[method_name]
- else
+ attribute_method_matchers_cache.fetch(method_name) do |name|
# 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) }
- attribute_method_matchers_cache[method_name] = match
+ matchers.detect { |method| match = method.match(name) }
+ attribute_method_matchers_cache[name] = match
end
end
@@ -294,18 +355,18 @@ module ActiveModel
# using the given `extra` args. This fallbacks `define_method`
# and `send` if the given names cannot be compiled.
def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
- if name =~ NAME_COMPILABLE_REGEXP
- defn = "def #{name}(*args)"
+ defn = if name =~ NAME_COMPILABLE_REGEXP
+ "def #{name}(*args)"
else
- defn = "define_method(:'#{name}') do |*args|"
+ "define_method(:'#{name}') do |*args|"
end
- extra = (extra.map(&:inspect) << "*args").join(", ")
+ extra = (extra.map!(&:inspect) << "*args").join(", ")
- if send =~ CALL_COMPILABLE_REGEXP
- target = "#{"self." unless include_private}#{send}(#{extra})"
+ target = if send =~ CALL_COMPILABLE_REGEXP
+ "#{"self." unless include_private}#{send}(#{extra})"
else
- target = "send(:'#{send}', #{extra})"
+ "send(:'#{send}', #{extra})"
end
mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -315,14 +376,12 @@ module ActiveModel
RUBY
end
- class AttributeMethodMatcher
+ class AttributeMethodMatcher #:nodoc:
attr_reader :prefix, :suffix, :method_missing_target
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
def initialize(options = {})
- options.symbolize_keys!
-
if options[:prefix] == '' || options[:suffix] == ''
ActiveSupport::Deprecation.warn(
"Specifying an empty prefix/suffix for an attribute method is no longer " \
@@ -332,7 +391,7 @@ module ActiveModel
)
end
- @prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
+ @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '')
@regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
@@ -341,8 +400,6 @@ module ActiveModel
def match(method_name)
if @regex =~ method_name
AttributeMethodMatch.new(method_missing_target, $1, method_name)
- else
- nil
end
end
@@ -401,7 +458,7 @@ module ActiveModel
end
protected
- def attribute_method?(attr_name)
+ def attribute_method?(attr_name) #:nodoc:
respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
@@ -410,7 +467,7 @@ module ActiveModel
# 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 && attribute_method?(match.attr_name) ? match : nil
+ match if 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 ebb4b51aa3..e442455a53 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 in the chain returns +false+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
@@ -18,9 +18,10 @@ module ActiveModel
#
# define_model_callbacks :create, :update
#
- # This will provide all three standard callbacks (before, around and after) for
- # both the :create and :update methods. To implement, you need to wrap the methods
- # you want callbacks on in a block so that the callbacks get a chance to fire:
+ # This will provide all three standard callbacks (before, around and after)
+ # for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
+ # you need to wrap the methods you want callbacks on in a block so that the
+ # callbacks get a chance to fire:
#
# def create
# run_callbacks :create do
@@ -28,8 +29,8 @@ module ActiveModel
# end
# end
#
- # Then in your class, you can use the +before_create+, +after_create+ and +around_create+
- # methods, just as you would in an Active Record module.
+ # Then in your class, you can use the +before_create+, +after_create+ and
+ # +around_create+ methods, just as you would in an Active Record module.
#
# before_create :action_before_create
#
@@ -37,39 +38,52 @@ module ActiveModel
# # Your code here
# end
#
+ # When defining an around callback remember to yield to the block, otherwise
+ # it won't be executed:
+ #
+ # around_create :log_status
+ #
+ # def log_status
+ # puts 'going to call the block...'
+ # yield
+ # puts 'block successfully called.'
+ # end
+ #
# You can choose not to have all three callbacks by passing a hash to the
- # define_model_callbacks method.
+ # +define_model_callbacks+ method.
#
- # define_model_callbacks :create, :only => [:after, :before]
+ # define_model_callbacks :create, only: [:after, :before]
#
- # Would only create the after_create and before_create callback methods in your
- # class.
+ # Would only create the +after_create+ and +before_create+ callback methods in
+ # your class.
module Callbacks
- def self.extended(base)
+ def self.extended(base) #:nodoc:
base.class_eval do
include ActiveSupport::Callbacks
end
end
- # define_model_callbacks accepts the same options define_callbacks does, in case
- # you want to overwrite a default. Besides that, it also accepts an :only option,
- # where you can choose if you want all types (before, around or after) or just some.
+ # define_model_callbacks accepts the same options +define_callbacks+ does,
+ # in case you want to overwrite a default. Besides that, it also accepts an
+ # <tt>:only</tt> option, where you can choose if you want all types (before,
+ # around or after) or just some.
#
- # define_model_callbacks :initializer, :only => :after
+ # define_model_callbacks :initializer, only: :after
#
- # Note, the <tt>:only => <type></tt> hash will apply to all callbacks defined on
- # that method call. To get around this you can call the define_model_callbacks
+ # Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
+ # on that method call. To get around this you can call the define_model_callbacks
# method as many times as you need.
#
- # define_model_callbacks :create, :only => :after
- # define_model_callbacks :update, :only => :before
- # define_model_callbacks :destroy, :only => :around
+ # define_model_callbacks :create, only: :after
+ # define_model_callbacks :update, only: :before
+ # define_model_callbacks :destroy, only: :around
#
- # Would create +after_create+, +before_update+ and +around_destroy+ methods only.
+ # Would create +after_create+, +before_update+ and +around_destroy+ methods
+ # only.
#
- # You can pass in a class to before_<type>, after_<type> and around_<type>, in which
- # case the callback will call that class's <action>_<type> method passing the object
- # that the callback is being called on.
+ # You can pass in a class to before_<type>, after_<type> and around_<type>,
+ # in which case the callback will call that class's <action>_<type> method
+ # passing the object that the callback is being called on.
#
# class MyModel
# extend ActiveModel::Callbacks
@@ -83,15 +97,14 @@ module ActiveModel
# # obj is the MyModel instance that the callback is being called on
# end
# end
- #
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]
- }.merge(options)
+ :terminator => "result == false",
+ :skip_after_callbacks_if_terminated => true,
+ :scope => [:kind, :name],
+ :only => [:before, :around, :after]
+ }.merge!(options)
types = Array(options.delete(:only))
@@ -104,6 +117,8 @@ module ActiveModel
end
end
+ private
+
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
deleted file mode 100644
index ba5a6a2075..0000000000
--- a/activemodel/lib/active_model/configuration.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActiveModel
- # This API is for Rails' internal use and is not currently considered 'public', so
- # it may change in the future without warning.
- #
- # It creates configuration attributes that can be inherited from a module down
- # to a class that includes the module. E.g.
- #
- # module MyModel
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- #
- # class Post
- # include MyModel
- # end
- #
- # Post.awesome # => true
- #
- # Post.awesome = false
- # Post.awesome # => false
- # MyModel.awesome # => true
- #
- # We assume that the module will have a ClassMethods submodule containing methods
- # to be transferred to the including class' singleton class.
- #
- # Config options can also be defined directly on a class:
- #
- # class Post
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # end
- #
- # So this allows us to define a module that doesn't care about whether it is being
- # included in a class or a module:
- #
- # module Awesomeness
- # extend ActiveSupport::Concern
- #
- # included do
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- # end
- #
- # class Post
- # include Awesomeness
- # end
- #
- # module AwesomeModel
- # include Awesomeness
- # end
- module Configuration #:nodoc:
- def config_attribute(name, options = {})
- klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
- klass.new(self, name, options).define
- end
-
- class Attribute
- attr_reader :host, :name, :options
-
- def initialize(host, name, options)
- @host, @name, @options = host, name, options
- end
-
- def instance_writer?
- options.fetch(:instance_writer, false)
- end
- end
-
- class ClassAttribute < Attribute
- def define
- if options[:global]
- host.cattr_accessor name, :instance_writer => instance_writer?
- else
- host.class_attribute name, :instance_writer => instance_writer?
- end
- end
- end
-
- class ModuleAttribute < Attribute
- def class_methods
- @class_methods ||= begin
- if host.const_defined?(:ClassMethods, false)
- host.const_get(:ClassMethods)
- else
- host.const_set(:ClassMethods, Module.new)
- end
- end
- end
-
- def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- attr_accessor :#{name}
- def #{name}?; !!#{name}; end
- CODE
-
- name, host = self.name, self.host
-
- class_methods.class_eval do
- define_method(name) { host.send(name) }
- define_method("#{name}?") { !!send(name) }
- end
-
- host.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
- def #{name}?; !!#{name}; end
- CODE
-
- if options[:global]
- class_methods.class_eval do
- define_method("#{name}=") { |val| host.send("#{name}=", val) }
- end
- else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
- end
- CODE
- end
-
- host.send(:attr_writer, name) if instance_writer?
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index d7f30f0920..48c53f0789 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -1,4 +1,3 @@
-require 'active_support/concern'
require 'active_support/inflector'
module ActiveModel
@@ -18,17 +17,23 @@ module ActiveModel
# end
#
# cm = ContactMessage.new
- # cm.to_model == self # => true
- # cm.to_key # => nil
- # cm.to_param # => nil
- # cm.to_partial_path # => "contact_messages/contact_message"
- #
+ # cm.to_model == cm # => true
+ # cm.to_key # => nil
+ # cm.to_param # => nil
+ # cm.to_partial_path # => "contact_messages/contact_message"
module Conversion
extend ActiveSupport::Concern
# 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.
+ # returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Conversion
+ # end
+ #
+ # person = Person.new
+ # person.to_model == person # => true
#
# 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
@@ -37,21 +42,40 @@ module ActiveModel
self
end
- # Returns an Enumerable of all key attributes if any is set, regardless
- # if the object is persisted or not.
+ # Returns an Enumerable of all key attributes if any is set, regardless if
+ # the object is persisted or not. If there no key attributes, returns +nil+.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # person = Person.create
+ # person.to_key # => [1]
def to_key
key = respond_to?(:id) && id
key ? [key] : nil
end
- # Returns a string representing the object's key suitable for use in URLs,
- # or nil if <tt>persisted?</tt> is false.
+ # 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
+ # end
+ #
+ # person = Person.create
+ # person.to_param # => "1"
def to_param
persisted? ? to_key.join('-') : nil
end
- # Returns a string identifying the path associated with the object.
+ # Returns a +string+ identifying the path associated with the object.
# ActionPack uses this to find a suitable partial to represent the object.
+ #
+ # class Person
+ # include ActiveModel::Conversion
+ # end
+ #
+ # person = Person.new
+ # person.to_partial_path # => "people/person"
def to_partial_path
self.class._to_partial_path
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 7014d8114f..c0b268fa4d 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,7 +1,6 @@
require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
-require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Dirty
@@ -80,8 +79,8 @@ module ActiveModel
# 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.
+ # to mark that the attribute is changing. Otherwise ActiveModel can't track
+ # changes to in-place attributes.
#
# person.name_will_change!
# person.name_change # => ["Bill", "Bill"]
@@ -115,7 +114,7 @@ module ActiveModel
end
# Returns a hash of changed attributes indicating their original
- # and new values like <tt>attr => [original value, new value]</tt>.
+ # and new values like <tt>attr => [original value, new value]</tt>.
#
# person.changes # => {}
# person.name = 'bob'
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index aba6618b56..b3b9ba8e56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -2,7 +2,6 @@
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Errors
@@ -54,8 +53,8 @@ module ActiveModel
# The above allows you to do:
#
# p = Person.new
- # p.validate! # => ["can not be nil"]
- # p.errors.full_messages # => ["name can not be nil"]
+ # person.validate! # => ["can not be nil"]
+ # person.errors.full_messages # => ["name can not be nil"]
# # etc..
class Errors
include Enumerable
@@ -76,33 +75,55 @@ module ActiveModel
@messages = {}
end
- def initialize_dup(other)
+ def initialize_dup(other) #:nodoc:
@messages = other.messages.dup
super
end
- # Clear the messages
+ # Clear the error messages.
+ #
+ # person.errors.full_messages # => ["name can not be nil"]
+ # person.errors.clear
+ # person.errors.full_messages # => []
def clear
messages.clear
end
- # Do the error messages include an error with key +error+?
- def include?(error)
- (v = messages[error]) && v.any?
+ # 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.include?(:name) # => true
+ # person.errors.include?(:age) # => false
+ def include?(attribute)
+ (v = messages[attribute]) && v.any?
end
+ # aliases include?
alias :has_key? :include?
- # Get messages for +key+
+ # Get messages for +key+.
+ #
+ # person.errors.messages # => { :name => ["can not be nil"] }
+ # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.get(:age) # => nil
def get(key)
messages[key]
end
- # Set messages for +key+ to +value+
+ # Set messages for +key+ to +value+.
+ #
+ # person.errors.get(:name) # => ["can not be nil"]
+ # person.errors.set(:name, ["can't be nil"])
+ # person.errors.get(:name) # => ["can't be nil"]
def set(key, value)
messages[key] = value
end
- # Delete messages for +key+
+ # 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.get(:name) # => nil
def delete(key)
messages.delete(key)
end
@@ -110,16 +131,16 @@ module ActiveModel
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
- # p.errors[:name] # => ["can not be nil"]
- # p.errors['name'] # => ["can not be nil"]
+ # person.errors[:name] # => ["can not be nil"]
+ # person.errors['name'] # => ["can not be nil"]
def [](attribute)
get(attribute.to_sym) || set(attribute.to_sym, [])
end
# Adds to the supplied attribute the supplied error message.
#
- # p.errors[:name] = "must be set"
- # p.errors[:name] # => ['must be set']
+ # person.errors[:name] = "must be set"
+ # person.errors[:name] # => ['must be set']
def []=(attribute, error)
self[attribute] << error
end
@@ -128,13 +149,13 @@ module ActiveModel
# Yields the attribute and the error for that attribute. If the attribute
# has more than one error message, yields once for each error message.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, error|
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
- # p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, error|
+ # person.errors.add(:name, "must be specified")
+ # person.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -146,54 +167,65 @@ module ActiveModel
# Returns the number of error messages.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.size # => 1
- # p.errors.add(:name, "must be specified")
- # p.errors.size # => 2
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.size # => 1
+ # person.errors.add(:name, "must be specified")
+ # person.errors.size # => 2
def size
values.flatten.size
end
- # Returns all message values
+ # Returns all message values.
+ #
+ # 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
end
- # Returns all message keys
+ # Returns all message keys.
+ #
+ # person.errors.messages # => { :name => ["can not be nil", "must be specified"] }
+ # person.errors.keys # => [:name]
def keys
messages.keys
end
- # Returns an array of error messages, with the attribute name included
+ # Returns an array of error messages, with the attribute name included.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.add(:name, "must be specified")
- # p.errors.to_a # => ["name can't be blank", "name must be specified"]
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, "must be specified")
+ # person.errors.to_a # => ["name can't be blank", "name must be specified"]
def to_a
full_messages
end
# Returns the number of error messages.
- # p.errors.add(:name, "can't be blank")
- # p.errors.count # => 1
- # p.errors.add(:name, "must be specified")
- # p.errors.count # => 2
+ #
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.count # => 1
+ # person.errors.add(:name, "must be specified")
+ # person.errors.count # => 2
def count
to_a.size
end
- # Returns true if no errors are found, false otherwise.
+ # 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.empty? # => false
def empty?
all? { |k, v| v && v.empty? && !v.is_a?(String) }
end
+ # aliases empty?
alias_method :blank?, :empty?
# Returns an xml formatted representation of the Errors hash.
#
- # p.errors.add(:name, "can't be blank")
- # p.errors.add(:name, "must be specified")
- # p.errors.to_xml
+ # person.errors.add(:name, "can't be blank")
+ # person.errors.add(:name, "must be specified")
+ # person.errors.to_xml
# # =>
# # <?xml version=\"1.0\" encoding=\"UTF-8\"?>
# # <errors>
@@ -204,14 +236,21 @@ module ActiveModel
to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
- # Returns an Hash that can be used as the JSON representation for this object.
- # Options:
- # * <tt>:full_messages</tt> - determines if json object should contain
- # full messages or not. Default: <tt>false</tt>.
+ # 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"] }
def as_json(options=nil)
to_hash(options && options[:full_messages])
end
+ # 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"] }
def to_hash(full_messages = false)
if full_messages
messages = {}
@@ -224,22 +263,50 @@ module ActiveModel
end
end
- # Adds +message+ to the error messages on +attribute+. More than one error can be added to the same
- # +attribute+.
- # If no +message+ is supplied, <tt>:invalid</tt> is assumed.
+ # Adds +message+ to the error messages on +attribute+. More than one error
+ # can be added to the same +attribute+. If no +message+ is supplied,
+ # <tt>:invalid</tt> is assumed.
+ #
+ # person.errors.add(:name)
+ # # => ["is invalid"]
+ # person.errors.add(:name, 'must be implemented')
+ # # => ["is invalid", "must be implemented"]
+ #
+ # person.errors.messages
+ # # => { :name => ["must be implemented", "is invalid"] }
+ #
+ # If +message+ is a symbol, it will be translated using the appropriate
+ # scope (see +generate_message+).
+ #
+ # If +message+ is a proc, it will be called, allowing for things like
+ # <tt>Time.now</tt> to be used within an error.
#
- # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+).
- # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error.
+ # 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.
+ #
+ # person.errors.add(:name, nil, strict: true)
+ # # => ActiveModel::StrictValidationFailed: name is invalid
+ # person.errors.add(:name, nil, strict: NameIsInvalid)
+ # # => NameIsInvalid: name is invalid
+ #
+ # person.errors.messages # => {}
def add(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
- if options[:strict]
- raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
+ if exception = options[:strict]
+ exception = ActiveModel::StrictValidationFailed if exception == true
+ raise exception, full_message(attribute, message)
end
self[attribute] << message
end
- # Will add an error message to each of the attributes in +attributes+ that is empty.
+ # Will add an error message to each of the attributes in +attributes+
+ # that is empty.
+ #
+ # person.errors.add_on_empty(:name)
+ # person.errors.messages
+ # # => { :name => ["can't be empty"] }
def add_on_empty(attributes, options = {})
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
@@ -248,7 +315,12 @@ module ActiveModel
end
end
- # Will add an error message to each of the attributes in +attributes+ that is blank (using Object#blank?).
+ # Will add an error message to each of the attributes in +attributes+ that
+ # is blank (using Object#blank?).
+ #
+ # person.errors.add_on_blank(:name)
+ # person.errors.messages
+ # # => { :name => ["can't be blank"] }
def add_on_blank(attributes, options = {})
[attributes].flatten.each do |attribute|
value = @base.send(:read_attribute_for_validation, attribute)
@@ -256,10 +328,11 @@ module ActiveModel
end
end
- # Returns true if an error on the attribute with the given message is present, false otherwise.
- # +message+ is treated the same as for +add+.
- # p.errors.add :name, :blank
- # p.errors.added? :name, :blank # => true
+ # Returns +true+ if an error on the attribute with the given message is
+ # present, +false+ otherwise. +message+ is treated the same as for +add+.
+ #
+ # person.errors.add :name, :blank
+ # person.errors.added? :name, :blank # => true
def added?(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
self[attribute].include? message
@@ -267,22 +340,21 @@ module ActiveModel
# Returns all the full error messages in an array.
#
- # class Company
+ # class Person
# validates_presence_of :name, :address, :email
- # validates_length_of :name, :in => 5..30
+ # validates_length_of :name, in: 5..30
# end
#
- # company = Company.create(:address => '123 First St.')
- # company.errors.full_messages # =>
- # ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
+ # person = Person.create(address: '123 First St.')
+ # person.errors.full_messages
+ # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank", "Email can't be blank"]
def full_messages
map { |attribute, message| full_message(attribute, message) }
end
# Returns a full message for a given attribute.
#
- # company.errors.full_message(:name, "is invalid") # =>
- # "Name is invalid"
+ # 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
@@ -298,10 +370,11 @@ module ActiveModel
# (<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 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 interpolation.
+ # if it's not there, it's looked up in <tt>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
+ # interpolation.
#
# When using inheritance in your models, it will check all the inherited
# models too, but only if the model itself hasn't been found. Say you have
@@ -317,7 +390,6 @@ module ActiveModel
# * <tt>activemodel.errors.messages.blank</tt>
# * <tt>errors.attributes.title.blank</tt>
# * <tt>errors.messages.blank</tt>
- #
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
@@ -366,6 +438,21 @@ module ActiveModel
end
end
+ # Raised when a validation cannot be corrected by end users and are considered
+ # exceptional.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ #
+ # validates_presence_of :name, strict: true
+ # end
+ #
+ # person = Person.new
+ # person.name = nil
+ # person.valid?
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
class StrictValidationFailed < StandardError
end
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 88b730626c..550fa474ea 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -12,19 +12,20 @@ module ActiveModel
# you want all features out of the box.
#
# These tests do not attempt to determine the semantic correctness of the
- # returned values. For instance, you could implement valid? to always
- # return true, and the tests would pass. It is up to you to ensure that
- # the values are semantically meaningful.
+ # returned values. For instance, you could implement <tt>valid?</tt> to
+ # always return true, and the tests would pass. It is up to you to ensure
+ # that the values are semantically meaningful.
#
- # Objects you pass in are expected to return a compliant object from a
- # call to to_model. It is perfectly fine for to_model to return self.
+ # Objects you pass in are expected to return a compliant object from a call
+ # to <tt>to_model</tt>. It is perfectly fine for <tt>to_model</tt> to return
+ # +self+.
module Tests
# == Responds to <tt>to_key</tt>
#
# Returns an Enumerable of all (primary) key attributes
- # or nil if model.persisted? is false. This is used by
- # dom_id to generate unique ids for the object.
+ # or nil if <tt>model.persisted?</tt> is false. This is used by
+ # <tt>dom_id</tt> to generate unique ids for the object.
def test_to_key
assert model.respond_to?(:to_key), "The model should respond to to_key"
def model.persisted?() false end
@@ -34,13 +35,14 @@ module ActiveModel
# == Responds to <tt>to_param</tt>
#
# Returns a string representing the object's key suitable for use in URLs
- # or nil if model.persisted? is false.
+ # or +nil+ if <tt>model.persisted?</tt> is +false+.
#
- # Implementers can decide to either raise an exception or provide a default
- # in case the record uses a composite primary key. There are no tests for this
- # behavior in lint because it doesn't make sense to force any of the possible
- # implementation strategies on the implementer. However, if the resource is
- # not persisted?, then to_param should always return nil.
+ # Implementers can decide to either raise an exception or provide a
+ # default in case the record uses a composite primary key. There are no
+ # tests for this behavior in lint because it doesn't make sense to force
+ # any of the possible implementation strategies on the implementer.
+ # However, if the resource is not persisted?, then <tt>to_param</tt>
+ # should always return +nil+.
def test_to_param
assert model.respond_to?(:to_param), "The model should respond to to_param"
def model.to_key() [1] end
@@ -50,9 +52,8 @@ module ActiveModel
# == Responds to <tt>to_partial_path</tt>
#
- # Returns a string giving a relative path. This is used for looking up
+ # Returns a string giving a relative path. This is used for looking up
# partials. For example, a BlogPost model might return "blog_posts/blog_post"
- #
def test_to_partial_path
assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
assert_kind_of String, model.to_partial_path
@@ -60,11 +61,11 @@ module ActiveModel
# == Responds to <tt>persisted?</tt>
#
- # Returns a boolean that specifies whether the object has been persisted yet.
- # This is used when calculating the URL for an object. If the object is
- # not persisted, a form for that object, for instance, will route to the
- # create action. If it is persisted, a form for the object will routes to
- # the update action.
+ # Returns a boolean that specifies whether the object has been persisted
+ # yet. This is used when calculating the URL for an object. If the object
+ # is not persisted, a form for that object, for instance, will route to
+ # the create action. If it is persisted, a form for the object will routes
+ # to the update action.
def test_persisted?
assert model.respond_to?(:persisted?), "The model should respond to persisted?"
assert_boolean model.persisted?, "persisted?"
@@ -73,8 +74,8 @@ module ActiveModel
# == Naming
#
# Model.model_name must return a string with some convenience methods:
- # :human, :singular, and :plural. Check ActiveModel::Naming for more information.
- #
+ # <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"
model_name = model.class.model_name
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index cfce1542b1..f9841abcb0 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -1,73 +1,73 @@
-require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
require 'active_model/mass_assignment_security/permission_set'
require 'active_model/mass_assignment_security/sanitizer'
module ActiveModel
- # = Active Model Mass-Assignment Security
+ # == Active Model Mass-Assignment Security
+ #
+ # Mass assignment security provides an interface for protecting attributes
+ # from end-user assignment. For more complex permissions, mass assignment
+ # security may be handled outside the model by extending a non-ActiveRecord
+ # class, such as a controller, with this behavior.
+ #
+ # For example, a logged in user may need to assign additional attributes
+ # depending on their role:
+ #
+ # class AccountsController < ApplicationController
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessible :first_name, :last_name
+ # attr_accessible :first_name, :last_name, :plan_id, as: :admin
+ #
+ # def update
+ # ...
+ # @account.update_attributes(account_params)
+ # ...
+ # end
+ #
+ # protected
+ #
+ # def account_params
+ # role = admin ? :admin : :default
+ # sanitize_for_mass_assignment(params[:account], role)
+ # end
+ #
+ # end
+ #
+ # === Configuration options
+ #
+ # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible
+ # values are:
+ # * <tt>:logger</tt> (default) - writes filtered attributes to logger
+ # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt>
+ # on any protected attribute update.
+ #
+ # You can specify your own sanitizer object eg. <tt>MySanitizer.new</tt>.
+ # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for
+ # example implementation.
module MassAssignmentSecurity
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
+ class_attribute :_accessible_attributes, instance_writer: false
+ class_attribute :_protected_attributes, instance_writer: false
+ class_attribute :_active_authorizer, instance_writer: false
- config_attribute :_accessible_attributes
- config_attribute :_protected_attributes
- config_attribute :_active_authorizer
-
- config_attribute :_mass_assignment_sanitizer
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
self.mass_assignment_sanitizer = :logger
end
- # Mass assignment security provides an interface for protecting attributes
- # from end-user assignment. For more complex permissions, mass assignment security
- # may be handled outside the model by extending a non-ActiveRecord class,
- # such as a controller, with this behavior.
- #
- # For example, a logged in user may need to assign additional attributes depending
- # on their role:
- #
- # class AccountsController < ApplicationController
- # include ActiveModel::MassAssignmentSecurity
- #
- # attr_accessible :first_name, :last_name
- # attr_accessible :first_name, :last_name, :plan_id, :as => :admin
- #
- # def update
- # ...
- # @account.update_attributes(account_params)
- # ...
- # end
- #
- # protected
- #
- # def account_params
- # role = admin ? :admin : :default
- # sanitize_for_mass_assignment(params[:account], role)
- # end
- #
- # end
- #
- # = Configuration options
- #
- # * <tt>mass_assignment_sanitizer</tt> - Defines sanitize method. Possible values are:
- # * <tt>:logger</tt> (default) - writes filtered attributes to logger
- # * <tt>:strict</tt> - raise <tt>ActiveModel::MassAssignmentSecurity::Error</tt> on any protected attribute update
- #
- # You can specify your own sanitizer object eg. MySanitizer.new.
- # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation.
- #
- #
module ClassMethods
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
- # attributes is optional, if no role is provided then :default is used.
- # A role can be defined by using the :as option with a symbol or an array of symbols as the value.
+ # attributes is optional, if no role is provided then <tt>:default</tt>
+ # is used. A role can be defined by using the <tt>:as</tt> option with a
+ # symbol or an array of symbols as the value.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
# sensitive attributes from being overwritten by malicious users
- # tampering with URLs or forms. Example:
+ # tampering with URLs or forms.
#
# class Customer
# include ActiveModel::MassAssignmentSecurity
@@ -76,7 +76,7 @@ module ActiveModel
#
# attr_protected :logins_count
# # Suppose that admin can not change email for customer
- # attr_protected :logins_count, :email, :as => :admin
+ # attr_protected :logins_count, :email, as: :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -85,23 +85,23 @@ module ActiveModel
# end
# end
#
- # When using the :default role:
+ # When using the <tt>:default</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default)
- # customer.name # => "David"
- # customer.email # => "a@b.com"
- # customer.logins_count # => nil
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5 }, as: :default)
+ # customer.name # => "David"
+ # customer.email # => "a@b.com"
+ # customer.logins_count # => nil
#
- # And using the :admin role:
+ # And using the <tt>:admin</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin)
- # customer.name # => "David"
- # customer.email # => nil
- # customer.logins_count # => nil
+ # customer.assign_attributes({ name: 'David', email: 'a@b.com', logins_count: 5}, as: :admin)
+ # customer.name # => "David"
+ # customer.email # => nil
+ # customer.logins_count # => nil
#
- # customer.email = "c@d.com"
+ # customer.email = 'c@d.com'
# customer.email # => "c@d.com"
#
# To start from an all-closed default and enable attributes as needed,
@@ -127,8 +127,9 @@ module ActiveModel
# mass-assignment.
#
# Like +attr_protected+, a role for the attributes is optional,
- # if no role is provided then :default is used. A role can be defined by
- # using the :as option with a symbol or an array of symbols as the value.
+ # if no role is provided then <tt>:default</tt> is used. A role can be
+ # defined by using the <tt>:as</tt> option with a symbol or an array of
+ # symbols as the value.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
@@ -143,8 +144,10 @@ module ActiveModel
#
# attr_accessor :name, :credit_rating
#
- # attr_accessible :name
- # attr_accessible :name, :credit_rating, :as => :admin
+ # # Both admin and default user can change name of a customer
+ # attr_accessible :name, as: [:admin, :default]
+ # # Only admin can change credit rating of a customer
+ # attr_accessible :credit_rating, as: :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -153,20 +156,20 @@ module ActiveModel
# end
# end
#
- # When using the :default role:
+ # When using the <tt>:default</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :default)
# customer.name # => "David"
# customer.credit_rating # => nil
#
- # customer.credit_rating = "Average"
+ # customer.credit_rating = 'Average'
# customer.credit_rating # => "Average"
#
- # And using the :admin role:
+ # And using the <tt>:admin</tt> role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.assign_attributes({ name: 'David', credit_rating: 'Excellent', last_login: 1.day.ago }, as: :admin)
# customer.name # => "David"
# customer.credit_rating # => "Excellent"
#
@@ -186,23 +189,131 @@ module ActiveModel
self._active_authorizer = self._accessible_attributes
end
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::BlackList</tt>
+ # with the attributes protected by #attr_protected method. If no +role+
+ # is provided, then <tt>:default</tt> is used.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :email, :logins_count
+ #
+ # attr_protected :logins_count
+ # attr_protected :logins_count, :email, as: :admin
+ # end
+ #
+ # Customer.protected_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
+ #
+ # Customer.protected_attributes(:default)
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count"}>
+ #
+ # Customer.protected_attributes(:admin)
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {"logins_count", "email"}>
def protected_attributes(role = :default)
protected_attributes_configs[role]
end
+ # Returns an instance of <tt>ActiveModel::MassAssignmentSecurity::WhiteList</tt>
+ # with the attributes protected by #attr_accessible method. If no +role+
+ # is provided, then <tt>:default</tt> is used.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ #
+ # attr_accessible :name, as: [:admin, :default]
+ # attr_accessible :credit_rating, as: :admin
+ # end
+ #
+ # Customer.accessible_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ #
+ # Customer.accessible_attributes(:default)
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ #
+ # Customer.accessible_attributes(:admin)
+ # # => #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>
def accessible_attributes(role = :default)
accessible_attributes_configs[role]
end
+ # Returns a hash with the protected attributes (by #attr_accessible or
+ # #attr_protected) per role.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name, :credit_rating
+ #
+ # attr_accessible :name, as: [:admin, :default]
+ # attr_accessible :credit_rating, as: :admin
+ # end
+ #
+ # Customer.active_authorizers
+ # # => {
+ # # :admin=> #<ActiveModel::MassAssignmentSecurity::WhiteList: {"name", "credit_rating"}>,
+ # # :default=>#<ActiveModel::MassAssignmentSecurity::WhiteList: {"name"}>
+ # #  }
def active_authorizers
self._active_authorizer ||= protected_attributes_configs
end
alias active_authorizer active_authorizers
+ # Returns an empty array by default. You can still override this to define
+ # the default attributes protected by #attr_protected method.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # def self.attributes_protected_by_default
+ # [:name]
+ # end
+ # end
+ #
+ # Customer.protected_attributes
+ # # => #<ActiveModel::MassAssignmentSecurity::BlackList: {:name}>
def attributes_protected_by_default
[]
end
+ # Defines sanitize method.
+ #
+ # class Customer
+ # include ActiveModel::MassAssignmentSecurity
+ #
+ # attr_accessor :name
+ #
+ # attr_protected :name
+ #
+ # def assign_attributes(values)
+ # sanitize_for_mass_assignment(values).each do |k, v|
+ # send("#{k}=", v)
+ # end
+ # end
+ # end
+ #
+ # # See ActiveModel::MassAssignmentSecurity::StrictSanitizer for more information.
+ # Customer.mass_assignment_sanitizer = :strict
+ #
+ # customer = Customer.new
+ # customer.assign_attributes(name: 'David')
+ # # => ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Customer: name
+ #
+ # Also, you can specify your own sanitizer object.
+ #
+ # class CustomSanitizer < ActiveModel::MassAssignmentSecurity::Sanitizer
+ # def process_removed_attributes(klass, attrs)
+ # raise StandardError
+ # end
+ # end
+ #
+ # Customer.mass_assignment_sanitizer = CustomSanitizer.new
+ #
+ # customer = Customer.new
+ # customer.assign_attributes(name: 'David')
+ # # => StandardError: StandardError
def mass_assignment_sanitizer=(value)
self._mass_assignment_sanitizer = if value.is_a?(Symbol)
const_get(:"#{value.to_s.camelize}Sanitizer").new(self)
@@ -228,11 +339,11 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, role = nil)
+ def sanitize_for_mass_assignment(attributes, role = nil) #:nodoc:
_mass_assignment_sanitizer.sanitize(self.class, attributes, mass_assignment_authorizer(role))
end
- def mass_assignment_authorizer(role)
+ def mass_assignment_authorizer(role) #:nodoc:
self.class.active_authorizer[role || :default]
end
end
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 9661349503..f104d0306c 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -2,10 +2,10 @@ require 'set'
module ActiveModel
module MassAssignmentSecurity
- class PermissionSet < Set
+ class PermissionSet < Set #:nodoc:
def +(values)
- super(values.map(&:to_s))
+ super(values.compact.map(&:to_s))
end
def include?(key)
@@ -23,14 +23,14 @@ module ActiveModel
end
end
- class WhiteList < PermissionSet
+ class WhiteList < PermissionSet #:nodoc:
def deny?(key)
!include?(key)
end
end
- class BlackList < PermissionSet
+ class BlackList < PermissionSet #:nodoc:
def deny?(key)
include?(key)
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index 44ce5a489d..dafb7cdff3 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -1,6 +1,6 @@
module ActiveModel
module MassAssignmentSecurity
- class Sanitizer
+ class Sanitizer #:nodoc:
# Returns all attributes not denied by the authorizer.
def sanitize(klass, attributes, authorizer)
rejected = []
@@ -18,7 +18,7 @@ module ActiveModel
end
end
- class LoggerSanitizer < Sanitizer
+ class LoggerSanitizer < Sanitizer #:nodoc:
def initialize(target)
@target = target
super()
@@ -50,7 +50,7 @@ module ActiveModel
end
end
- class StrictSanitizer < Sanitizer
+ class StrictSanitizer < Sanitizer #:nodoc:
def initialize(target = nil)
super()
end
@@ -65,7 +65,7 @@ module ActiveModel
end
end
- class Error < StandardError
+ class Error < StandardError #:nodoc:
def initialize(klass, attrs)
super("Can't mass-assign protected attributes for #{klass.name}: #{attrs.join(', ')}")
end
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
index 3af95b09b0..33a530e6bd 100644
--- a/activemodel/lib/active_model/model.rb
+++ b/activemodel/lib/active_model/model.rb
@@ -2,11 +2,11 @@ module ActiveModel
# == Active Model Basic Model
#
- # Includes the required interface for an object to interact with <tt>ActionPack</tt>,
- # using different <tt>ActiveModel</tt> modules. It includes model name introspections,
- # conversions, translations and validations. Besides that, it allows you to
- # initialize the object with a hash of attributes, pretty much like
- # <tt>ActiveRecord</tt> does.
+ # Includes the required interface for an object to interact with
+ # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
+ # It includes model name introspections, conversions, translations and
+ # validations. Besides that, it allows you to initialize the object with a
+ # hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
#
# A minimal implementation could be:
#
@@ -15,13 +15,13 @@ module ActiveModel
# attr_accessor :name, :age
# end
#
- # person = Person.new(:name => 'bob', :age => '18')
+ # person = Person.new(name: 'bob', age: '18')
# person.name # => 'bob'
- # person.age # => 18
+ # person.age # => 18
#
- # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> to
- # return <tt>false</tt>, which is the most common case. You may want to override it
- # in your class to simulate a different scenario:
+ # 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
+ # it in your class to simulate a different scenario:
#
# class Person
# include ActiveModel::Model
@@ -32,11 +32,12 @@ module ActiveModel
# end
# end
#
- # person = Person.new(:id => 1, :name => 'bob')
+ # person = Person.new(id: 1, name: 'bob')
# person.persisted? # => true
#
- # Also, if for some reason you need to run code on <tt>initialize</tt>, make sure you
- # call super if you want the attributes hash initialization to happen.
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
+ # sure you call +super+ if you want the attributes hash initialization to
+ # happen.
#
# class Person
# include ActiveModel::Model
@@ -48,13 +49,14 @@ module ActiveModel
# end
# end
#
- # person = Person.new(:id => 1, :name => 'bob')
+ # person = Person.new(id: 1, name: 'bob')
# person.omg # => true
#
- # For more detailed information on other functionalities available, please refer
- # to the specific modules included in <tt>ActiveModel::Model</tt> (see below).
+ # For more detailed information on other functionalities available, please
+ # refer to the specific modules included in <tt>ActiveModel::Model</tt>
+ # (see below).
module Model
- def self.included(base)
+ def self.included(base) #:nodoc:
base.class_eval do
extend ActiveModel::Naming
extend ActiveModel::Translation
@@ -63,12 +65,31 @@ module ActiveModel
end
end
+ # Initializes a new model with the given +params+.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :name, :age
+ # end
+ #
+ # person = Person.new(name: 'bob', age: '18')
+ # person.name # => "bob"
+ # person.age # => 18
def initialize(params={})
params.each do |attr, value|
self.public_send("#{attr}=", value)
end if params
end
+ # Indicates if the model is persisted. Default is +false+.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name
+ # end
+ #
+ # person = Person.new(id: 1, name: 'bob')
+ # person.persisted? # => false
def persisted?
false
end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 2b5fc57a3a..c0d93e5d53 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,8 +1,6 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/object/blank'
module ActiveModel
class Name
@@ -14,9 +12,137 @@ module ActiveModel
alias_method :cache_key, :collection
+ ##
+ # :method: ==
+ #
+ # :call-seq:
+ # ==(other)
+ #
+ # Equivalent to <tt>String#==</tt>. Returns +true+ if the class name and
+ # +other+ are equal, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name == 'BlogPost' # => true
+ # BlogPost.model_name == 'Blog Post' # => false
+
+ ##
+ # :method: ===
+ #
+ # :call-seq:
+ # ===(other)
+ #
+ # Equivalent to <tt>#==</tt>.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name === 'BlogPost' # => true
+ # BlogPost.model_name === 'Blog Post' # => false
+
+ ##
+ # :method: <=>
+ #
+ # :call-seq:
+ # ==(other)
+ #
+ # Equivalent to <tt>String#<=></tt>.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name <=> 'BlogPost' # => 0
+ # BlogPost.model_name <=> 'Blog' # => 1
+ # BlogPost.model_name <=> 'BlogPosts' # => -1
+
+ ##
+ # :method: =~
+ #
+ # :call-seq:
+ # =~(regexp)
+ #
+ # Equivalent to <tt>String#=~</tt>. Match the class name against the given
+ # regexp. Returns the position where the match starts or +nil+ if there is
+ # no match.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name =~ /Post/ # => 4
+ # BlogPost.model_name =~ /\d/ # => nil
+
+ ##
+ # :method: !~
+ #
+ # :call-seq:
+ # !~(regexp)
+ #
+ # Equivalent to <tt>String#!~</tt>. Match the class name against the given
+ # regexp. Returns +true+ if there is no match, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name !~ /Post/ # => false
+ # BlogPost.model_name !~ /\d/ # => true
+
+ ##
+ # :method: eql?
+ #
+ # :call-seq:
+ # eql?(other)
+ #
+ # Equivalent to <tt>String#eql?</tt>. Returns +true+ if the class name and
+ # +other+ have the same length and content, otherwise +false+.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name.eql?('BlogPost') # => true
+ # BlogPost.model_name.eql?('Blog Post') # => false
+
+ ##
+ # :method: to_s
+ #
+ # :call-seq:
+ # to_s()
+ #
+ # Returns the class name.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
+ #
+ # BlogPost.model_name.to_s # => "BlogPost"
+
+ ##
+ # :method: to_str
+ #
+ # :call-seq:
+ # to_str()
+ #
+ # Equivalent to +to_s+.
delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
: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
+ # respectively.
+ #
+ # module Foo
+ # class Bar
+ # end
+ # end
+ #
+ # ActiveModel::Name.new(Foo::Bar).to_s
+ # # => "Foo::Bar"
def initialize(klass, namespace = nil, name = nil)
@name = name || klass.name
@@ -38,7 +164,11 @@ module ActiveModel
end
# Transform the model name into a more humane format, using I18n. By default,
- # it will underscore then humanize the class name
+ # it will underscore then humanize the class name.
+ #
+ # class BlogPost
+ # extend ActiveModel::Naming
+ # end
#
# BlogPost.model_name.human # => "Blog post"
#
@@ -82,11 +212,20 @@ module ActiveModel
# 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 provided
- # method below, or rolling your own is required.
+ # is required to pass the Active Model Lint test. So either extending the
+ # provided method below, or rolling your own is required.
module Naming
# Returns an ActiveModel::Name object for module. It can be
- # used to retrieve all kinds of naming-related information.
+ # used to retrieve all kinds of naming-related information
+ # (See ActiveModel::Name for more information).
+ #
+ # class Person < ActiveModel::Model
+ # end
+ #
+ # Person.model_name # => Person
+ # Person.model_name.class # => ActiveModel::Name
+ # Person.model_name.singular # => "person"
+ # Person.model_name.plural # => "people"
def model_name
@_model_name ||= begin
namespace = self.parents.detect do |n|
@@ -96,7 +235,7 @@ module ActiveModel
end
end
- # Returns the plural class name of a record or class. Examples:
+ # Returns the plural class name of a record or class.
#
# ActiveModel::Naming.plural(post) # => "posts"
# ActiveModel::Naming.plural(Highrise::Person) # => "highrise_people"
@@ -104,7 +243,7 @@ module ActiveModel
model_name_from_record_or_class(record_or_class).plural
end
- # Returns the singular class name of a record or class. Examples:
+ # Returns the singular class name of a record or class.
#
# ActiveModel::Naming.singular(post) # => "post"
# ActiveModel::Naming.singular(Highrise::Person) # => "highrise_person"
@@ -112,10 +251,10 @@ module ActiveModel
model_name_from_record_or_class(record_or_class).singular
end
- # Identifies whether the class name of a record or class is uncountable. Examples:
+ # Identifies whether the class name of a record or class is uncountable.
#
# ActiveModel::Naming.uncountable?(Sheep) # => true
- # ActiveModel::Naming.uncountable?(Post) => false
+ # ActiveModel::Naming.uncountable?(Post) # => false
def self.uncountable?(record_or_class)
plural(record_or_class) == singular(record_or_class)
end
@@ -123,11 +262,11 @@ module ActiveModel
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> post
+ # # For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> post
#
- # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
+ # # For shared engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post
def self.singular_route_key(record_or_class)
model_name_from_record_or_class(record_or_class).singular_route_key
end
@@ -135,11 +274,11 @@ module ActiveModel
# Returns string to use while generating route names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> posts
+ # # For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> posts
#
- # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
+ # # For shared engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
#
# The route key also considers if the noun is uncountable and, in
# such cases, automatically appends _index.
@@ -150,23 +289,24 @@ module ActiveModel
# Returns string to use for params names. It differs for
# namespaced models regarding whether it's inside isolated engine.
#
- # For isolated engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> post
+ # # For isolated engine:
+ # ActiveModel::Naming.param_key(Blog::Post) #=> post
#
- # For shared engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
+ # # For shared engine:
+ # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
def self.param_key(record_or_class)
model_name_from_record_or_class(record_or_class).param_key
end
- private
- def self.model_name_from_record_or_class(record_or_class)
- (record_or_class.is_a?(Class) ? record_or_class : convert_to_model(record_or_class).class).model_name
- end
-
- def self.convert_to_model(object)
- object.respond_to?(:to_model) ? object.to_model : object
+ 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
+ else
+ record_or_class.class.model_name
end
+ end
+ private_class_method :model_name_from_record_or_class
end
-
end
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index 3d463885be..77bc0f71e3 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -5,18 +5,24 @@ module ActiveModel
# a particular model class.
class ObserverArray < Array
attr_reader :model_class
- def initialize(model_class, *args)
+ 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.
- def disabled_for?(observer)
+ # 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
#
@@ -27,9 +33,6 @@ module ActiveModel
# ORM.observers.disable :observer_1, :observer_2
# # => disables Observer1 and Observer2 for all models.
#
- # ORM.observers.disable :all
- # # => disables all observers for all models.
- #
# User.observers.disable :all do
# # all user observers are disabled for
# # just the duration of the block
@@ -40,6 +43,11 @@ module ActiveModel
# 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
#
@@ -51,9 +59,6 @@ module ActiveModel
# ORM.observers.enable :observer_1, :observer_2
# # => enables Observer1 and Observer2 for all models.
#
- # ORM.observers.enable :all
- # # => enables all observers for all models.
- #
# User.observers.enable :all do
# # all user observers are enabled for
# # just the duration of the block
@@ -67,11 +72,11 @@ module ActiveModel
protected
- def disabled_observers
+ def disabled_observers #:nodoc:
@disabled_observers ||= Set.new
end
- def observer_class_for(observer)
+ def observer_class_for(observer) #:nodoc:
return observer if observer.is_a?(Class)
if observer.respond_to?(:to_sym) # string/symbol
@@ -82,25 +87,25 @@ module ActiveModel
end
end
- def start_transaction
+ 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
+ def disabled_observer_stack #:nodoc:
@disabled_observer_stack ||= []
end
- def end_transaction
+ def end_transaction #:nodoc:
@disabled_observers = disabled_observer_stack.pop
each_subclass_array do |array|
array.end_transaction
end
end
- def transaction
+ def transaction #:nodoc:
start_transaction
begin
@@ -110,13 +115,13 @@ module ActiveModel
end
end
- def each_subclass_array
+ def each_subclass_array #:nodoc:
model_class.descendants.each do |subclass|
yield subclass.observers
end
end
- def set_enablement(enabled, observers)
+ def set_enablement(enabled, observers) #:nodoc:
if block_given?
transaction do
set_enablement(enabled, observers)
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index f5ea285ccb..9db7639ea3 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -4,11 +4,11 @@ require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/remove_method'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/enumerable'
-require 'active_support/deprecation'
require 'active_support/core_ext/object/try'
require 'active_support/descendants_tracker'
module ActiveModel
+ # == Active Model Observers Activation
module Observing
extend ActiveSupport::Concern
@@ -17,9 +17,7 @@ module ActiveModel
end
module ClassMethods
- # == Active Model Observers Activation
- #
- # Activates the observers assigned. Examples:
+ # Activates the observers assigned.
#
# class ORM
# include ActiveModel::Observing
@@ -35,34 +33,95 @@ module ActiveModel
# ORM.observers = Cacher, GarbageCollector
#
# Note: Setting this does not instantiate the observers yet.
- # +instantiate_observers+ is called during startup, and before
+ # <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 <tt>ActiveModel::ObserverArray.enable</tt> and
- # <tt>ActiveModel::ObserverArray.disable</tt> for more on this)
+ # 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
- # Gets the current observer instances.
+ # 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 'update', otherwise it
- # raises an +ArgumentError+ exception.
+ # 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'"
@@ -70,16 +129,47 @@ module ActiveModel
observer_instances << observer
end
- # Notify list of observers of a change.
+ # 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
- # Total number of observers.
+ # 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)
@@ -104,27 +194,36 @@ module ActiveModel
end
# Notify observers when the observed class is subclassed.
- def inherited(subclass)
+ def inherited(subclass) #:nodoc:
super
notify_observers :observed_class_inherited, subclass
end
end
- # Fires notifications to model's observers
+ # Notify a change to the list of observers.
+ #
+ # class Foo
+ # include ActiveModel::Observing
#
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
+ # attr_accessor :status
# end
#
- # Custom notifications can be sent in a similar fashion:
+ # class FooObserver < ActiveModel::Observer
+ # def on_spec(record, *args)
+ # record.status = true
+ # end
+ # end
#
- # notify_observers(:custom_notification, :foo)
+ # Foo.observers = FooObserver
+ # Foo.instantiate_observers # => [FooObserver]
#
- # This will call +custom_notification+, passing as arguments
- # the current object and :foo.
+ # 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
@@ -136,15 +235,15 @@ module ActiveModel
# 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. Example:
+ # class.
#
# class CommentObserver < ActiveModel::Observer
# def after_save(comment)
- # Notifications.comment("admin@do.com", "New comment was posted", comment).deliver
+ # Notifications.comment('admin@do.com', 'New comment was posted', comment).deliver
# end
# end
#
- # This Observer sends an email when a Comment#save is finished.
+ # This Observer sends an email when a <tt>Comment#save</tt> is finished.
#
# class ContactObserver < ActiveModel::Observer
# def after_create(contact)
@@ -161,44 +260,50 @@ module ActiveModel
# == Observing a class that can't be inferred
#
# Observers will by default be mapped to the class with which they share a
- # name. So CommentObserver will be tied to observing Comment, ProductManagerObserver
- # to ProductManager, 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 (Product) or a symbol for that
- # class (:product):
+ # 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")
+ # 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:
+ # 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")
+ # AuditTrail.new(record, 'UPDATED')
# end
# end
#
- # The AuditObserver will now act on both updates to Account and Balance by treating
- # them both as records.
+ # 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
+ # 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 }
@@ -207,6 +312,8 @@ module ActiveModel
# 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
@@ -218,8 +325,11 @@ module ActiveModel
Array(observed_class)
end
- # The class observed by default is inferred from the observer's class name:
- # assert_equal Person, PersonObserver.observed_class
+ # 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
@@ -227,7 +337,7 @@ module ActiveModel
# Start observing the declared classes and their subclasses.
# Called automatically by the instance method.
- def initialize
+ def initialize #:nodoc:
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -238,8 +348,7 @@ module ActiveModel
# 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 unless respond_to?(observed_method)
- return if disabled_for?(object)
+ return if !respond_to?(observed_method) || disabled_for?(object)
send(observed_method, object, *extra_args, &block)
end
@@ -256,7 +365,7 @@ module ActiveModel
end
# Returns true if notifications are disabled for this object.
- def disabled_for?(object)
+ def disabled_for?(object) #:nodoc:
klass = object.class
return false unless klass.respond_to?(:observers)
klass.observers.disabled_for?(self)
diff --git a/activemodel/lib/active_model/railtie.rb b/activemodel/lib/active_model/railtie.rb
index 63ffe5db63..f239758b35 100644
--- a/activemodel/lib/active_model/railtie.rb
+++ b/activemodel/lib/active_model/railtie.rb
@@ -1,2 +1,8 @@
require "active_model"
-require "rails" \ No newline at end of file
+require "rails"
+
+module ActiveModel
+ class Railtie < Rails::Railtie
+ config.eager_load_namespaces << ActiveModel
+ end
+end \ No newline at end of file
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 3eab745c89..d011402081 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -6,12 +6,12 @@ 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 'validations: false' as an argument.
- # You can add more validations by hand if need be.
+ # 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.
#
- # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use has_secure_password:
+ # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password:
#
# gem 'bcrypt-ruby', '~> 3.0.0'
#
@@ -22,35 +22,36 @@ module ActiveModel
# has_secure_password
# end
#
- # user = User.new(:name => "david", :password => "", :password_confirmation => "nomatch")
+ # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch')
# user.save # => false, password required
- # user.password = "mUc3m00RsqyRe"
+ # user.password = 'mUc3m00RsqyRe'
# user.save # => false, confirmation doesn't match
- # user.password_confirmation = "mUc3m00RsqyRe"
+ # 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.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.
+ # 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'
attr_reader :password
-
+
if options.fetch(:validations, true)
validates_confirmation_of :password
validates_presence_of :password, :on => :create
+
+ before_create { raise "Password digest missing on new record" if password_digest.blank? }
end
-
- before_create { raise "Password digest missing on new record" if password_digest.blank? }
include InstanceMethodsOnActivation
if respond_to?(:attributes_protected_by_default)
- def self.attributes_protected_by_default
+ def self.attributes_protected_by_default #:nodoc:
super + ['password_digest']
end
end
@@ -58,13 +59,32 @@ module ActiveModel
end
module InstanceMethodsOnActivation
- # Returns self if the password is correct, otherwise false.
+ # Returns +self+ if the password is correct, otherwise +false+.
+ #
+ # class User < ActiveRecord::Base
+ # has_secure_password validations: false
+ # end
+ #
+ # user = User.new(name: 'david', password: 'mUc3m00RsqyRe')
+ # user.save
+ # user.authenticate('notright') # => false
+ #  user.authenticate('mUc3m00RsqyRe') # => user
def authenticate(unencrypted_password)
BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute, only if the
+ # Encrypts the password into the +password_digest+ attribute, only if the
# new password is not blank.
+ #
+ # class User < ActiveRecord::Base
+ # has_secure_password validations: false
+ # end
+ #
+ # user = User.new
+ # user.password = nil
+ # user.password_digest # => nil
+ # user.password = 'mUc3m00RsqyRe'
+ # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4."
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 6d8fd21814..8a63014ffb 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -25,17 +25,16 @@ 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+.
+ # 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+.
#
# Most of the time though, you will want to include the JSON or XML
# serializations. Both of these modules automatically include the
- # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly
- # include it.
+ # <tt>ActiveModel::Serialization</tt> module, so there is no need to
+ # explicitly include it.
#
# A minimal implementation including XML and JSON would be:
#
@@ -64,13 +63,37 @@ module ActiveModel
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
- # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>:include</tt>.
- # The following are all valid examples:
+ # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
+ # <tt>:include</tt>. The following are all valid examples:
#
- # person.serializable_hash(:only => 'name')
- # person.serializable_hash(:include => :address)
- # person.serializable_hash(:include => { :address => { :only => 'city' }})
+ # person.serializable_hash(only: 'name')
+ # person.serializable_hash(include: :address)
+ # person.serializable_hash(include: { address: { only: 'city' }})
module Serialization
+ # Returns a serialized hash of your object.
+ #
+ # class Person
+ # include ActiveModel::Serialization
+ #
+ # attr_accessor :name, :age
+ #
+ # def attributes
+ # {'name' => nil, 'age' => nil}
+ # end
+ #
+ # def capitalized_name
+ # name.capitalize
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = 'bob'
+ # person.age = 22
+ # person.serializable_hash # => {"name"=>"bob", "age"=>22}
+ # 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"}
def serializable_hash(options = nil)
options ||= {}
@@ -115,7 +138,6 @@ module ActiveModel
# @data[key]
# end
# end
- #
alias :read_attribute_for_serialization :send
# Add associations specified via the <tt>:include</tt> option.
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 63ab8e7edc..a4252b995d 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -1,5 +1,4 @@
require 'active_support/json'
-require 'active_support/core_ext/class/attribute'
module ActiveModel
# == Active Model JSON Serializer
@@ -10,86 +9,89 @@ module ActiveModel
included do
extend ActiveModel::Naming
- extend ActiveModel::Configuration
- config_attribute :include_root_in_json
- self.include_root_in_json = true
+ class_attribute :include_root_in_json
+ self.include_root_in_json = false
end
# Returns a hash representing the model. Some configuration can be
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
- # of +as_json+. If true (the default) +as_json+ will emit a single root
- # node named after the object's type. For example:
+ # of +as_json+. If +true+, +as_json+ will emit a single root node named
+ # after the object's type. The default value for <tt>include_root_in_json</tt>
+ # option is +false+.
#
# user = User.find(1)
# user.as_json
- # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true} }
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
+ #
+ # ActiveRecord::Base.include_root_in_json = true
#
- # ActiveRecord::Base.include_root_in_json = false
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
- # This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in:
+ # This behavior can also be achieved by setting the <tt>:root</tt> option
+ # to +true+ as in:
#
# user = User.find(1)
- # user.as_json(root: false)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
- #
- # The remainder of the examples in this section assume include_root_in_json is set to
- # <tt>false</tt>.
+ # user.as_json(root: true)
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
# Without any +options+, the returned Hash will include all the model's
- # attributes. For example:
+ # attributes.
#
# user = User.find(1)
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
#
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method. For example:
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
+ # the attributes included, and work similar to the +attributes+ method.
#
- # user.as_json(:only => [ :id, :name ])
- # # => {"id": 1, "name": "Konata Izumi"}
+ # user.as_json(only: [:id, :name])
+ # # => { "id" => 1, "name" => "Konata Izumi" }
#
- # user.as_json(:except => [ :id, :created_at, :age ])
- # # => {"name": "Konata Izumi", "awesome": true}
+ # user.as_json(except: [:id, :created_at, :age])
+ # # => { "name" => "Konata Izumi", "awesome" => true }
#
# To include the result of some method calls on the model use <tt>:methods</tt>:
#
- # user.as_json(:methods => :permalink)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "permalink": "1-konata-izumi"}
+ # user.as_json(methods: :permalink)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "permalink" => "1-konata-izumi" }
#
# To include associations use <tt>:include</tt>:
#
- # user.as_json(:include => :posts)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
- # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
+ # user.as_json(include: :posts)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
+ # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
#
# Second level and higher order associations work as well:
#
- # user.as_json(:include => { :posts => {
- # :include => { :comments => {
- # :only => :body } },
- # :only => :title } })
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
- # "title": "Welcome to the weblog"},
- # {"comments": [{"body": "Don't think too hard"}],
- # "title": "So I was thinking"}]}
+ # user.as_json(include: { posts: {
+ # include: { comments: {
+ # only: :body } },
+ # only: :title } })
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
+ # # "title" => "Welcome to the weblog" },
+ # # { "comments" => [ { "body" => "Don't think too hard" } ],
+ # # "title" => "So I was thinking" } ] }
def as_json(options = nil)
- root = include_root_in_json
- root = options[:root] if options.try(:key?, :root)
+ root = if options && options.key?(:root)
+ options[:root]
+ else
+ include_root_in_json
+ end
+
if root
root = self.class.model_name.element if root == true
{ root => serializable_hash(options) }
@@ -98,6 +100,40 @@ module ActiveModel
end
end
+ # Sets the model +attributes+ from a JSON string. Returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Serializers::JSON
+ #
+ # attr_accessor :name, :age, :awesome
+ #
+ # def attributes=(hash)
+ # hash.each do |key, value|
+ # instance_variable_set("@#{key}", value)
+ # end
+ # end
+ #
+ # def attributes
+ # instance_values
+ # end
+ # end
+ #
+ # json = { 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
+ #
+ # The default value for +include_root+ is +false+. You can change it to
+ # +true+ if the given JSON string includes a single root node.
+ #
+ # 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
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 2b3e9ce134..cf742d0569 100644..100755
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -110,11 +110,15 @@ module ActiveModel
end
end
- # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
+ # TODO: This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well.
def add_associations(association, records, opts)
merged_options = opts.merge(options.slice(:builder, :indent))
merged_options[:skip_instruct] = true
+ [:skip_types, :dasherize, :camelize].each do |key|
+ merged_options[key] = options[key] if merged_options[key].nil? && !options[key].nil?
+ end
+
if records.respond_to?(:to_ary)
records = records.to_ary
@@ -161,8 +165,8 @@ module ActiveModel
# Returns XML representing the model. Configuration can be
# passed through +options+.
#
- # Without any +options+, the returned XML string will include all the model's
- # attributes. For example:
+ # Without any +options+, the returned XML string will include all the
+ # model's attributes.
#
# user = User.find(1)
# user.to_xml
@@ -175,18 +179,42 @@ module ActiveModel
# <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
# </user>
#
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method.
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the
+ # attributes included, and work similar to the +attributes+ method.
#
# To include the result of some method calls on the model use <tt>:methods</tt>.
#
# To include associations use <tt>:include</tt>.
#
- # For further documentation see activerecord/lib/active_record/serializers/xml_serializer.xml.
+ # For further documentation, see <tt>ActiveRecord::Serialization#to_xml</tt>
def to_xml(options = {}, &block)
Serializer.new(self, options).serialize(&block)
end
+ # Sets the model +attributes+ from a JSON string. Returns +self+.
+ #
+ # class Person
+ # include ActiveModel::Serializers::Xml
+ #
+ # attr_accessor :name, :age, :awesome
+ #
+ # def attributes=(hash)
+ # hash.each do |key, value|
+ # instance_variable_set("@#{key}", value)
+ # end
+ # end
+ #
+ # def attributes
+ # instance_values
+ # end
+ # end
+ #
+ # xml = { name: 'bob', age: 22, awesome:true }.to_xml
+ # person = Person.new
+ # person.from_xml(xml) # => #<Person:0x007fec5e3b3c40 @age=22, @awesome=true, @name="bob">
+ # person.name # => "bob"
+ # person.age # => 22
+ # person.awesome # => true
def from_xml(xml)
self.attributes = Hash.from_xml(xml).values.first
self
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 611e9ffd55..4762f39044 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -1,5 +1,4 @@
require 'active_support/core_ext/array/extract_options'
-require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'
require 'active_model/errors'
@@ -33,12 +32,11 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors # => #<Hash {: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> object, so
- # there is no need for you to do this manually.
+ # 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>
+ # object, so there is no need for you to do this manually.
module Validations
extend ActiveSupport::Concern
@@ -52,8 +50,7 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- extend ActiveModel::Configuration
- config_attribute :_validators
+ class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
@@ -65,27 +62,27 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
+ # validates_each :first_name, :last_name, allow_blank: true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
#
# 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>)
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
- # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
- # proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a +true+ or +false+ value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a +true+ or +false+
+ # value.
def validates_each(*attr_names, &block)
- options = attr_names.extract_options!.symbolize_keys
- validates_with BlockValidator, options.merge(:attributes => attr_names.flatten), &block
+ validates_with BlockValidator, _merge_attributes(attr_names), &block
end
# Adds a validation method or block to the class. This is useful when
@@ -100,7 +97,7 @@ module ActiveModel
# validate :must_be_friends
#
# def must_be_friends
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
@@ -114,7 +111,7 @@ module ActiveModel
# end
#
# def must_be_friends
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
@@ -124,23 +121,24 @@ module ActiveModel
# include ActiveModel::Validations
#
# validate do
- # errors.add(:base, "Must be friends to leave a comment") unless commenter.friend_of?(commentee)
+ # errors.add(:base, 'Must be friends to leave a comment') unless commenter.friend_of?(commentee)
# end
# end
#
# Options:
# * <tt>:on</tt> - Specifies the context where this validation is active
- # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>)
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
# * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
- # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
- # proc or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a +true+ or +false+ value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a +true+ or +false+
+ # value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
@@ -154,44 +152,121 @@ module ActiveModel
# List all validators that are being used to validate the model using
# +validates_with+ method.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # validates_with MyValidator
+ # validates_with OtherValidator, on: :create
+ # validates_with StrictValidator, strict: true
+ # end
+ #
+ # Person.validators
+ # # => [
+ # # #<MyValidator:0x007fbff403e808 @options={}>,
+ # # #<OtherValidator:0x007fbff403d930 @options={:on=>:create}>,
+ # # #<StrictValidator:0x007fbff3204a30 @options={:strict=>true}>
+ # # ]
def validators
_validators.values.flatten.uniq
end
# List all validators that are being used to validate a specific attribute.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name , :age
+ #
+ # validates_presence_of :name
+ # validates_inclusion_of :age, in: 0..99
+ # end
+ #
+ # 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.map do |attribute|
_validators[attribute.to_sym]
end.flatten
end
- # Check if method is an attribute method or not.
+ # Returns +true+ if +attribute+ is an attribute method, +false+ otherwise.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # end
+ #
+ # User.attribute_method?(:name) # => true
+ # User.attribute_method?(:age) # => false
def attribute_method?(attribute)
method_defined?(attribute)
end
# Copy validators on inheritance.
- def inherited(base)
+ def inherited(base) #:nodoc:
dup = _validators.dup
base._validators = dup.each { |k, v| dup[k] = v.dup }
super
end
end
- # Clean the +Errors+ object if instance is duped
- def initialize_dup(other) # :nodoc:
+ # Clean the +Errors+ object if instance is duped.
+ def initialize_dup(other) #:nodoc:
@errors = nil
super
end
- # Returns the +Errors+ object that holds all information about attribute error messages.
+ # Returns the +Errors+ object that holds all information about attribute
+ # error messages.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.valid? # => false
+ # person.errors # => #<ActiveModel::Errors:0x007fe603816640 @messages={:name=>["can't be blank"]}>
def errors
@errors ||= Errors.new(self)
end
- # Runs all the specified validations and returns true if no errors were added
- # otherwise false. Context can optionally be supplied to define which callbacks
- # to test against (the context is defined on the validations using :on).
+ # Runs all the specified validations and returns +true+ if no errors were
+ # added otherwise +false+.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.valid? # => false
+ # person.name = 'david'
+ # person.valid? # => true
+ #
+ # Context can optionally be supplied to define which callbacks to test
+ # against (the context is defined on the validations using <tt>:on</tt>).
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name, on: :new
+ # end
+ #
+ # person = Person.new
+ # person.valid? # => true
+ # person.valid?(:new) # => false
def valid?(context = nil)
current_context, self.validation_context = validation_context, context
errors.clear
@@ -200,8 +275,35 @@ module ActiveModel
self.validation_context = current_context
end
- # Performs the opposite of <tt>valid?</tt>. Returns true if errors were added,
- # false otherwise.
+ # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were
+ # added, +false+ otherwise.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.invalid? # => true
+ # person.name = 'david'
+ # person.invalid? # => false
+ #
+ # Context can optionally be supplied to define which callbacks to test
+ # against (the context is defined on the validations using <tt>:on</tt>).
+ #
+ # class Person
+ # include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates_presence_of :name, on: :new
+ # end
+ #
+ # person = Person.new
+ # person.invalid? # => false
+ # person.invalid?(:new) # => true
def invalid?(context = nil)
!valid?(context)
end
@@ -222,12 +324,11 @@ module ActiveModel
# @data[key]
# end
# end
- #
alias :read_attribute_for_validation :send
protected
- def run_validations!
+ def run_validations! #:nodoc:
run_callbacks :validate
errors.empty?
end
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 38abd0c1fa..8d5ebf527f 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -2,9 +2,9 @@ module ActiveModel
# == Active Model Acceptance Validator
module Validations
- class AcceptanceValidator < EachValidator
+ class AcceptanceValidator < EachValidator #:nodoc:
def initialize(options)
- super(options.reverse_merge(:allow_nil => true, :accept => "1"))
+ super({ :allow_nil => true, :accept => "1" }.merge!(options))
end
def validate_each(record, attribute, value)
@@ -27,7 +27,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
@@ -37,29 +37,17 @@ module ActiveModel
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "must be
# accepted").
- # * <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>:allow_nil</tt> - Skip validation if attribute is +nil+ (default
- # is true).
+ # 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
# a database column, since the attribute is typecast from "1" to +true+
# before validation.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
- # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false
- # value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should not occur (for example,
- # <tt>:unless => :skip_validation</tt>, or
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or
- # false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_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 dbafd0bd1a..bf3fe7ff04 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -2,24 +2,24 @@ require 'active_support/callbacks'
module ActiveModel
module Validations
+ # == Active Model Validation callbacks
+ #
+ # Provides an interface for any class to have +before_validation+ and
+ # +after_validation+ callbacks.
+ #
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
+ #
+ # class MyModel
+ # include ActiveModel::Validations::Callbacks
+ #
+ # before_validation :do_stuff_before_validation
+ # 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.
module Callbacks
- # == Active Model Validation callbacks
- #
- # Provides an interface for any class to have <tt>before_validation</tt> and
- # <tt>after_validation</tt> callbacks.
- #
- # First, include ActiveModel::Validations::Callbacks from the class you are
- # creating:
- #
- # class MyModel
- # include ActiveModel::Validations::Callbacks
- #
- # before_validation :do_stuff_before_validation
- # after_validation :do_stuff_after_validation
- # end
- #
- # Like other before_* callbacks if <tt>before_validation</tt> returns false
- # then <tt>valid?</tt> will not be called.
extend ActiveSupport::Concern
included do
@@ -28,6 +28,30 @@ module ActiveModel
end
module ClassMethods
+ # Defines a callback that will get called right before validation
+ # happens.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # include ActiveModel::Validations::Callbacks
+ #
+ # attr_accessor :name
+ #
+ # validates_length_of :name, maximum: 6
+ #
+ # before_validation :remove_whitespaces
+ #
+ # private
+ #
+ # def remove_whitespaces
+ # name.strip!
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = ' bob '
+ # person.valid? # => true
+ # person.name # => "bob"
def before_validation(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
@@ -37,6 +61,33 @@ module ActiveModel
set_callback(:validation, :before, *args, &block)
end
+ # Defines a callback that will get called right after validation
+ # happens.
+ #
+ # class Person
+ # include ActiveModel::Validations
+ # include ActiveModel::Validations::Callbacks
+ #
+ # attr_accessor :name, :status
+ #
+ # validates_presence_of :name
+ #
+ # after_validation :set_status
+ #
+ # private
+ #
+ # def set_status
+ # self.status = errors.empty?
+ # end
+ # end
+ #
+ # person = Person.new
+ # person.name = ''
+ # person.valid? # => false
+ # person.status # => false
+ #  person.name = 'bob'
+ # person.valid? # => true
+ # person.status # => true
def after_validation(*args, &block)
options = args.extract_options!
options[:prepend] = true
@@ -49,7 +100,7 @@ module ActiveModel
protected
# Overwrite run validations to include callbacks.
- def run_validations!
+ def run_validations! #:nodoc:
run_callbacks(:validation) { super }
end
end
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index b632a2bd6b..643a6f2b7c 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -2,12 +2,12 @@ require 'active_support/core_ext/range.rb'
module ActiveModel
module Validations
- module Clusivity
+ module Clusivity #:nodoc:
ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
- "and must be supplied as the :in option of the configuration hash"
+ "and must be supplied as the :in (or :within) option of the configuration hash"
def check_validity!
- unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ unless [:include?, :call].any?{ |method| delimiter.respond_to?(method) }
raise ArgumentError, ERROR_MESSAGE
end
end
@@ -15,11 +15,14 @@ module ActiveModel
private
def include?(record, value)
- delimiter = options[:in]
exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
exclusions.send(inclusion_method(exclusions), 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.
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index ede34d15bc..baa034eca6 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Confirmation Validator
module Validations
- class ConfirmationValidator < EachValidator
+ class ConfirmationValidator < EachValidator #:nodoc:
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
human_attribute_name = record.class.human_attribute_name(attribute)
@@ -25,7 +25,7 @@ module ActiveModel
# class Person < ActiveRecord::Base
# validates_confirmation_of :user_name, :password
# validates_confirmation_of :email_address,
- # :message => "should match confirmation"
+ # message: 'should match confirmation'
# end
#
# View:
@@ -38,29 +38,18 @@ module ActiveModel
# attribute.
#
# NOTE: This check is performed only if +password_confirmation+ is not
- # +nil+. To require confirmation, make sure
- # to add a presence check for the confirmation attribute:
+ # +nil+. To require confirmation, make sure to add a presence check for
+ # the confirmation attribute:
#
- # validates_presence_of :password_confirmation, :if => :password_changed?
+ # validates_presence_of :password_confirmation, if: :password_changed?
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "doesn't match
# confirmation").
- # * <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>:if</tt> - Specifies a method, proc or string to call to determine
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
- # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false
- # value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to
- # determine if the validation should not occur (e.g.
- # <tt>:unless => :skip_validation</tt>, or
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_confirmation_of(*attr_names)
validates_with ConfirmationValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 4f09679541..dc3368c569 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -4,12 +4,12 @@ module ActiveModel
# == Active Model Exclusion Validator
module Validations
- class ExclusionValidator < EachValidator
+ class ExclusionValidator < EachValidator #:nodoc:
include Clusivity
def validate_each(record, attribute, value)
if include?(record, value)
- record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
+ record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value))
end
end
end
@@ -19,38 +19,29 @@ module ActiveModel
# particular enumerable object.
#
# class Person < ActiveRecord::Base
- # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here"
- # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60"
- # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %{value} is not allowed"
- # validates_exclusion_of :password, :in => lambda { |p| [p.username, p.first_name] },
- # :message => "should not be the same as your username or first name"
+ # validates_exclusion_of :username, in: %w( admin superuser ), message: "You don't belong here"
+ # validates_exclusion_of :age, in: 30..60, message: 'This site is only for under 30 and over 60'
+ # validates_exclusion_of :format, in: %w( mov avi ), message: "extension %{value} is not allowed"
+ # validates_exclusion_of :password, in: ->(person) { [person.username, person.first_name] },
+ # message: 'should not be the same as your username or first name'
# end
#
# Configuration options:
- # * <tt>:in</tt> - An enumerable object of items that the value shouldn't be
- # part of. This can be supplied as a proc or lambda which returns an
+ # * <tt>:in</tt> - An enumerable object of items that the value shouldn't
+ # be part of. This can be supplied as a proc or lambda which returns an
# enumerable. If the enumerable is a range the test is performed with
- # <tt>Range#cover?</tt> (backported in Active Support for 1.8), otherwise
- # with <tt>include?</tt>.
+ # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt>
+ # <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_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>: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>:if</tt> - Specifies a method, proc or string to call to determine if
- # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index dd87e312f9..80150229a0 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Format Validator
module Validations
- class FormatValidator < EachValidator
+ class FormatValidator < EachValidator #:nodoc:
def validate_each(record, attribute, value)
if options[:with]
regexp = option_call(record, :with)
@@ -33,10 +33,20 @@ module ActiveModel
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
end
+ def regexp_using_multiline_anchors?(regexp)
+ regexp.source.start_with?("^") ||
+ (regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
+ end
+
def check_options_validity(options, name)
option = options[name]
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
+ elsif option && option.is_a?(Regexp) &&
+ regexp_using_multiline_anchors?(option) && options[:multiline] != true
+ raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
+ "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
+ ":multiline => true option?"
end
end
end
@@ -47,14 +57,14 @@ module ActiveModel
# attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
- # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
+ # validates_format_of :email, with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create
# end
#
# Alternatively, you can require that the specified attribute does _not_
# match the regular expression:
#
# class Person < ActiveRecord::Base
- # validates_format_of :email, :without => /NOSPAM/
+ # validates_format_of :email, without: /NOSPAM/
# end
#
# You can also provide a proc or lambda which will determine the regular
@@ -63,41 +73,41 @@ module ActiveModel
# class Person < ActiveRecord::Base
# # Admin can have number as a first letter in their screen name
# validates_format_of :screen_name,
- # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\Z/i : /\A[a-z][a-z0-9_\-]*\Z/i }
+ # with: ->(person) { person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
+ # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass
+ # the <tt>multiline: true</tt> option in case you use any of these two
+ # anchors in the provided regular expression. In most cases, you should be
+ # using <tt>\A</tt> and <tt>\z</tt>.
+ #
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
# In addition, both must be a regular expression or a proc or lambda, or
# else an exception will be raised.
#
# 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_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.
- # * <tt>:without</tt> - Regular expression that if the attribute does not match
- # will result in a successful validation. This can be provided as a proc or
+ # result in a successful validation. This can be provided as a proc or
# lambda returning regular expression which will be called at runtime.
- # * <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>:if</tt> - Specifies a method, proc or string to call to determine
- # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ # * <tt>:without</tt> - Regular expression that if the attribute does not
+ # match will result in a successful validation. This can be provided as
+ # a proc or lambda returning regular expression which will be called at
+ # runtime.
+ # * <tt>:multiline</tt> - Set to true if your regular expression contains
+ # anchors that match the beginning or end of lines as opposed to the
+ # 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+.
+ # See <tt>ActiveModel::Validation#validates</tt> for more information
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index ffdbed0fc1..c2835c550b 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -4,12 +4,12 @@ module ActiveModel
# == Active Model Inclusion Validator
module Validations
- class InclusionValidator < EachValidator
+ class InclusionValidator < EachValidator #:nodoc:
include Clusivity
def validate_each(record, attribute, value)
unless include?(record, value)
- record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
+ record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value))
end
end
end
@@ -19,36 +19,28 @@ module ActiveModel
# particular enumerable object.
#
# class Person < ActiveRecord::Base
- # validates_inclusion_of :gender, :in => %w( m f )
- # validates_inclusion_of :age, :in => 0..99
- # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %{value} is not included in the list"
- # validates_inclusion_of :states, :in => lambda{ |person| STATES[person.country] }
+ # validates_inclusion_of :gender, in: %w( m f )
+ # validates_inclusion_of :age, in: 0..99
+ # validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
+ # validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
# end
#
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
- # supplied as a proc or lambda which returns an enumerable. If the enumerable
- # is a range the test is performed with <tt>Range#cover?</tt>
- # (backported in Active Support for 1.8), otherwise with <tt>include?</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
+ # supplied as a proc or lambda which returns an enumerable. If the
+ # enumerable is a 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
+ # 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+).
- # * <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>:if</tt> - Specifies a method, proc or string to call to determine if
- # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index 64b4fe2d74..e4a1f9e80a 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Length Validator
module Validations
- class LengthValidator < EachValidator
+ class LengthValidator < EachValidator #:nodoc:
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
@@ -36,12 +36,12 @@ module ActiveModel
def validate_each(record, attribute, value)
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)
- errors_options = options.except(*RESERVED_OPTIONS)
errors_options[:count] = check_value
default_message = options[MESSAGES[key]]
@@ -62,56 +62,48 @@ module ActiveModel
module HelperMethods
- # Validates that the specified attribute matches the length restrictions supplied. Only one option can be used at a time:
+ # Validates that the specified attribute matches the length restrictions
+ # supplied. Only one option can be used at a time:
#
# class Person < ActiveRecord::Base
- # validates_length_of :first_name, :maximum => 30
- # validates_length_of :last_name, :maximum => 30, :message => "less than 30 if you don't mind"
- # validates_length_of :fax, :in => 7..32, :allow_nil => true
- # validates_length_of :phone, :in => 7..32, :allow_blank => true
- # validates_length_of :user_name, :within => 6..20, :too_long => "pick a shorter name", :too_short => "pick a longer name"
- # validates_length_of :zip_code, :minimum => 5, :too_short => "please enter at least 5 characters"
- # validates_length_of :smurf_leader, :is => 4, :message => "papa is spelled with 4 characters... don't play me."
- # validates_length_of :essay, :minimum => 100, :too_short => "Your essay must be at least 100 words.",
- # :tokenizer => lambda { |str| str.scan(/\w+/) }
+ # validates_length_of :first_name, maximum: 30
+ # validates_length_of :last_name, maximum: 30, message: "less than 30 if you don't mind"
+ # validates_length_of :fax, in: 7..32, allow_nil: true
+ # validates_length_of :phone, in: 7..32, allow_blank: true
+ # validates_length_of :user_name, within: 6..20, too_long: 'pick a shorter name', too_short: 'pick a longer name'
+ # validates_length_of :zip_code, minimum: 5, too_short: 'please enter at least 5 characters'
+ # validates_length_of :smurf_leader, is: 4, message: "papa is spelled with 4 characters... don't play me."
+ # validates_length_of :essay, minimum: 100, too_short: 'Your essay must be at least 100 words.',
+ # tokenizer: ->(str) { str.scan(/\w+/) }
# end
#
# Configuration options:
# * <tt>:minimum</tt> - The minimum size of the attribute.
# * <tt>:maximum</tt> - The maximum size of the attribute.
# * <tt>:is</tt> - The exact size of the attribute.
- # * <tt>:within</tt> - A range specifying the minimum and maximum size of the
- # attribute.
- # * <tt>:in</tt> - A synonym(or alias) for <tt>:within</tt>.
+ # * <tt>:within</tt> - A range specifying the minimum and maximum size of
+ # the attribute.
+ # * <tt>:in</tt> - A synonym (or alias) for <tt>:within</tt>.
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
# * <tt>:too_long</tt> - The error message if the attribute goes over the
# maximum (default is: "is too long (maximum is %{count} characters)").
# * <tt>:too_short</tt> - The error message if the attribute goes under the
# minimum (default is: "is too short (min is %{count} characters)").
- # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt> method
- # and the attribute is the wrong size (default is: "is the wrong length
- # (should be %{count} characters)").
+ # * <tt>:wrong_length</tt> - The error message if using the <tt>:is</tt>
+ # method and the attribute is the wrong size (default is: "is the wrong
+ # length (should be %{count} characters)").
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>,
# <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate
# <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
- # * <tt>: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>:if</tt> - Specifies a method, proc or string to call to determine if
- # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
- # proc or string should return or evaluate to a true or false value.
# * <tt>:tokenizer</tt> - Specifies how to split up the attribute string.
- # (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to count words
- # as in above example). Defaults to <tt>lambda{ |value| value.split(//) }</tt>
+ # (e.g. <tt>tokenizer: ->(str) { str.scan(/\w+/) }</tt> to count words
+ # as in above example). Defaults to <tt>->(value) { value.split(//) }</tt>
# which counts individual characters.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 40b5b92b84..edebca94a8 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -2,7 +2,7 @@ module ActiveModel
# == Active Model Numericality Validator
module Validations
- class NumericalityValidator < EachValidator
+ 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
@@ -79,48 +79,40 @@ module ActiveModel
end
module HelperMethods
- # Validates whether the value of the specified attribute is numeric by trying
- # to convert it to a float with Kernel.Float (if <tt>only_integer</tt> is false)
- # or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt> (if
- # <tt>only_integer</tt> is set to true).
+ # Validates whether the value of the specified attribute is numeric by
+ # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
+ # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\Z/</tt>
+ # (if <tt>only_integer</tt> is set to +true+).
#
# class Person < ActiveRecord::Base
- # validates_numericality_of :value, :on => :create
+ # validates_numericality_of :value, on: :create
# end
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "is not a number").
- # * <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>:only_integer</tt> - Specifies whether the value has to be an integer,
- # e.g. an integral value (default is +false+).
+ # * <tt>:only_integer</tt> - Specifies whether the value has to be an
+ # integer, e.g. an integral value (default is +false+).
# * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
# +false+). Notice that for fixnum and float columns empty strings are
# converted to +nil+.
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
# supplied value.
- # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be greater
- # than or equal the supplied value.
- # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value.
- # * <tt>:less_than</tt> - Specifies the value must be less than the supplied
- # value.
- # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or
- # equal the supplied value.
- # * <tt>:other_than</tt> - Specifies the value must be other than the supplied
+ # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
+ # greater than or equal the supplied value.
+ # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
# value.
+ # * <tt>:less_than</tt> - Specifies the value must be less than the
+ # supplied value.
+ # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
+ # than or equal the supplied value.
+ # * <tt>:other_than</tt> - Specifies the value must be other than the
+ # supplied value.
# * <tt>:odd</tt> - Specifies the value must be an odd number.
# * <tt>:even</tt> - Specifies the value must be an even number.
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if
- # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
- # method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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
#
# The following checks can also be supplied with a proc or a symbol which
# corresponds to a method:
@@ -134,8 +126,8 @@ module ActiveModel
# For example:
#
# class Person < ActiveRecord::Base
- # validates_numericality_of :width, :less_than => Proc.new { |person| person.height }
- # validates_numericality_of :width, :greater_than => :minimum_weight
+ # validates_numericality_of :width, less_than: Proc.new { |person| person.height }
+ # validates_numericality_of :width, greater_than: :minimum_weight
# end
def validates_numericality_of(*attr_names)
validates_with NumericalityValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 018ef1e733..f159e40858 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -1,10 +1,9 @@
-require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Presence Validator
module Validations
- class PresenceValidator < EachValidator
+ class PresenceValidator < EachValidator #:nodoc:
def validate(record)
record.errors.add_on_blank(attributes, options)
end
@@ -20,28 +19,19 @@ module ActiveModel
#
# The first_name attribute must be in the object and it cannot be blank.
#
- # If you want to validate the presence of a boolean field (where the real values
- # are true and false), you will want to use
- # <tt>validates_inclusion_of :field_name, :in => [true, false]</tt>.
+ # If you want to validate the presence of a boolean field (where the real
+ # values are +true+ and +false+), you will want to use
+ # <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
#
# This is due to the way Object#blank? handles boolean values:
# <tt>false.blank? # => true</tt>.
#
# Configuration options:
# * <tt>:message</tt> - A custom error message (default is: "can't be blank").
- # * <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>:if</tt> - Specifies a method, proc or string to call to determine if
- # the validation should occur (e.g. <tt>:if => :allow_validation</tt>, or
- # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method, proc
- # or string should return or evaluate to a true or false value.
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
- # if the validation should not occur (e.g. <tt>:unless => :skip_validation</tt>,
- # or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The method,
- # proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
- # See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ #
+ # 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_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 6c13d2b4a2..eb6e604851 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -11,18 +11,18 @@ module ActiveModel
#
# Examples of using the default rails validators:
#
- # validates :terms, :acceptance => true
- # validates :password, :confirmation => true
- # validates :username, :exclusion => { :in => %w(admin superuser) }
- # validates :email, :format => { :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create }
- # validates :age, :inclusion => { :in => 0..9 }
- # validates :first_name, :length => { :maximum => 30 }
- # validates :age, :numericality => true
- # validates :username, :presence => true
- # validates :username, :uniqueness => true
+ # validates :terms, acceptance: true
+ # validates :password, confirmation: true
+ # validates :username, exclusion: { in: %w(admin superuser) }
+ # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create }
+ # validates :age, inclusion: { in: 0..9 }
+ # validates :first_name, length: { maximum: 30 }
+ # validates :age, numericality: true
+ # validates :username, presence: true
+ # validates :username, uniqueness: true
#
# The power of the +validates+ method comes when using custom validators
- # and default validators in one call for a given attribute e.g.
+ # and default validators in one call for a given attribute.
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
@@ -35,12 +35,12 @@ module ActiveModel
# include ActiveModel::Validations
# attr_accessor :name, :email
#
- # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
- # validates :email, :presence => true, :email => true
+ # validates :name, presence: true, uniqueness: true, length: { maximum: 100 }
+ # validates :email, presence: true, email: true
# end
#
# Validator classes may also exist within the class being validated
- # allowing custom modules of validators to be included as needed e.g.
+ # allowing custom modules of validators to be included as needed.
#
# class Film
# include ActiveModel::Validations
@@ -51,33 +51,53 @@ module ActiveModel
# end
# end
#
- # validates :name, :title => true
+ # validates :name, title: true
# end
#
- # Additionally validator classes may be in another namespace and still used within any class.
+ # Additionally validator classes may be in another namespace and still
+ # used within any class.
#
# validates :name, :'film/title' => true
#
- # The validators hash can also handle regular expressions, ranges,
- # arrays and strings in shortcut form, e.g.
+ # The validators hash can also handle regular expressions, ranges, arrays
+ # and strings in shortcut form.
#
- # validates :email, :format => /@/
- # validates :gender, :inclusion => %w(male female)
- # validates :password, :length => 6..20
+ # validates :email, format: /@/
+ # validates :gender, inclusion: %w(male female)
+ # validates :password, length: 6..20
#
# When using shortcut form, ranges and arrays are passed to your
- # validator's initializer as +options[:in]+ while other types including
- # regular expressions and strings are passed as +options[:with]+
+ # validator's initializer as <tt>options[:in]</tt> while other types
+ # including regular expressions and strings are passed as <tt>options[:with]</tt>.
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
- # can be given to one specific validator, as a hash:
+ # There is also a list of options that could be used along with validators:
#
- # validates :password, :presence => { :if => :password_required? }, :confirmation => true
+ # * <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>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a +true+ or +false+ value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a +true+ or
+ # +false+ value.
+ # * <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.
#
- # Or to all at the same time:
+ # Example:
#
- # validates :password, :presence => true, :confirmation => true, :if => :password_required?
+ # validates :password, presence: true, confirmation: true, if: :password_required?
+ # validates :token, uniqueness: true, strict: TokenGenerationException
#
+ #
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+
+ # and +:strict+ can be given to one specific validator, as a hash:
+ #
+ # validates :password, presence: { if: :password_required? }, confirmation: true
def validates(*attributes)
defaults = attributes.extract_options!.dup
validations = defaults.slice!(*_validates_default_keys)
@@ -105,8 +125,20 @@ module ActiveModel
# users and are considered exceptional. So each validator defined with bang
# or <tt>:strict</tt> option set to <tt>true</tt> will always raise
# <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
- # when validation fails.
- # See <tt>validates</tt> for more information about the validation itself.
+ # when validation fails. See <tt>validates</tt> for more information about
+ # the validation itself.
+ #
+ # class Person
+ #  include ActiveModel::Validations
+ #
+ # attr_accessor :name
+ # validates! :name, presence: true
+ # end
+ #
+ # person = Person.new
+ #  person.name = ''
+ #  person.valid?
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
@@ -117,7 +149,7 @@ 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
+ def _validates_default_keys #:nodoc:
[:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 66cc9daa2c..869591cd9e 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -3,12 +3,14 @@ module ActiveModel
module HelperMethods
private
def _merge_attributes(attr_names)
- options = attr_names.extract_options!
- options.merge(:attributes => attr_names.flatten)
+ options = attr_names.extract_options!.symbolize_keys
+ attr_names.flatten!
+ options[:attributes] = attr_names
+ options
end
end
- class WithValidator < EachValidator
+ class WithValidator < EachValidator #:nodoc:
def validate_each(record, attr, val)
method_name = options[:with]
@@ -32,7 +34,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors.add :base, "This record is invalid"
+ # record.errors.add :base, 'This record is invalid'
# end
# end
#
@@ -46,30 +48,32 @@ module ActiveModel
#
# class Person
# include ActiveModel::Validations
- # validates_with MyValidator, MyOtherValidator, :on => :create
+ # validates_with MyValidator, MyOtherValidator, on: :create
# end
#
# Configuration options:
# * <tt>:on</tt> - Specifies when this validation is active
- # (<tt>:create</tt> or <tt>:update</tt>
+ # (<tt>:create</tt> or <tt>:update</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, proc or string should return or evaluate to a true or false value.
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
+ # The method, proc or string should return or evaluate to a +true+ or
+ # +false+ value.
# * <tt>:unless</tt> - Specifies a method, proc or string to call to
# determine if the validation should not occur
- # (e.g. <tt>:unless => :skip_validation</tt>, or
- # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>).
- # The method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # (e.g. <tt>unless: :skip_validation</tt>, or
+ # <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
+ # The method, proc or string should return or evaluate to a +true+ or
+ # +false+ value.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>:
+ # to the class and available as +options+:
#
# class Person
# include ActiveModel::Validations
- # validates_with MyValidator, :my_custom_key => "my custom value"
+ # validates_with MyValidator, my_custom_key: 'my custom value'
# end
#
# class MyValidator < ActiveModel::Validator
@@ -117,17 +121,17 @@ module ActiveModel
# class Person
# include ActiveModel::Validations
#
- # validate :instance_validations, :on => :create
+ # validate :instance_validations, on: :create
#
# def instance_validations
# validates_with MyValidator, MyOtherValidator
# end
# end
#
- # Standard configuration options (:on, :if and :unless), which are
- # available on the class version of +validates_with+, should instead be
- # placed on the +validates+ method as these are applied and tested
- # in the callback.
+ # Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
+ # <tt>:unless</tt>), which are available on the class version of
+ # +validates_with+, should instead be placed on the +validates+ method
+ # as these are applied and tested in the callback.
#
# If you pass any additional configuration options, they will be passed
# to the class and available as +options+, please refer to the
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 2953126c3c..85aec00f25 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,8 +1,6 @@
require "active_support/core_ext/module/anonymous"
-require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/inclusion'
-module ActiveModel #:nodoc:
+module ActiveModel
# == Active Model Validator
#
@@ -28,7 +26,7 @@ module ActiveModel #:nodoc:
# end
#
# Any class that inherits from ActiveModel::Validator must implement a method
- # called <tt>validate</tt> which accepts a <tt>record</tt>.
+ # called +validate+ which accepts a +record+.
#
# class Person
# include ActiveModel::Validations
@@ -42,8 +40,8 @@ module ActiveModel #:nodoc:
# end
# end
#
- # To cause a validation error, you must add to the <tt>record</tt>'s errors directly
- # from within the validators message
+ # To cause a validation error, you must add to the +record+'s errors directly
+ # from within the validators message.
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
@@ -63,16 +61,16 @@ module ActiveModel #:nodoc:
# end
#
# The easiest way to add custom validators for validating individual attributes
- # is with the convenient <tt>ActiveModel::EachValidator</tt>. For example:
+ # is with the convenient <tt>ActiveModel::EachValidator</tt>.
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors.add attribute, 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
+ # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
# end
# end
#
# This can now be used in combination with the +validates+ method
- # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this)
+ # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
#
# class Person
# include ActiveModel::Validations
@@ -83,8 +81,7 @@ module ActiveModel #:nodoc:
#
# 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
- # for example:
+ # useful when there are prerequisites such as an +attr_accessor+ being present.
#
# class MyValidator < ActiveModel::Validator
# def setup(klass)
@@ -94,15 +91,13 @@ module ActiveModel #:nodoc:
#
# 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
- # Returns the kind of the validator. Examples:
+ # Returns the kind of the validator.
#
# PresenceValidator.kind # => :presence
# UniquenessValidator.kind # => :uniqueness
- #
def self.kind
@kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
end
@@ -113,6 +108,9 @@ module ActiveModel #:nodoc:
end
# Return the kind for this validator.
+ #
+ # PresenceValidator.new.kind # => :presence
+ # UniquenessValidator.new.kind # => :uniqueness 
def kind
self.class.kind
end
@@ -129,7 +127,7 @@ module ActiveModel #:nodoc:
# record, attribute and value.
#
# All Active Model validations are built on top of this validator.
- class EachValidator < Validator
+ class EachValidator < Validator #:nodoc:
attr_reader :attributes
# Returns a new validator instance. All options will be available via the
@@ -168,7 +166,7 @@ module ActiveModel #:nodoc:
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
# and call this block for each attribute being validated. +validates_each+ uses this validator.
- class BlockValidator < EachValidator
+ class BlockValidator < EachValidator #:nodoc:
def initialize(options, &block)
@block = block
super