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.rb69
-rw-r--r--activemodel/lib/active_model/callbacks.rb8
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/conversion.rb10
-rw-r--r--activemodel/lib/active_model/dirty.rb8
-rw-r--r--activemodel/lib/active_model/errors.rb60
-rw-r--r--activemodel/lib/active_model/lint.rb54
-rw-r--r--activemodel/lib/active_model/locale/en.yml3
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb66
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb2
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb36
-rw-r--r--activemodel/lib/active_model/model.rb76
-rw-r--r--activemodel/lib/active_model/naming.rb71
-rw-r--r--activemodel/lib/active_model/observing.rb73
-rw-r--r--activemodel/lib/active_model/secure_password.rb13
-rw-r--r--activemodel/lib/active_model/serialization.rb46
-rw-r--r--activemodel/lib/active_model/serializers/json.rb3
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb5
-rw-r--r--activemodel/lib/active_model/test_case.rb12
-rw-r--r--activemodel/lib/active_model/translation.rb23
-rw-r--r--activemodel/lib/active_model/validations.rb27
-rw-r--r--activemodel/lib/active_model/validations/callbacks.rb10
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb31
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb5
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb26
-rw-r--r--activemodel/lib/active_model/validations/length.rb27
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb5
-rw-r--r--activemodel/lib/active_model/validations/presence.rb6
-rw-r--r--activemodel/lib/active_model/validations/validates.rb21
-rw-r--r--activemodel/lib/active_model/validations/with.rb8
-rw-r--r--activemodel/lib/active_model/validator.rb3
-rw-r--r--activemodel/lib/active_model/version.rb4
33 files changed, 594 insertions, 377 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index e69cb5c459..97a83e58af 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -61,69 +61,12 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- class_attribute :attribute_method_matchers, :instance_writer => false
+ extend ActiveModel::Configuration
+ config_attribute :attribute_method_matchers
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
module ClassMethods
- # Defines an "attribute" method (like +inheritance_column+ or +table_name+).
- # A new (class) method will be created with the given name. If a value is
- # specified, the new method will return that value (as a string).
- # Otherwise, the given block will be used to compute the value of the
- # method.
- #
- # The original method will be aliased, with the new name being prefixed
- # with "original_". This allows the new method to access the original
- # value.
- #
- # Example:
- #
- # class Person
- #
- # include ActiveModel::AttributeMethods
- #
- # cattr_accessor :primary_key
- # cattr_accessor :inheritance_column
- #
- # define_attr_method :primary_key, "sysid"
- # define_attr_method( :inheritance_column ) do
- # original_inheritance_column + "_id"
- # end
- #
- # end
- #
- # Provides you with:
- #
- # Person.primary_key
- # # => "sysid"
- # Person.inheritance_column = 'address'
- # Person.inheritance_column
- # # => 'address_id'
- def define_attr_method(name, value=nil, &block)
- sing = singleton_class
- sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
- if method_defined?('original_#{name}')
- undef :'original_#{name}'
- end
- alias_method :'original_#{name}', :'#{name}'
- eorb
- if block_given?
- sing.send :define_method, name, &block
- else
- # If we can compile the method name, do it. Otherwise use define_method.
- # This is an important *optimization*, please don't change it. define_method
- # has slower dispatch and consumes more memory.
- if name =~ NAME_COMPILABLE_REGEXP
- sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
- RUBY
- else
- value = value.to_s if value
- sing.send(:define_method, name) { value }
- end
- end
- end
-
# Declares a method available for all attributes with the given prefix.
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method.
#
@@ -280,7 +223,7 @@ module ActiveModel
unless instance_method_already_implemented?(method_name)
generate_method = "define_method_#{matcher.method_missing_target}"
- if respond_to?(generate_method)
+ if respond_to?(generate_method, true)
send(generate_method, attr_name)
else
define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
@@ -382,14 +325,14 @@ module ActiveModel
end
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
- @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
+ @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@method_name = "#{prefix}%s#{suffix}"
end
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2, method_name)
+ AttributeMethodMatch.new(method_missing_target, $1, method_name)
else
nil
end
@@ -451,7 +394,7 @@ module ActiveModel
protected
def attribute_method?(attr_name)
- attributes.include?(attr_name)
+ respond_to_without_attributes?(:attributes) && attributes.include?(attr_name)
end
private
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 37d0c9a0b9..ebb4b51aa3 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/callbacks'
module ActiveModel
@@ -41,7 +40,7 @@ module ActiveModel
# You can choose not to have all three callbacks by passing a hash to the
# 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.
@@ -89,11 +88,12 @@ module ActiveModel
options = callbacks.extract_options!
options = {
:terminator => "result == false",
+ :skip_after_callbacks_if_terminated => true,
:scope => [:kind, :name],
:only => [:before, :around, :after]
}.merge(options)
- types = Array.wrap(options.delete(:only))
+ types = Array(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
@@ -125,7 +125,7 @@ module ActiveModel
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array.wrap(options[:if]) << "!halted && value != false"
+ options[:if] = Array(options[:if]) << "value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
new file mode 100644
index 0000000000..ba5a6a2075
--- /dev/null
+++ b/activemodel/lib/active_model/configuration.rb
@@ -0,0 +1,134 @@
+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 80a3ba51c3..d7f30f0920 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -21,7 +21,7 @@ module ActiveModel
# cm.to_model == self # => true
# cm.to_key # => nil
# cm.to_param # => nil
- # cm.to_path # => "contact_messages/contact_message"
+ # cm.to_partial_path # => "contact_messages/contact_message"
#
module Conversion
extend ActiveSupport::Concern
@@ -39,11 +39,9 @@ module ActiveModel
# Returns an Enumerable of all key attributes if any is set, regardless
# if the object is persisted or not.
- #
- # Note the default implementation uses persisted? just because all objects
- # in Ruby 1.8.x responds to <tt>:id</tt>.
def to_key
- persisted? ? [id] : nil
+ key = respond_to?(:id) && id
+ key ? [key] : nil
end
# Returns a string representing the object's key suitable for use in URLs,
@@ -59,7 +57,7 @@ module ActiveModel
end
module ClassMethods #:nodoc:
- # Provide a class level cache for the to_path. This is an
+ # Provide a class level cache for #to_partial_path. This is an
# internal method and should not be accessed directly.
def _to_partial_path #:nodoc:
@_to_partial_path ||= begin
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 026f077ee7..3d35b5bb6f 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -1,6 +1,7 @@
require 'active_model/attribute_methods'
require 'active_support/hash_with_indifferent_access'
require 'active_support/core_ext/object/duplicable'
+require 'active_support/core_ext/object/blank'
module ActiveModel
# == Active Model Dirty
@@ -82,6 +83,7 @@ module ActiveModel
# in-place attributes.
#
# person.name_will_change!
+ # person.name_change # => ['Bill', 'Bill']
# person.name << 'y'
# person.name_change # => ['Bill', 'Billy']
module Dirty
@@ -98,7 +100,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- changed_attributes.any?
+ changed_attributes.present?
end
# List of attributes with unsaved changes.
@@ -150,13 +152,15 @@ module ActiveModel
# Handle <tt>*_will_change!</tt> for +method_missing+.
def attribute_will_change!(attr)
+ return if attribute_changed?(attr)
+
begin
value = __send__(attr)
value = value.duplicable? ? value.clone : value
rescue TypeError, NoMethodError
end
- changed_attributes[attr] = value unless changed_attributes.include?(attr)
+ changed_attributes[attr] = value
end
# Handle <tt>reset_*!</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 8337b04c0d..aba6618b56 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -1,16 +1,13 @@
# -*- coding: utf-8 -*-
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/hash/reverse_merge'
-require 'active_support/ordered_hash'
module ActiveModel
# == Active Model Errors
#
- # Provides a modified +OrderedHash+ that you can include in your object
+ # Provides a modified +Hash+ that you can include in your object
# for handling error messages and interacting with Action Pack helpers.
#
# A minimal implementation could be:
@@ -76,7 +73,12 @@ module ActiveModel
# end
def initialize(base)
@base = base
- @messages = ActiveSupport::OrderedHash.new
+ @messages = {}
+ end
+
+ def initialize_dup(other)
+ @messages = other.messages.dup
+ super
end
# Clear the messages
@@ -100,6 +102,11 @@ module ActiveModel
messages[key] = value
end
+ # Delete messages for +key+
+ def delete(key)
+ messages.delete(key)
+ end
+
# When passed a symbol or a name of a method, returns an array of errors
# for the method.
#
@@ -114,7 +121,7 @@ module ActiveModel
# p.errors[:name] = "must be set"
# p.errors[:name] # => ['must be set']
def []=(attribute, error)
- self[attribute.to_sym] << error
+ self[attribute] << error
end
# Iterates through each error key, value pair in the error messages hash.
@@ -122,12 +129,12 @@ module ActiveModel
# has more than one error message, yields once for each error message.
#
# p.errors.add(:name, "can't be blank")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# end
#
# p.errors.add(:name, "must be specified")
- # p.errors.each do |attribute, errors_array|
+ # p.errors.each do |attribute, error|
# # Will yield :name and "can't be blank"
# # then yield :name and "must be specified"
# end
@@ -176,8 +183,9 @@ module ActiveModel
end
# Returns true if no errors are found, false otherwise.
+ # If the error message is a string it can be empty.
def empty?
- all? { |k, v| v && v.empty? }
+ all? { |k, v| v && v.empty? && !v.is_a?(String) }
end
alias_method :blank?, :empty?
@@ -193,28 +201,39 @@ module ActiveModel
# # <error>name must be specified</error>
# # </errors>
def to_xml(options={})
- to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true)
+ to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options))
end
- # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object.
+ # 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>.
def as_json(options=nil)
- to_hash
+ to_hash(options && options[:full_messages])
end
- def to_hash
- messages.dup
+ def to_hash(full_messages = false)
+ if full_messages
+ messages = {}
+ self.messages.each do |attribute, array|
+ messages[attribute] = array.map { |message| full_message(attribute, message) }
+ end
+ messages
+ else
+ self.messages.dup
+ 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.
#
- # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_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.
def add(attribute, message = nil, options = {})
message = normalize_message(attribute, message, options)
if options[:strict]
- raise ActiveModel::StrictValidationFailed, message
+ raise ActiveModel::StrictValidationFailed, full_message(attribute, message)
end
self[attribute] << message
@@ -266,7 +285,7 @@ module ActiveModel
# "Name is invalid"
def full_message(attribute, message)
return message if attribute == :base
- attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = attribute.to_s.tr('.', '_').humanize
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
I18n.t(:"errors.format", {
:default => "%{attribute} %{message}",
@@ -327,7 +346,7 @@ module ActiveModel
:model => @base.class.model_name.human,
:attribute => @base.class.human_attribute_name(attribute),
:value => value
- }.merge(options)
+ }.merge!(options)
I18n.translate(key, options)
end
@@ -336,9 +355,10 @@ module ActiveModel
def normalize_message(attribute, message, options)
message ||= :invalid
- if message.is_a?(Symbol)
+ case message
+ when Symbol
generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
+ when Proc
message.call
else
message
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index bfe7ea1869..88b730626c 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -3,9 +3,13 @@ module ActiveModel
# == Active Model Lint Tests
#
# You can test whether an object is compliant with the Active Model API by
- # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include
- # tests that tell you whether your object is fully compliant, or if not,
- # which aspects of the API are not implemented.
+ # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will
+ # include tests that tell you whether your object is fully compliant,
+ # or if not, which aspects of the API are not implemented.
+ #
+ # Note an object is not required to implement all APIs in order to work
+ # with Action Pack. This module only intends to provide guidance in case
+ # 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
@@ -19,7 +23,8 @@ module ActiveModel
# == Responds to <tt>to_key</tt>
#
# Returns an Enumerable of all (primary) key attributes
- # or nil if model.persisted? is false
+ # or nil if model.persisted? is false. This is used by
+ # dom_id 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
@@ -53,22 +58,13 @@ module ActiveModel
assert_kind_of String, model.to_partial_path
end
- # == Responds to <tt>valid?</tt>
- #
- # Returns a boolean that specifies whether the object is in a valid or invalid
- # state.
- def test_valid?
- assert model.respond_to?(:valid?), "The model should respond to valid?"
- assert_boolean model.valid?, "valid?"
- end
-
# == 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 be POSTed to the
- # collection. If it is persisted, a form for the object will be PUT to the
- # URL for the object.
+ # 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?"
@@ -82,33 +78,23 @@ module ActiveModel
def test_model_naming
assert model.class.respond_to?(:model_name), "The model should respond to model_name"
model_name = model.class.model_name
- assert_kind_of String, model_name
- assert_kind_of String, model_name.human
- assert_kind_of String, model_name.singular
- assert_kind_of String, model_name.plural
+ assert model_name.respond_to?(:to_str)
+ assert model_name.human.respond_to?(:to_str)
+ assert model_name.singular.respond_to?(:to_str)
+ assert model_name.plural.respond_to?(:to_str)
end
# == Errors Testing
#
- # Returns an object that has :[] and :full_messages defined on it. See below
- # for more details.
- #
- # Returns an Array of Strings that are the errors for the attribute in
- # question. If localization is used, the Strings should be localized
- # for the current locale. If no error is present, this method should
- # return an empty Array.
+ # Returns an object that implements [](attribute) defined which returns an
+ # Array of Strings that are the errors for the attribute in question.
+ # If localization is used, the Strings should be localized for the current
+ # locale. If no error is present, this method should return an empty Array.
def test_errors_aref
assert model.respond_to?(:errors), "The model should respond to errors"
assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
end
- # Returns an Array of all error messages for the object. Each message
- # should contain information about the field, if applicable.
- def test_errors_full_messages
- assert model.respond_to?(:errors), "The model should respond to errors"
- assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array"
- end
-
private
def model
assert @model.respond_to?(:to_model), "The object should respond_to to_model"
diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml
index 44425b4a28..d17848c861 100644
--- a/activemodel/lib/active_model/locale/en.yml
+++ b/activemodel/lib/active_model/locale/en.yml
@@ -9,7 +9,7 @@ en:
inclusion: "is not included in the list"
exclusion: "is reserved"
invalid: "is invalid"
- confirmation: "doesn't match confirmation"
+ confirmation: "doesn't match %{attribute}"
accepted: "must be accepted"
empty: "can't be empty"
blank: "can't be blank"
@@ -23,5 +23,6 @@ en:
equal_to: "must be equal to %{count}"
less_than: "must be less than %{count}"
less_than_or_equal_to: "must be less than or equal to %{count}"
+ other_than: "must be other than %{count}"
odd: "must be odd"
even: "must be even"
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 3f9feb7631..5e5405fe27 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -1,6 +1,5 @@
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/wrap'
require 'active_model/mass_assignment_security/permission_set'
require 'active_model/mass_assignment_security/sanitizer'
@@ -10,11 +9,13 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- class_attribute :_accessible_attributes
- class_attribute :_protected_attributes
- class_attribute :_active_authorizer
+ extend ActiveModel::Configuration
- class_attribute :_mass_assignment_sanitizer
+ config_attribute :_accessible_attributes
+ config_attribute :_protected_attributes
+ config_attribute :_active_authorizer
+
+ config_attribute :_mass_assignment_sanitizer
self.mass_assignment_sanitizer = :logger
end
@@ -56,7 +57,7 @@ module ActiveModel
# 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
@@ -71,10 +72,11 @@ module ActiveModel
# class Customer
# include ActiveModel::MassAssignmentSecurity
#
- # attr_accessor :name, :credit_rating
+ # attr_accessor :name, :email, :logins_count
#
- # attr_protected :credit_rating, :last_login
- # attr_protected :last_login, :as => :admin
+ # attr_protected :logins_count
+ # # Suppose that admin can not change email for customer
+ # attr_protected :logins_count, :email, :as => :admin
#
# def assign_attributes(values, options = {})
# sanitize_for_mass_assignment(values, options[:as]).each do |k, v|
@@ -83,37 +85,38 @@ module ActiveModel
# end
# end
#
- # When using the :default role :
+ # When using the :default role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
+ # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default)
# customer.name # => "David"
- # customer.credit_rating # => nil
- # customer.last_login # => nil
+ # customer.email # => "a@b.com"
+ # customer.logins_count # => nil
#
- # customer.credit_rating = "Average"
- # customer.credit_rating # => "Average"
- #
- # And using the :admin role :
+ # And using the :admin role:
#
# customer = Customer.new
- # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
+ # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin)
# customer.name # => "David"
- # customer.credit_rating # => "Excellent"
- # customer.last_login # => nil
+ # customer.email # => nil
+ # customer.logins_count # => nil
+ #
+ # customer.email = "c@d.com"
+ # customer.email # => "c@d.com"
#
# To start from an all-closed default and enable attributes as needed,
# have a look at +attr_accessible+.
#
- # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_protected+
- # to sanitize attributes won't provide sufficient protection.
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
+ # +attr_protected+ to sanitize attributes provides basically the same
+ # functionality, but it makes a bit tricky to deal with nested attributes.
def attr_protected(*args)
options = args.extract_options!
role = options[:as] || :default
self._protected_attributes = protected_attributes_configs.dup
- Array.wrap(role).each do |name|
+ Array(role).each do |name|
self._protected_attributes[name] = self.protected_attributes(name) + args
end
@@ -150,7 +153,7 @@ module ActiveModel
# end
# end
#
- # When using the :default role :
+ # When using the :default role:
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default)
@@ -160,22 +163,23 @@ module ActiveModel
# customer.credit_rating = "Average"
# customer.credit_rating # => "Average"
#
- # And using the :admin role :
+ # And using the :admin role:
#
# customer = Customer.new
# customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin)
# customer.name # => "David"
# customer.credit_rating # => "Excellent"
#
- # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of +attr_accessible+
- # to sanitize attributes won't provide sufficient protection.
+ # Note that using <tt>Hash#except</tt> or <tt>Hash#slice</tt> in place of
+ # +attr_accessible+ to sanitize attributes provides basically the same
+ # functionality, but it makes a bit tricky to deal with nested attributes.
def attr_accessible(*args)
options = args.extract_options!
role = options[:as] || :default
self._accessible_attributes = accessible_attributes_configs.dup
- Array.wrap(role).each do |name|
+ Array(role).each do |name|
self._accessible_attributes[name] = self.accessible_attributes(name) + args
end
@@ -224,12 +228,12 @@ module ActiveModel
protected
- def sanitize_for_mass_assignment(attributes, role = :default)
+ def sanitize_for_mass_assignment(attributes, role = nil)
_mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role))
end
- def mass_assignment_authorizer(role = :default)
- self.class.active_authorizer[role]
+ def mass_assignment_authorizer(role)
+ self.class.active_authorizer[role || :default]
end
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 a1fcdf1a38..9661349503 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -13,7 +13,7 @@ module ActiveModel
end
def deny?(key)
- raise NotImplementedError, "#deny?(key) suppose to be overwritten"
+ raise NotImplementedError, "#deny?(key) supposed to be overwritten"
end
protected
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index bbdddfb50d..4491e07a72 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -1,36 +1,31 @@
-require 'active_support/core_ext/module/delegation'
-
module ActiveModel
module MassAssignmentSecurity
class Sanitizer
- def initialize(target=nil)
- end
-
# Returns all attributes not denied by the authorizer.
def sanitize(attributes, authorizer)
- sanitized_attributes = attributes.reject { |key, value| authorizer.deny?(key) }
- debug_protected_attribute_removal(attributes, sanitized_attributes)
+ rejected = []
+ sanitized_attributes = attributes.reject do |key, value|
+ rejected << key if authorizer.deny?(key)
+ end
+ process_removed_attributes(rejected) unless rejected.empty?
sanitized_attributes
end
protected
- def debug_protected_attribute_removal(attributes, sanitized_attributes)
- removed_keys = attributes.keys - sanitized_attributes.keys
- process_removed_attributes(removed_keys) if removed_keys.any?
- end
-
def process_removed_attributes(attrs)
raise NotImplementedError, "#process_removed_attributes(attrs) suppose to be overwritten"
end
end
class LoggerSanitizer < Sanitizer
- delegate :logger, :to => :@target
-
def initialize(target)
@target = target
- super
+ super()
+ end
+
+ def logger
+ @target.logger
end
def logger?
@@ -38,14 +33,18 @@ module ActiveModel
end
def process_removed_attributes(attrs)
- logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger?
+ logger.warn "Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger?
end
end
class StrictSanitizer < Sanitizer
+ def initialize(target = nil)
+ super()
+ end
+
def process_removed_attributes(attrs)
return if (attrs - insensitive_attributes).empty?
- raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}"
+ raise ActiveModel::MassAssignmentSecurity::Error.new(attrs)
end
def insensitive_attributes
@@ -54,6 +53,9 @@ module ActiveModel
end
class Error < StandardError
+ def initialize(attrs)
+ super("Can't mass-assign protected attributes: #{attrs.join(', ')}")
+ end
end
end
end
diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb
new file mode 100644
index 0000000000..3af95b09b0
--- /dev/null
+++ b/activemodel/lib/active_model/model.rb
@@ -0,0 +1,76 @@
+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.
+ #
+ # A minimal implementation could be:
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :name, :age
+ # end
+ #
+ # person = Person.new(:name => 'bob', :age => '18')
+ # person.name # => 'bob'
+ # 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:
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name
+ #
+ # def persisted?
+ # self.id == 1
+ # end
+ # end
+ #
+ # 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.
+ #
+ # class Person
+ # include ActiveModel::Model
+ # attr_accessor :id, :name, :omg
+ #
+ # def initialize(attributes={})
+ # super
+ # @omg ||= true
+ # end
+ # end
+ #
+ # 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).
+ module Model
+ def self.included(base)
+ base.class_eval do
+ extend ActiveModel::Naming
+ extend ActiveModel::Translation
+ include ActiveModel::Validations
+ include ActiveModel::Conversion
+ end
+ end
+
+ def initialize(params={})
+ params.each do |attr, value|
+ self.public_send("#{attr}=", value)
+ end if params
+ end
+
+ def persisted?
+ false
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index f16459ede2..2b5fc57a3a 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -1,30 +1,40 @@
require 'active_support/inflector'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/introspection'
-require 'active_support/core_ext/module/deprecation'
+require 'active_support/core_ext/module/delegation'
+require 'active_support/core_ext/object/blank'
module ActiveModel
- class Name < String
- attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
+ class Name
+ include Comparable
+
+ attr_reader :singular, :plural, :element, :collection,
+ :singular_route_key, :route_key, :param_key, :i18n_key,
+ :name
+
alias_method :cache_key, :collection
- deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
+ delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s,
+ :to_str, :to => :name
def initialize(klass, namespace = nil, name = nil)
- name ||= klass.name
- super(name)
- @unnamespaced = self.sub(/^#{namespace.name}::/, '') if namespace
-
- @klass = klass
- @singular = _singularize(self).freeze
- @plural = ActiveSupport::Inflector.pluralize(@singular).freeze
- @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)).freeze
- @human = ActiveSupport::Inflector.humanize(@element).freeze
- @collection = ActiveSupport::Inflector.tableize(self).freeze
- @partial_path = "#{@collection}/#{@element}".freeze
- @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
- @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
- @i18n_key = self.underscore.to_sym
+ @name = name || klass.name
+
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if @name.blank?
+
+ @unnamespaced = @name.sub(/^#{namespace.name}::/, '') if namespace
+ @klass = klass
+ @singular = _singularize(@name)
+ @plural = ActiveSupport::Inflector.pluralize(@singular)
+ @element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(@name))
+ @human = ActiveSupport::Inflector.humanize(@element)
+ @collection = ActiveSupport::Inflector.tableize(@name)
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular)
+ @i18n_key = @name.underscore.to_sym
+
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup)
+ @singular_route_key = ActiveSupport::Inflector.singularize(@route_key)
+ @route_key << "_index" if @plural == @singular
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -44,7 +54,7 @@ module ActiveModel
defaults << options[:default] if options[:default]
defaults << @human
- options = {:scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults}.merge(options.except(:default))
+ options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default))
I18n.translate(defaults.shift, options)
end
@@ -68,8 +78,8 @@ module ActiveModel
# BookCover.model_name # => "BookCover"
# BookCover.model_name.human # => "Book cover"
#
- # BookCover.model_name.i18n_key # => "book_cover"
- # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover"
+ # BookCover.model_name.i18n_key # => :book_cover
+ # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
#
# Providing the functionality that ActiveModel::Naming provides in your object
# is required to pass the Active Model Lint test. So either extending the provided
@@ -79,7 +89,9 @@ module ActiveModel
# used to retrieve all kinds of naming-related information.
def model_name
@_model_name ||= begin
- namespace = self.parents.detect { |n| n.respond_to?(:_railtie) }
+ namespace = self.parents.detect do |n|
+ n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming?
+ end
ActiveModel::Name.new(self, namespace)
end
end
@@ -112,10 +124,25 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# For isolated engine:
+ # ActiveModel::Naming.route_key(Blog::Post) #=> 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
+
+ # 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 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.
def self.route_key(record_or_class)
model_name_from_record_or_class(record_or_class).route_key
end
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index cd8eb357de..f5ea285ccb 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -1,10 +1,11 @@
require 'singleton'
require 'active_model/observer_array'
-require 'active_support/core_ext/array/wrap'
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
@@ -64,33 +65,40 @@ module ActiveModel
# raises an +ArgumentError+ exception.
def add_observer(observer)
unless observer.respond_to? :update
- raise ArgumentError, "observer needs to respond to `update'"
+ raise ArgumentError, "observer needs to respond to 'update'"
end
observer_instances << observer
end
# Notify list of observers of a change.
- def notify_observers(*arg)
- observer_instances.each { |observer| observer.update(*arg) }
+ def notify_observers(*args)
+ observer_instances.each { |observer| observer.update(*args) }
end
# Total number of observers.
- def count_observers
+ def observers_count
observer_instances.size
end
+ def count_observers
+ msg = "count_observers is deprecated in favor of observers_count"
+ ActiveSupport::Deprecation.warn(msg)
+ observers_count
+ end
+
protected
def instantiate_observer(observer) #:nodoc:
# string/symbol
if observer.respond_to?(:to_sym)
- observer.to_s.camelize.constantize.instance
- elsif observer.respond_to?(:instance)
+ observer = observer.to_s.camelize.constantize
+ end
+ if observer.respond_to?(:instance)
observer.instance
else
raise ArgumentError,
- "#{observer} must be a lowercase, underscored class name (or an " +
- "instance of the class itself) responding to the instance " +
- "method. Example: Person.observers = :big_brother # calls " +
+ "#{observer} must be a lowercase, underscored class name (or " +
+ "the class itself) responding to the method :instance. " +
+ "Example: Person.observers = :big_brother # calls " +
"BigBrother.instance"
end
end
@@ -102,17 +110,24 @@ module ActiveModel
end
end
- private
- # Fires notifications to model's observers
- #
- # def save
- # notify_observers(:before_save)
- # ...
- # notify_observers(:after_save)
- # end
- def notify_observers(method)
- self.class.notify_observers(method, self)
- end
+ # Fires notifications to model's observers
+ #
+ # def save
+ # notify_observers(:before_save)
+ # ...
+ # notify_observers(:after_save)
+ # end
+ #
+ # Custom notifications can be sent in a similar fashion:
+ #
+ # notify_observers(:custom_notification, :foo)
+ #
+ # This will call +custom_notification+, passing as arguments
+ # the current object and :foo.
+ #
+ def notify_observers(method, *extra_args)
+ self.class.notify_observers(method, self, *extra_args)
+ end
end
# == Active Model Observers
@@ -187,7 +202,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- redefine_method(:observed_classes) { models }
+ singleton_class.redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
@@ -200,21 +215,18 @@ module ActiveModel
# end
# end
def observed_classes
- Array.wrap(observed_class)
+ Array(observed_class)
end
# The class observed by default is inferred from the observer's class name:
# assert_equal Person, PersonObserver.observed_class
def observed_class
- if observed_class_name = name[/(.*)Observer/, 1]
- observed_class_name.constantize
- else
- nil
- end
+ name[/(.*)Observer/, 1].try :constantize
end
end
# Start observing the declared classes and their subclasses.
+ # Called automatically by the instance method.
def initialize
observed_classes.each { |klass| add_observer!(klass) }
end
@@ -225,10 +237,10 @@ 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, &block) #:nodoc:
+ def update(observed_method, object, *extra_args, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object, &block)
+ send(observed_method, object, *extra_args, &block)
end
# Special method sent by the observed class when it is inherited.
@@ -243,6 +255,7 @@ module ActiveModel
klass.add_observer(self)
end
+ # Returns true if notifications are disabled for this object.
def disabled_for?(object)
klass = object.class
return false unless klass.respond_to?(:observers)
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index db78864c67..8711b24124 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -29,7 +29,7 @@ module ActiveModel
# user.save # => true
# user.authenticate("notright") # => false
# user.authenticate("mUc3m00RsqyRe") # => user
- # User.find_by_name("david").try(:authenticate, "notright") # => nil
+ # User.find_by_name("david").try(:authenticate, "notright") # => false
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
# Load bcrypt-ruby only when has_secure_password is used.
@@ -55,17 +55,14 @@ module ActiveModel
module InstanceMethodsOnActivation
# Returns self if the password is correct, otherwise false.
def authenticate(unencrypted_password)
- if BCrypt::Password.new(password_digest) == unencrypted_password
- self
- else
- false
- end
+ BCrypt::Password.new(password_digest) == unencrypted_password && self
end
- # Encrypts the password into the password_digest attribute.
+ # Encrypts the password into the password_digest attribute, only if the
+ # new password is not blank.
def password=(unencrypted_password)
- @password = unencrypted_password
unless unencrypted_password.blank?
+ @password = unencrypted_password
self.password_digest = BCrypt::Password.create(unencrypted_password)
end
end
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index a4b58ab456..4403ef060b 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -1,7 +1,5 @@
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/hash/slice'
-require 'active_support/core_ext/array/wrap'
-
module ActiveModel
# == Active Model Serialization
@@ -11,15 +9,13 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# include ActiveModel::Serialization
#
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
- #
# end
#
# Which would provide you with:
@@ -29,27 +25,29 @@ module ActiveModel
# person.name = "Bob"
# person.serializable_hash # => {"name"=>"Bob"}
#
- # You need to declare some sort of attributes hash which contains the attributes
- # you want to serialize and their current value.
+ # 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
- # ActiveModel::Serialization module, so there is no need to explicitly
+ # <tt>ActiveModel::Serialization</tt> module, so there is no need to explicitly
# include it.
#
- # So a minimal implementation including XML and JSON would be:
+ # A minimal implementation including XML and JSON would be:
#
# class Person
- #
# include ActiveModel::Serializers::JSON
# include ActiveModel::Serializers::Xml
#
# attr_accessor :name
#
# def attributes
- # {'name' => name}
+ # {'name' => nil}
# end
- #
# end
#
# Which would provide you with:
@@ -66,26 +64,30 @@ module ActiveModel
# person.to_json # => "{\"name\":\"Bob\"}"
# person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
#
- # Valid options are <tt>:only</tt>, <tt>:except</tt> and <tt>:methods</tt> .
+ # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and <tt>include</tt>.
+ # The following are all valid examples:
+ #
+ # person.serializable_hash(:only => 'name')
+ # person.serializable_hash(:include => :address)
+ # person.serializable_hash(:include => { :address => { :only => 'city' }})
module Serialization
def serializable_hash(options = nil)
options ||= {}
attribute_names = attributes.keys.sort
if only = options[:only]
- attribute_names &= Array.wrap(only).map(&:to_s)
+ attribute_names &= Array(only).map(&:to_s)
elsif except = options[:except]
- attribute_names -= Array.wrap(except).map(&:to_s)
+ attribute_names -= Array(except).map(&:to_s)
end
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
- method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) }
- method_names.each { |n| hash[n] = send(n) }
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
serializable_add_includes(options) do |association, records, opts|
- hash[association] = if records.is_a?(Enumerable)
+ hash[association.to_s] = if records.is_a?(Enumerable)
records.map { |a| a.serializable_hash(opts) }
else
records.serializable_hash(opts)
@@ -123,13 +125,13 @@ module ActiveModel
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
def serializable_add_includes(options = {}) #:nodoc:
- return unless include = options[:include]
+ return unless includes = options[:include]
- unless include.is_a?(Hash)
- include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
+ unless includes.is_a?(Hash)
+ includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
end
- include.each do |association, opts|
+ includes.each do |association, opts|
if records = send(association)
yield association, records, opts
end
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index c845440120..63ab8e7edc 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,8 +10,9 @@ module ActiveModel
included do
extend ActiveModel::Naming
+ extend ActiveModel::Configuration
- class_attribute :include_root_in_json
+ config_attribute :include_root_in_json
self.include_root_in_json = true
end
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index d61d9d7119..5084298210 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/hash/conversions'
@@ -56,7 +55,7 @@ module ActiveModel
end
def serializable_collection
- methods = Array.wrap(options[:methods]).map(&:to_s)
+ methods = Array(options[:methods]).map(&:to_s)
serializable_hash.map do |name, value|
name = name.to_s
if methods.include?(name)
@@ -146,7 +145,7 @@ module ActiveModel
def add_procs
if procs = options.delete(:procs)
- Array.wrap(procs).each do |proc|
+ Array(procs).each do |proc|
if proc.arity == 1
proc.call(options)
else
diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb
index 6328807ad7..5004855d56 100644
--- a/activemodel/lib/active_model/test_case.rb
+++ b/activemodel/lib/active_model/test_case.rb
@@ -1,16 +1,4 @@
module ActiveModel #:nodoc:
class TestCase < ActiveSupport::TestCase #:nodoc:
- def with_kcode(kcode)
- if RUBY_VERSION < '1.9'
- orig_kcode, $KCODE = $KCODE, kcode
- begin
- yield
- ensure
- $KCODE = orig_kcode
- end
- else
- yield
- end
- end
end
end
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 6d64c81b5f..6f0ca92e2a 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/hash/reverse_merge'
-
module ActiveModel
# == Active Model Translation
@@ -43,15 +41,28 @@ module ActiveModel
#
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
- defaults = lookup_ancestors.map do |klass|
- :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}"
+ options = { :count => 1 }.merge!(options)
+ parts = attribute.to_s.split(".", 2)
+ attribute = parts.pop
+ namespace = parts.pop
+ attributes_scope = "#{self.i18n_scope}.attributes"
+
+ if namespace
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}"
+ end
+ defaults << :"#{attributes_scope}.#{namespace}.#{attribute}"
+ else
+ defaults = lookup_ancestors.map do |klass|
+ :"#{attributes_scope}.#{klass.model_name.i18n_key}.#{attribute}"
+ end
end
defaults << :"attributes.#{attribute}"
defaults << options.delete(:default) if options[:default]
- defaults << attribute.to_s.humanize
+ defaults << attribute.humanize
- options.reverse_merge! :count => 1, :default => defaults
+ options[:default] = defaults
I18n.translate(defaults.shift, options)
end
end
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 8ed392abca..3ed72bae3b 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/array/wrap'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/hash/except'
@@ -34,7 +33,7 @@ module ActiveModel
# person.first_name = 'zoolander'
# person.valid? # => false
# person.invalid? # => true
- # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}>
+ # 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
@@ -42,9 +41,9 @@ module ActiveModel
#
module Validations
extend ActiveSupport::Concern
- include ActiveSupport::Callbacks
included do
+ extend ActiveModel::Callbacks
extend ActiveModel::Translation
extend HelperMethods
@@ -53,7 +52,8 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- class_attribute :_validators
+ extend ActiveModel::Configuration
+ config_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
@@ -65,7 +65,7 @@ module ActiveModel
#
# attr_accessor :first_name, :last_name
#
- # validates_each :first_name, :last_name do |record, attr, value|
+ # validates_each :first_name, :last_name, :allow_blank => true do |record, attr, value|
# record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
# end
# end
@@ -128,11 +128,24 @@ module ActiveModel
# end
# end
#
+ # Options:
+ # * <tt>:on</tt> - Specifies the context where this validation is active
+ # (e.g. <tt>:on => :create</tt> or <tt>:on => :custom_validation_context</tt>)
+ # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+.
+ # * <tt>:allow_blank</tt> - Skip validation if attribute is blank.
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
+ # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>,
+ # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The method,
+ # proc or string should return or evaluate to a true or false value.
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
+ # not occur (e.g. <tt>:unless => :skip_validation</tt>, or
+ # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
+ # method, proc or string should return or evaluate to a true or false value.
def validate(*args, &block)
options = args.extract_options!
if options.key?(:on)
options = options.dup
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if].unshift("validation_context == :#{options[:on]}")
end
args << options
@@ -145,7 +158,7 @@ module ActiveModel
_validators.values.flatten.uniq
end
- # List all validators that being used to validate a specific attribute.
+ # List all validators that are being used to validate a specific attribute.
def validators_on(*attributes)
attributes.map do |attribute|
_validators[attribute.to_sym]
diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb
index 22a77320dc..dbafd0bd1a 100644
--- a/activemodel/lib/active_model/validations/callbacks.rb
+++ b/activemodel/lib/active_model/validations/callbacks.rb
@@ -8,7 +8,8 @@ module ActiveModel
# Provides an interface for any class to have <tt>before_validation</tt> and
# <tt>after_validation</tt> callbacks.
#
- # First, extend ActiveModel::Callbacks from the class you are creating:
+ # First, include ActiveModel::Validations::Callbacks from the class you are
+ # creating:
#
# class MyModel
# include ActiveModel::Validations::Callbacks
@@ -23,14 +24,14 @@ module ActiveModel
included do
include ActiveSupport::Callbacks
- define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name]
+ define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name]
end
module ClassMethods
def before_validation(*args, &block)
options = args.last
if options.is_a?(Hash) && options[:on]
- options[:if] = Array.wrap(options[:if])
+ options[:if] = Array(options[:if])
options[:if].unshift("self.validation_context == :#{options[:on]}")
end
set_callback(:validation, :before, *args, &block)
@@ -39,8 +40,7 @@ module ActiveModel
def after_validation(*args, &block)
options = args.extract_options!
options[:prepend] = true
- options[:if] = Array.wrap(options[:if])
- options[:if] << "!halted"
+ options[:if] = Array(options[:if])
options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on]
set_callback(:validation, :after, *(args << options), &block)
end
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
new file mode 100644
index 0000000000..b632a2bd6b
--- /dev/null
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -0,0 +1,31 @@
+require 'active_support/core_ext/range.rb'
+
+module ActiveModel
+ module Validations
+ module Clusivity
+ ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
+ "and must be supplied as the :in option of the configuration hash"
+
+ def check_validity!
+ unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
+ raise ArgumentError, ERROR_MESSAGE
+ end
+ end
+
+ private
+
+ def include?(record, value)
+ delimiter = options[:in]
+ exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
+ exclusions.send(inclusion_method(exclusions), value)
+ end
+
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
+ # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
+ # uses the previous logic of comparing a value with the range endpoints.
+ def inclusion_method(enumerable)
+ enumerable.is_a?(Range) ? :cover? : :include?
+ end
+ end
+ end
+end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index 6573a7d264..69ab74734d 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -5,7 +5,8 @@ module ActiveModel
class ConfirmationValidator < EachValidator
def validate_each(record, attribute, value)
if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed)
- record.errors.add(attribute, :confirmation, options)
+ human_attribute_name = record.class.human_attribute_name(attribute)
+ record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name))
end
end
@@ -37,7 +38,7 @@ module ActiveModel
# attribute.
#
# NOTE: This check is performed only if +password_confirmation+ is not
- # +nil+, and by default only on save. To require confirmation, make sure
+ # +nil+. To require confirmation, make sure
# to add a presence check for the confirmation attribute:
#
# validates_presence_of :password_confirmation, :if => :password_changed?
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 644cc814a7..5fedb1978b 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -1,35 +1,17 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
module ActiveModel
# == Active Model Exclusion Validator
module Validations
class ExclusionValidator < EachValidator
- ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
- "and must be supplied as the :in option of the configuration hash"
-
- def check_validity!
- unless [:include?, :call].any? { |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- if exclusions.send(inclusion_method(exclusions), value)
+ if include?(record, value)
record.errors.add(attribute, :exclusion, options.except(:in).merge!(:value => value))
end
end
-
- private
-
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
- # uses the previous logic of comparing a value with the range endpoints.
- def inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
- end
end
module HelperMethods
@@ -59,7 +41,7 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_exclusion_of(*attr_names)
validates_with ExclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index 147e2ecb69..15ae7b1959 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -1,35 +1,17 @@
-require 'active_support/core_ext/range'
+require "active_model/validations/clusivity"
module ActiveModel
# == Active Model Inclusion Validator
module Validations
class InclusionValidator < EachValidator
- ERROR_MESSAGE = "An object with the method #include? or a proc or lambda is required, " <<
- "and must be supplied as the :in option of the configuration hash"
-
- def check_validity!
- unless [:include?, :call].any?{ |method| options[:in].respond_to?(method) }
- raise ArgumentError, ERROR_MESSAGE
- end
- end
+ include Clusivity
def validate_each(record, attribute, value)
- delimiter = options[:in]
- exclusions = delimiter.respond_to?(:call) ? delimiter.call(record) : delimiter
- unless exclusions.send(inclusion_method(exclusions), value)
+ unless include?(record, value)
record.errors.add(attribute, :inclusion, options.except(:in).merge!(:value => value))
end
end
-
- private
-
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt>
- # uses the previous logic of comparing a value with the range endpoints.
- def inclusion_method(enumerable)
- enumerable.is_a?(Range) ? :cover? : :include?
- end
end
module HelperMethods
@@ -59,7 +41,7 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_inclusion_of(*attr_names)
validates_with InclusionValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb
index eb7aac709d..037f8c2db8 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -6,14 +6,12 @@ module ActiveModel
MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze
CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze
- DEFAULT_TOKENIZER = lambda { |value| value.split(//) }
RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long]
def initialize(options)
if range = (options.delete(:in) || options.delete(:within))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
- options[:minimum], options[:maximum] = range.begin, range.end
- options[:maximum] -= 1 if range.exclude_end?
+ options[:minimum], options[:maximum] = range.min, range.max
end
super
@@ -23,27 +21,24 @@ module ActiveModel
keys = CHECKS.keys & options.keys
if keys.empty?
- raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.'
+ raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.'
end
keys.each do |key|
value = options[key]
- unless value.is_a?(Integer) && value >= 0
- raise ArgumentError, ":#{key} must be a nonnegative Integer"
+ unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY
+ raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity"
end
end
end
def validate_each(record, attribute, value)
- value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String)
+ value = tokenize(value)
+ value_length = value.respond_to?(:length) ? value.length : value.to_s.length
CHECKS.each do |key, validity_check|
next unless check_value = options[key]
-
- value ||= [] if key == :maximum
-
- value_length = value.respond_to?(:length) ? value.length : value.to_s.length
next if value_length.send(validity_check, check_value)
errors_options = options.except(*RESERVED_OPTIONS)
@@ -55,6 +50,14 @@ module ActiveModel
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
+
+ private
+
+ def tokenize(value)
+ if options[:tokenizer] && value.kind_of?(String)
+ options[:tokenizer].call(value)
+ end || value
+ end
end
module HelperMethods
@@ -96,7 +99,7 @@ module ActiveModel
# * <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> which counts individual characters.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
def validates_length_of(*attr_names)
validates_with LengthValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb
index 34d447a0fa..bb9f9679fc 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -5,7 +5,7 @@ module ActiveModel
class NumericalityValidator < EachValidator
CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=,
:equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=,
- :odd => :odd?, :even => :even? }.freeze
+ :odd => :odd?, :even => :even?, :other_than => :!= }.freeze
RESERVED_OPTIONS = CHECKS.keys + [:only_integer]
@@ -99,6 +99,7 @@ module ActiveModel
# * <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
@@ -107,7 +108,7 @@ module ActiveModel
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
# method, proc or string should return or evaluate to a true or false value.
- # * <tt>:strict</tt> - Specifies whether validation should be strict.
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information
#
# The following checks can also be supplied with a proc or a symbol which corresponds to a method:
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index 35af7152db..9a643a6f5c 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -25,14 +25,14 @@ module ActiveModel
# 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>: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
+ # * <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
+ # * <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.
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 8e09f6ac35..d94c4e3f4f 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -1,7 +1,6 @@
require 'active_support/core_ext/hash/slice'
module ActiveModel
-
# == Active Model validates method
module Validations
module ClassMethods
@@ -59,7 +58,7 @@ module ActiveModel
#
# validates :name, :'film/title' => true
#
- # The validators hash can also handle regular expressions, ranges,
+ # The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
#
# validates :email, :format => /@/
@@ -70,7 +69,7 @@ module ActiveModel
# validator's initializer as +options[:in]+ while other types including
# regular expressions and strings are passed as +options[:with]+
#
- # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+
+ # 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
@@ -80,7 +79,7 @@ module ActiveModel
# validates :password, :presence => true, :confirmation => true, :if => :password_required?
#
def validates(*attributes)
- defaults = attributes.extract_options!
+ defaults = attributes.extract_options!.dup
validations = defaults.slice!(*_validates_default_keys)
raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
@@ -101,12 +100,12 @@ module ActiveModel
end
end
- # This method is used to define validation that can not be corrected by end user
- # and is considered exceptional.
- # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt>
- # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error
- # when validation fails
- # See <tt>validates</tt> for more information about validation itself.
+ # This method is used to define validations that cannot be corrected by end
+ # 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.
def validates!(*attributes)
options = attributes.extract_options!
options[:strict] = true
@@ -118,7 +117,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
- [ :if, :unless, :on, :allow_blank, :allow_nil , :strict]
+ [:if, :unless, :on, :allow_blank, :allow_nil , :strict]
end
def _parse_validates_options(options) #:nodoc:
diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb
index 93a340eb39..991c5f7b82 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -56,7 +56,7 @@ module ActiveModel
# 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
+ # * <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>).
@@ -126,12 +126,12 @@ module ActiveModel
# end
#
# Standard configuration options (:on, :if and :unless), which are
- # available on the class version of validates_with, should instead be
- # placed on the <tt>validates</tt> method as these are applied and tested
+ # available on the class version of +validates_with+, should instead be
+ # placed on the +validates+ method as these are applied and tested
# in the callback
#
# If you pass any additional configuration options, they will be passed
- # to the class and available as <tt>options</tt>, please refer to the
+ # to the class and available as +options+, please refer to the
# class version of this method for more information
#
def validates_with(*args, &block)
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 35ec98c822..2953126c3c 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/array/wrap'
require "active_support/core_ext/module/anonymous"
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/inclusion'
@@ -137,7 +136,7 @@ module ActiveModel #:nodoc:
# +options+ reader, however the <tt>:attributes</tt> option will be removed
# and instead be made available through the +attributes+ reader.
def initialize(options)
- @attributes = Array.wrap(options.delete(:attributes))
+ @attributes = Array(options.delete(:attributes))
raise ":attributes cannot be blank" if @attributes.empty?
super
check_validity!
diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb
index dbda55ca7c..e195c12a4d 100644
--- a/activemodel/lib/active_model/version.rb
+++ b/activemodel/lib/active_model/version.rb
@@ -1,7 +1,7 @@
module ActiveModel
module VERSION #:nodoc:
- MAJOR = 3
- MINOR = 2
+ MAJOR = 4
+ MINOR = 0
TINY = 0
PRE = "beta"