aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG105
-rw-r--r--activemodel/CHANGELOG.md116
-rw-r--r--activemodel/README.rdoc4
-rw-r--r--activemodel/activemodel.gemspec3
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb196
-rw-r--r--activemodel/lib/active_model/callbacks.rb4
-rw-r--r--activemodel/lib/active_model/conversion.rb10
-rw-r--r--activemodel/lib/active_model/dirty.rb4
-rw-r--r--activemodel/lib/active_model/errors.rb89
-rw-r--r--activemodel/lib/active_model/lint.rb8
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/sanitizer.rb5
-rw-r--r--activemodel/lib/active_model/naming.rb29
-rw-r--r--activemodel/lib/active_model/observing.rb7
-rw-r--r--activemodel/lib/active_model/secure_password.rb11
-rw-r--r--activemodel/lib/active_model/serialization.rb31
-rw-r--r--activemodel/lib/active_model/serializers/json.rb24
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb46
-rw-r--r--activemodel/lib/active_model/validations/acceptance.rb2
-rw-r--r--activemodel/lib/active_model/validations/confirmation.rb2
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/format.rb2
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb2
-rw-r--r--activemodel/lib/active_model/validations/length.rb4
-rw-r--r--activemodel/lib/active_model/validations/numericality.rb2
-rw-r--r--activemodel/lib/active_model/validations/presence.rb8
-rw-r--r--activemodel/lib/active_model/validations/validates.rb24
-rw-r--r--activemodel/lib/active_model/validations/with.rb10
-rw-r--r--activemodel/lib/active_model/validator.rb8
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb131
-rw-r--r--activemodel/test/cases/conversion_test.rb6
-rw-r--r--activemodel/test/cases/errors_test.rb101
-rw-r--r--activemodel/test/cases/mass_assignment_security/sanitizer_test.rb10
-rw-r--r--activemodel/test/cases/naming_test.rb61
-rw-r--r--activemodel/test/cases/observing_test.rb12
-rw-r--r--activemodel/test/cases/secure_password_test.rb10
-rw-r--r--activemodel/test/cases/serialization_test.rb32
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb24
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb19
-rw-r--r--activemodel/test/cases/validations/exclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb24
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb12
-rw-r--r--activemodel/test/cases/validations_test.rb33
-rw-r--r--activemodel/test/models/blog_post.rb8
43 files changed, 853 insertions, 400 deletions
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG
deleted file mode 100644
index 81386667c2..0000000000
--- a/activemodel/CHANGELOG
+++ /dev/null
@@ -1,105 +0,0 @@
-* Deprecate "Model.model_name.partial_path" in favor of "model.to_path" [Grant Hutchins]
-
-* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev]
-
-*Rails 3.1.0 (unreleased)*
-
-* Alternate I18n namespace lookup is no longer supported.
- Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim]
-
-* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis]
-
-* Add support for proc or lambda as an option for InclusionValidator,
- ExclusionValidator, and FormatValidator [Prem Sichanugrist]
-
- You can now supply Proc, lambda, or anything that respond to #call in those
- validations, and it will be called with current record as an argument.
- That given proc or lambda must returns an object which respond to #include? for
- InclusionValidator and ExclusionValidator, and returns a regular expression
- object for FormatValidator.
-
-* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH]
-
-* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov]
-
-* Add support for selectively enabling/disabling observers [Myron Marston]
-
-
-*Rails 3.0.7 (April 18, 2011)*
-
-*No changes.
-
-
-*Rails 3.0.6 (April 5, 2011)
-
-* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 [Robert Pankowecki, Santiago Pastorino]
-
-* Fix length validation for fixnums #6556 [Andriy Tyurnikov]
-
-* Fix i18n key collision with namespaced models #6448 [yves.senn]
-
-
-*Rails 3.0.5 (February 26, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.4 (February 8, 2011)*
-
-* No changes.
-
-
-*Rails 3.0.3 (November 16, 2010)*
-
-* No changes.
-
-
-*Rails 3.0.2 (November 15, 2010)*
-
-* No changes
-
-
-*Rails 3.0.1 (October 15, 2010)*
-
-* No Changes, just a version bump.
-
-
-*Rails 3.0.0 (August 29, 2010)*
-
-* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis]
-
-* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh]
-
-* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
- persisted? instead. A model is persisted if it's not a new_record? and it was
- not destroyed? [MG]
-
-* Added validations reflection in ActiveModel::Validations [JV]
-
- Model.validators
- Model.validators_on(:field)
-
-* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
- AMo objects with composite keys [MG]
-
-* ActiveModel::Observer#add_observer!
-
- It has a custom hook to define after_find that should really be in a
- ActiveRecord::Observer subclass:
-
- def add_observer!(klass)
- klass.add_observer(self)
- klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
- end
-
-* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH]
-
-* Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan]
-
- Example :
-
- validates_format_of :subdomain, :without => /www|admin|mail/
-
-* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean]
-
-* Extracted from Active Record and Active Resource.
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
new file mode 100644
index 0000000000..bd9ed996fe
--- /dev/null
+++ b/activemodel/CHANGELOG.md
@@ -0,0 +1,116 @@
+## Rails 3.2.0 (unreleased) ##
+
+* Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to
+ support methods like `set_table_name` in Active Record, which are themselves being deprecated.
+
+ *Jon Leighton*
+
+* Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin*
+
+* Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev*
+
+* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" *Grant Hutchins, Peter Jaros*
+
+* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev*
+
+## Rails 3.1.0 (August 30, 2011) ##
+
+* Alternate I18n namespace lookup is no longer supported.
+ Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead *José Valim*
+
+* attr_accessible and friends now accepts :as as option to specify a role *Josh Kalderimis*
+
+* Add support for proc or lambda as an option for InclusionValidator,
+ ExclusionValidator, and FormatValidator *Prem Sichanugrist*
+
+ You can now supply Proc, lambda, or anything that respond to #call in those
+ validations, and it will be called with current record as an argument.
+ That given proc or lambda must returns an object which respond to #include? for
+ InclusionValidator and ExclusionValidator, and returns a regular expression
+ object for FormatValidator.
+
+* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting *DHH*
+
+* ActiveModel::AttributeMethods allows attributes to be defined on demand *Alexander Uvarov*
+
+* Add support for selectively enabling/disabling observers *Myron Marston*
+
+
+## Rails 3.0.7 (April 18, 2011) ##
+
+* No changes.
+
+
+* Rails 3.0.6 (April 5, 2011)
+
+* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino*
+
+* Fix length validation for fixnums #6556 *Andriy Tyurnikov*
+
+* Fix i18n key collision with namespaced models #6448 *yves.senn*
+
+
+## Rails 3.0.5 (February 26, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.4 (February 8, 2011) ##
+
+* No changes.
+
+
+## Rails 3.0.3 (November 16, 2010) ##
+
+* No changes.
+
+
+## Rails 3.0.2 (November 15, 2010) ##
+
+* No changes
+
+
+## Rails 3.0.1 (October 15, 2010) ##
+
+* No Changes, just a version bump.
+
+
+## Rails 3.0.0 (August 29, 2010) ##
+
+* Added ActiveModel::MassAssignmentSecurity *Eric Chapweske, Josh Kalderimis*
+
+* JSON supports a custom root option: to_json(:root => 'custom') #4515 *Jatinder Singh*
+
+* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use
+ persisted? instead. A model is persisted if it's not a new_record? and it was
+ not destroyed? *MG*
+
+* Added validations reflection in ActiveModel::Validations *JV*
+
+ Model.validators
+ Model.validators_on(:field)
+
+* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for
+ AMo objects with composite keys *MG*
+
+* ActiveModel::Observer#add_observer!
+
+ It has a custom hook to define after_find that should really be in a
+ ActiveRecord::Observer subclass:
+
+ def add_observer!(klass)
+ klass.add_observer(self)
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
+ end
+
+* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 *DHH*
+
+* Add validates_format_of :without => /regexp/ option. #430 *Elliot Winkler, Peer Allan*
+
+ Example :
+
+ validates_format_of :subdomain, :without => /www|admin|mail/
+
+* Introduce validates_with to encapsulate attribute validations in a class. #2630 *Jeff Dean*
+
+* Extracted from Active Record and Active Resource.
diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc
index a0e6b4d9b3..67701bc422 100644
--- a/activemodel/README.rdoc
+++ b/activemodel/README.rdoc
@@ -186,13 +186,13 @@ modules:
== Download and installation
-The latest version of Active Model can be installed with Rubygems:
+The latest version of Active Model can be installed with RubyGems:
% [sudo] gem install activemodel
Source code can be downloaded as part of the Rails project on GitHub
-* https://github.com/rails/rails/tree/master/activemodel/
+* https://github.com/rails/rails/tree/master/activemodel
== License
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 562f07fcd7..49f664bf89 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -13,11 +13,10 @@ Gem::Specification.new do |s|
s.email = 'david@loudthinking.com'
s.homepage = 'http://www.rubyonrails.org'
- s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
+ s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
s.add_dependency('activesupport', version)
s.add_dependency('builder', '~> 3.0.0')
s.add_dependency('i18n', '~> 0.6')
- s.add_dependency('bcrypt-ruby', '~> 2.1.4')
end
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index bdc0eb4a0d..dba059eacd 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/class/attribute'
+require 'active_support/deprecation'
module ActiveModel
class MissingAttributeError < NoMethodError
@@ -56,54 +57,38 @@ module ActiveModel
module AttributeMethods
extend ActiveSupport::Concern
- COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ NAME_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?=]?\z/
+ CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
class_attribute :attribute_method_matchers, :instance_writer => false
- self.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:
- #
- # AttributePerson.primary_key
- # # => "sysid"
- # AttributePerson.inheritance_column = 'address'
- # AttributePerson.inheritance_column
- # # => 'address_id'
- def define_attr_method(name, value=nil, &block)
+ def define_attr_method(name, value=nil, deprecation_warning = true, &block) #:nodoc:
+ # This deprecation_warning param is for internal use so that we can silence
+ # the warning from Active Record, because we are implementing more specific
+ # messages there instead.
+ #
+ # It doesn't apply to the original_#{name} method as we want to warn if
+ # people are calling that regardless.
+ if deprecation_warning
+ ActiveSupport::Deprecation.warn("define_attr_method is deprecated and will be removed without replacement.")
+ end
+
sing = singleton_class
sing.class_eval <<-eorb, __FILE__, __LINE__ + 1
- if method_defined?('original_#{name}')
- undef :'original_#{name}'
+ remove_possible_method :'original_#{name}'
+ remove_possible_method :'_original_#{name}'
+ alias_method :'_original_#{name}', :'#{name}'
+ define_method :'original_#{name}' do
+ ActiveSupport::Deprecation.warn(
+ "This method is generated by ActiveModel::AttributeMethods::ClassMethods#define_attr_method, " \
+ "which is deprecated and will be removed."
+ )
+ send(:'_original_#{name}')
end
- alias_method :'original_#{name}', :'#{name}'
eorb
if block_given?
sing.send :define_method, name, &block
@@ -111,7 +96,7 @@ module ActiveModel
# 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 =~ COMPILABLE_REGEXP
+ if name =~ NAME_COMPILABLE_REGEXP
sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end
RUBY
@@ -239,18 +224,7 @@ module ActiveModel
attribute_method_matchers.each do |matcher|
matcher_new = matcher.method_name(new_name).to_s
matcher_old = matcher.method_name(old_name).to_s
-
- if matcher_new =~ COMPILABLE_REGEXP && matcher_old =~ COMPILABLE_REGEXP
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{matcher_new}(*args)
- send(:#{matcher_old}, *args)
- end
- RUBY
- else
- define_method(matcher_new) do |*args|
- send(matcher_old, *args)
- end
- end
+ define_optimized_call self, matcher_new, matcher_old
end
end
@@ -284,33 +258,15 @@ module ActiveModel
def define_attribute_method(attr_name)
attribute_method_matchers.each do |matcher|
- unless instance_method_already_implemented?(matcher.method_name(attr_name))
- generate_method = "define_method_#{matcher.prefix}attribute#{matcher.suffix}"
+ method_name = matcher.method_name(attr_name)
+
+ unless instance_method_already_implemented?(method_name)
+ generate_method = "define_method_#{matcher.method_missing_target}"
if respond_to?(generate_method)
send(generate_method, attr_name)
else
- method_name = matcher.method_name(attr_name)
-
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- if method_defined?('#{method_name}')
- undef :'#{method_name}'
- end
- RUBY
-
- if method_name.to_s =~ COMPILABLE_REGEXP
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- def #{method_name}(*args)
- send(:#{matcher.method_missing_target}, '#{attr_name}', *args)
- end
- RUBY
- else
- generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1
- define_method('#{method_name}') do |*args|
- send('#{matcher.method_missing_target}', '#{attr_name}', *args)
- end
- RUBY
- end
+ define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -336,7 +292,7 @@ module ActiveModel
protected
def instance_method_already_implemented?(method_name)
- method_defined?(method_name)
+ generated_attribute_methods.method_defined?(method_name)
end
private
@@ -349,27 +305,65 @@ module ActiveModel
# used to alleviate the GC, which ultimately also speeds up the app
# significantly (in our case our test suite finishes 10% faster with
# this cache).
- def attribute_method_matchers_cache
+ def attribute_method_matchers_cache #:nodoc:
@attribute_method_matchers_cache ||= {}
end
- def attribute_method_matcher(method_name)
+ def attribute_method_matcher(method_name) #:nodoc:
if attribute_method_matchers_cache.key?(method_name)
attribute_method_matchers_cache[method_name]
else
+ # 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
- attribute_method_matchers.detect { |method| match = method.match(method_name) }
+ matchers.detect { |method| match = method.match(method_name) }
attribute_method_matchers_cache[method_name] = match
end
end
+ # Define a method `name` in `mod` that dispatches to `send`
+ # using the given `extra` args. This fallbacks `define_method`
+ # and `send` if the given names cannot be compiled.
+ def define_optimized_call(mod, name, send, *extra) #:nodoc:
+ if name =~ NAME_COMPILABLE_REGEXP
+ defn = "def #{name}(*args)"
+ else
+ defn = "define_method(:'#{name}') do |*args|"
+ end
+
+ extra = (extra.map(&:inspect) << "*args").join(", ")
+
+ if send =~ CALL_COMPILABLE_REGEXP
+ target = "#{send}(#{extra})"
+ else
+ target = "send(:'#{send}', #{extra})"
+ end
+
+ mod.module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ #{defn}
+ #{target}
+ end
+ RUBY
+ end
+
class AttributeMethodMatcher
attr_reader :prefix, :suffix, :method_missing_target
- AttributeMethodMatch = Struct.new(:target, :attr_name)
+ 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 " \
+ "necessary. If the un-prefixed/suffixed version of the method has not been " \
+ "defined when `define_attribute_methods` is called, it will be defined " \
+ "automatically."
+ )
+ end
+
@prefix, @suffix = options[:prefix] || '', options[:suffix] || ''
@regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/
@method_missing_target = "#{@prefix}attribute#{@suffix}"
@@ -378,7 +372,7 @@ module ActiveModel
def match(method_name)
if @regex =~ method_name
- AttributeMethodMatch.new(method_missing_target, $2)
+ AttributeMethodMatch.new(method_missing_target, $2, method_name)
else
nil
end
@@ -387,6 +381,10 @@ module ActiveModel
def method_name(attr_name)
@method_name % attr_name
end
+
+ def plain?
+ prefix.empty? && suffix.empty?
+ end
end
end
@@ -401,13 +399,21 @@ module ActiveModel
# It's also possible to instantiate related objects, so a Client class
# belonging to the clients table with a +master_id+ foreign key can
# instantiate master through Client#master.
- def method_missing(method_id, *args, &block)
- method_name = method_id.to_s
- if match = match_attribute_method?(method_name)
- guard_private_attribute_method!(method_name, args)
- return __send__(match.target, match.attr_name, *args, &block)
+ def method_missing(method, *args, &block)
+ if respond_to_without_attributes?(method, true)
+ super
+ else
+ match = match_attribute_method?(method.to_s)
+ match ? attribute_missing(match, *args, &block) : super
end
- super
+ end
+
+ # attribute_missing is like method_missing, but for attributes. When method_missing is
+ # called we check to see if there is a matching attribute method. If so, we call
+ # attribute_missing to dispatch the attribute. This method can be overloaded to
+ # customise the behaviour.
+ def attribute_missing(match, *args, &block)
+ __send__(match.target, match.attr_name, *args, &block)
end
# A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>,
@@ -416,15 +422,14 @@ module ActiveModel
alias :respond_to_without_attributes? :respond_to?
def respond_to?(method, include_private_methods = false)
if super
- return true
+ true
elsif !include_private_methods && super(method, true)
# If we're here then we haven't found among non-private methods
# but found among all methods. Which means that the given method is private.
- return false
- elsif match_attribute_method?(method.to_s)
- return true
+ false
+ else
+ !match_attribute_method?(method.to_s).nil?
end
- super
end
protected
@@ -440,13 +445,6 @@ module ActiveModel
match && attribute_method?(match.attr_name) ? match : nil
end
- # prevent method_missing from calling private methods with #send
- def guard_private_attribute_method!(method_name, args)
- if self.class.private_method_defined?(method_name)
- raise NoMethodError.new("Attempt to call private method `#{method_name}'", method_name, args)
- end
- end
-
def missing_attribute(attr_name, stack)
raise ActiveModel::MissingAttributeError, "missing attribute: #{attr_name}", stack
end
diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb
index 37d0c9a0b9..15103f1185 100644
--- a/activemodel/lib/active_model/callbacks.rb
+++ b/activemodel/lib/active_model/callbacks.rb
@@ -41,7 +41,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.
@@ -93,7 +93,7 @@ module ActiveModel
:only => [:before, :around, :after]
}.merge(options)
- types = Array.wrap(options.delete(:only))
+ types = Array.wrap(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb
index 39977f12c3..80a3ba51c3 100644
--- a/activemodel/lib/active_model/conversion.rb
+++ b/activemodel/lib/active_model/conversion.rb
@@ -4,7 +4,7 @@ require 'active_support/inflector'
module ActiveModel
# == Active Model Conversions
#
- # Handles default conversions: to_model, to_key, to_param, and to_path.
+ # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
#
# Let's take for example this non-persisted object.
#
@@ -54,15 +54,15 @@ module ActiveModel
# Returns a string identifying the path associated with the object.
# ActionPack uses this to find a suitable partial to represent the object.
- def to_path
- self.class._to_path
+ def to_partial_path
+ self.class._to_partial_path
end
module ClassMethods #:nodoc:
# Provide a class level cache for the to_path. This is an
# internal method and should not be accessed directly.
- def _to_path #:nodoc:
- @_to_path ||= begin
+ def _to_partial_path #:nodoc:
+ @_to_partial_path ||= begin
element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self))
collection = ActiveSupport::Inflector.tableize(self)
"#{collection}/#{element}".freeze
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index e3e71525fa..026f077ee7 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -29,7 +29,7 @@ module ActiveModel
#
# include ActiveModel::Dirty
#
- # define_attribute_methods = [:name]
+ # define_attribute_methods [:name]
#
# def name
# @name
@@ -98,7 +98,7 @@ module ActiveModel
# person.name = 'bob'
# person.changed? # => true
def changed?
- !changed_attributes.empty?
+ changed_attributes.any?
end
# List of attributes with unsaved changes.
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 1cf8144e98..8337b04c0d 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -49,7 +49,7 @@ module ActiveModel
#
# The last three methods are required in your object for Errors to be
# able to generate error messages correctly and also handle multiple
- # languages. Of course, if you extend your object with ActiveModel::Translations
+ # languages. Of course, if you extend your object with ActiveModel::Translation
# you will not need to implement the last two. Likewise, using
# ActiveModel::Validations will handle the validation related methods
# for you.
@@ -63,7 +63,7 @@ module ActiveModel
class Errors
include Enumerable
- CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank]
+ CALLBACKS_OPTIONS = [:if, :unless, :on, :allow_nil, :allow_blank, :strict]
attr_reader :messages
@@ -88,6 +88,7 @@ module ActiveModel
def include?(error)
(v = messages[error]) && v.any?
end
+ alias :has_key? :include?
# Get messages for +key+
def get(key)
@@ -174,11 +175,12 @@ module ActiveModel
to_a.size
end
- # Returns true if there are any errors, false if not.
+ # Returns true if no errors are found, false otherwise.
def empty?
all? { |k, v| v && v.empty? }
end
alias_method :blank?, :empty?
+
# Returns an xml formatted representation of the Errors hash.
#
# p.errors.add(:name, "can't be blank")
@@ -203,20 +205,16 @@ module ActiveModel
messages.dup
end
- # Adds +message+ to the error messages on +attribute+, which will be returned on a call to
- # <tt>on(attribute)</tt> for the same attribute. More than one error can be added to the same
- # +attribute+ in which case an array will be returned on a call to <tt>on(attribute)</tt>.
+ # 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 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 ||= :invalid
-
- if message.is_a?(Symbol)
- message = generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
- elsif message.is_a?(Proc)
- message = message.call
+ message = normalize_message(attribute, message, options)
+ if options[:strict]
+ raise ActiveModel::StrictValidationFailed, message
end
self[attribute] << message
@@ -239,6 +237,15 @@ 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
+ def added?(attribute, message = nil, options = {})
+ message = normalize_message(attribute, message, options)
+ self[attribute].include? message
+ end
+
# Returns all the full error messages in an array.
#
# class Company
@@ -250,20 +257,22 @@ module ActiveModel
# company.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|
- if attribute == :base
- message
- else
- attr_name = attribute.to_s.gsub('.', '_').humanize
- attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
-
- I18n.t(:"errors.format", {
- :default => "%{attribute} %{message}",
- :attribute => attr_name,
- :message => message
- })
- end
- }
+ 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"
+ def full_message(attribute, message)
+ return message if attribute == :base
+ attr_name = attribute.to_s.gsub('.', '_').humanize
+ attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
+ I18n.t(:"errors.format", {
+ :default => "%{attribute} %{message}",
+ :attribute => attr_name,
+ :message => message
+ })
end
# Translates an error message in its default scope
@@ -293,13 +302,17 @@ module ActiveModel
def generate_message(attribute, type = :invalid, options = {})
type = options.delete(:message) if options[:message].is_a?(Symbol)
- defaults = @base.class.lookup_ancestors.map do |klass|
- [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
- :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
+ if @base.class.respond_to?(:i18n_scope)
+ defaults = @base.class.lookup_ancestors.map do |klass|
+ [ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.attributes.#{attribute}.#{type}",
+ :"#{@base.class.i18n_scope}.errors.models.#{klass.model_name.i18n_key}.#{type}" ]
+ end
+ else
+ defaults = []
end
defaults << options.delete(:message)
- defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}"
+ defaults << :"#{@base.class.i18n_scope}.errors.messages.#{type}" if @base.class.respond_to?(:i18n_scope)
defaults << :"errors.attributes.#{attribute}.#{type}"
defaults << :"errors.messages.#{type}"
@@ -318,5 +331,21 @@ module ActiveModel
I18n.translate(key, options)
end
+
+ private
+ def normalize_message(attribute, message, options)
+ message ||= :invalid
+
+ if message.is_a?(Symbol)
+ generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS))
+ elsif message.is_a?(Proc)
+ message.call
+ else
+ message
+ end
+ end
+ end
+
+ class StrictValidationFailed < StandardError
end
end
diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb
index 08c2e5fcf3..bfe7ea1869 100644
--- a/activemodel/lib/active_model/lint.rb
+++ b/activemodel/lib/active_model/lint.rb
@@ -43,14 +43,14 @@ module ActiveModel
assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
end
- # == Responds to <tt>to_path</tt>
+ # == Responds to <tt>to_partial_path</tt>
#
# 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_path
- assert model.respond_to?(:to_path), "The model should respond to to_path"
- assert_kind_of String, model.to_path
+ 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
end
# == Responds to <tt>valid?</tt>
diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
index bb0526adc3..bbdddfb50d 100644
--- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb
@@ -44,8 +44,13 @@ module ActiveModel
class StrictSanitizer < Sanitizer
def process_removed_attributes(attrs)
+ return if (attrs - insensitive_attributes).empty?
raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}"
end
+
+ def insensitive_attributes
+ ['id']
+ end
end
class Error < StandardError
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index 26fa3062eb..953d24a3b2 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -8,23 +8,26 @@ module ActiveModel
attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key
alias_method :cache_key, :collection
- deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_path on model instances directly instead."
+ deprecate :partial_path => "ActiveModel::Name#partial_path is deprecated. Call #to_partial_path on model instances directly instead."
def initialize(klass, namespace = nil, name = nil)
name ||= klass.name
+
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
+
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
+ @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
+ @param_key = (namespace ? _singularize(@unnamespaced) : @singular).freeze
+ @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural).freeze
+ @i18n_key = self.underscore.to_sym
end
# Transform the model name into a more humane format, using I18n. By default,
@@ -79,7 +82,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
diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb
index d48f2e8a1f..cd8eb357de 100644
--- a/activemodel/lib/active_model/observing.rb
+++ b/activemodel/lib/active_model/observing.rb
@@ -187,8 +187,7 @@ module ActiveModel
def observe(*models)
models.flatten!
models.collect! { |model| model.respond_to?(:to_sym) ? model.to_s.camelize.constantize : model }
- remove_possible_method(:observed_classes)
- define_method(:observed_classes) { models }
+ redefine_method(:observed_classes) { models }
end
# Returns an array of Classes to observe.
@@ -226,10 +225,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) #:nodoc:
+ def update(observed_method, object, &block) #:nodoc:
return unless respond_to?(observed_method)
return if disabled_for?(object)
- send(observed_method, object)
+ send(observed_method, object, &block)
end
# Special method sent by the observed class when it is inherited.
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 63380d6ffd..db78864c67 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -1,5 +1,3 @@
-require 'bcrypt'
-
module ActiveModel
module SecurePassword
extend ActiveSupport::Concern
@@ -12,6 +10,10 @@ module ActiveModel
# a "password_confirmation" attribute) are automatically added.
# 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:
+ #
+ # gem 'bcrypt-ruby', '~> 3.0.0'
+ #
# Example using Active Record (which automatically includes ActiveModel::SecurePassword):
#
# # Schema: User(name:string, password_digest:string)
@@ -30,6 +32,11 @@ module ActiveModel
# User.find_by_name("david").try(:authenticate, "notright") # => nil
# User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user
def has_secure_password
+ # Load bcrypt-ruby only when has_secure_password is used.
+ # This is to avoid ActiveModel (and by extension the entire framework) being dependent on a binary library.
+ gem 'bcrypt-ruby', '~> 3.0.0'
+ require 'bcrypt'
+
attr_reader :password
validates_confirmation_of :password
diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb
index 9260c5082d..a4b58ab456 100644
--- a/activemodel/lib/active_model/serialization.rb
+++ b/activemodel/lib/active_model/serialization.rb
@@ -1,5 +1,7 @@
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
@@ -76,8 +78,11 @@ module ActiveModel
attribute_names -= Array.wrap(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) }
- hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }]
+ method_names.each { |n| hash[n] = send(n) }
serializable_add_includes(options) do |association, records, opts|
hash[association] = if records.is_a?(Enumerable)
@@ -91,17 +96,37 @@ module ActiveModel
end
private
+
+ # Hook method defining how an attribute value should be retrieved for
+ # serialization. By default this is assumed to be an instance named after
+ # the attribute. Override this method in subclasses should you need to
+ # retrieve the value for a given attribute differently:
+ #
+ # class MyClass
+ # include ActiveModel::Validations
+ #
+ # def initialize(data = {})
+ # @data = data
+ # end
+ #
+ # def read_attribute_for_serialization(key)
+ # @data[key]
+ # end
+ # end
+ #
+ alias :read_attribute_for_serialization :send
+
# Add associations specified via the <tt>:include</tt> option.
#
# Expects a block that takes as arguments:
# +association+ - name of the association
# +records+ - the association record(s) to be serialized
# +opts+ - options for the association records
- def serializable_add_includes(options = {})
+ def serializable_add_includes(options = {}) #:nodoc:
return unless include = options[:include]
unless include.is_a?(Hash)
- include = Hash[Array.wrap(include).map { |n| [n, {}] }]
+ include = Hash[Array.wrap(include).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
end
include.each do |association, opts|
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 4fbccd7419..c845440120 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -15,7 +15,7 @@ module ActiveModel
self.include_root_in_json = true
end
- # Returns a JSON string representing the model. Some configuration can be
+ # 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
@@ -42,7 +42,7 @@ module ActiveModel
# The remainder of the examples in this section assume include_root_in_json is set to
# <tt>false</tt>.
#
- # Without any +options+, the returned JSON string will include all the model's
+ # Without any +options+, the returned Hash will include all the model's
# attributes. For example:
#
# user = User.find(1)
@@ -86,21 +86,15 @@ module ActiveModel
# "title": "Welcome to the weblog"},
# {"comments": [{"body": "Don't think too hard"}],
# "title": "So I was thinking"}]}
-
def as_json(options = nil)
- hash = serializable_hash(options)
-
- include_root = include_root_in_json
- if options.try(:key?, :root)
- include_root = options[:root]
+ root = include_root_in_json
+ root = options[:root] if options.try(:key?, :root)
+ if root
+ root = self.class.model_name.element if root == true
+ { root => serializable_hash(options) }
+ else
+ serializable_hash(options)
end
-
- if include_root
- custom_root = options && options[:root]
- hash = { custom_root || self.class.model_name.element => hash }
- end
-
- hash
end
def from_json(json, include_root=include_root_in_json)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index 64dda3bcee..d61d9d7119 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -15,10 +15,10 @@ module ActiveModel
class Attribute #:nodoc:
attr_reader :name, :value, :type
- def initialize(name, serializable, raw_value=nil)
+ def initialize(name, serializable, value)
@name, @serializable = name, serializable
- raw_value = raw_value.in_time_zone if raw_value.respond_to?(:in_time_zone)
- @value = raw_value || @serializable.send(name)
+ value = value.in_time_zone if value.respond_to?(:in_time_zone)
+ @value = value
@type = compute_type
end
@@ -49,40 +49,24 @@ module ActiveModel
def initialize(serializable, options = nil)
@serializable = serializable
@options = options ? options.dup : {}
-
- @options[:only] = Array.wrap(@options[:only]).map { |n| n.to_s }
- @options[:except] = Array.wrap(@options[:except]).map { |n| n.to_s }
end
- # To replicate the behavior in ActiveRecord#attributes, <tt>:except</tt>
- # takes precedence over <tt>:only</tt>. If <tt>:only</tt> is not set
- # for a N level model but is set for the N+1 level models,
- # then because <tt>:except</tt> is set to a default value, the second
- # level model can have both <tt>:except</tt> and <tt>:only</tt> set. So if
- # <tt>:only</tt> is set, always delete <tt>:except</tt>.
- def attributes_hash
- attributes = @serializable.attributes
- if options[:only].any?
- attributes.slice(*options[:only])
- elsif options[:except].any?
- attributes.except(*options[:except])
- else
- attributes
- end
+ def serializable_hash
+ @serializable.serializable_hash(@options.except(:include))
end
- def serializable_attributes
- attributes_hash.map do |name, value|
- self.class::Attribute.new(name, @serializable, value)
+ def serializable_collection
+ methods = Array.wrap(options[:methods]).map(&:to_s)
+ serializable_hash.map do |name, value|
+ name = name.to_s
+ if methods.include?(name)
+ self.class::MethodAttribute.new(name, @serializable, value)
+ else
+ self.class::Attribute.new(name, @serializable, value)
+ end
end
end
- def serializable_methods
- Array.wrap(options[:methods]).map do |name|
- self.class::MethodAttribute.new(name.to_s, @serializable) if @serializable.respond_to?(name.to_s)
- end.compact
- end
-
def serialize
require 'builder' unless defined? ::Builder
@@ -114,7 +98,7 @@ module ActiveModel
end
def add_attributes_and_methods
- (serializable_attributes + serializable_methods).each do |attribute|
+ serializable_collection.each do |attribute|
key = ActiveSupport::XmlMini.rename_key(attribute.name, options)
ActiveSupport::XmlMini.to_tag(key, attribute.value,
options.merge(attribute.decorations))
diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb
index 01907ac9da..e628c6f306 100644
--- a/activemodel/lib/active_model/validations/acceptance.rb
+++ b/activemodel/lib/active_model/validations/acceptance.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <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
def validates_acceptance_of(*attr_names)
validates_with AcceptanceValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb
index a9dcb0b505..6573a7d264 100644
--- a/activemodel/lib/active_model/validations/confirmation.rb
+++ b/activemodel/lib/active_model/validations/confirmation.rb
@@ -58,6 +58,8 @@ module ActiveModel
# <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
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 d3b8d31502..644cc814a7 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -59,6 +59,8 @@ 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.
+ # 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 090e8cfbae..d3faa8c6a6 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -84,6 +84,8 @@ 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.
+ # 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 9a9270d615..147e2ecb69 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -59,6 +59,8 @@ 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.
+ # 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 144e73904e..6bc928bab7 100644
--- a/activemodel/lib/active_model/validations/length.rb
+++ b/activemodel/lib/active_model/validations/length.rb
@@ -23,7 +23,7 @@ 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|
@@ -96,6 +96,8 @@ 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.
+ # 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 0d1903362c..34d447a0fa 100644
--- a/activemodel/lib/active_model/validations/numericality.rb
+++ b/activemodel/lib/active_model/validations/numericality.rb
@@ -107,6 +107,8 @@ 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.
+ # 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:
# * <tt>:greater_than</tt>
diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb
index cfb4c33dcc..9a643a6f5c 100644
--- a/activemodel/lib/active_model/validations/presence.rb
+++ b/activemodel/lib/active_model/validations/presence.rb
@@ -25,16 +25,18 @@ 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.
+ # See <tt>ActiveModel::Validation#validates!</tt> for more information
#
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index 7ff42de00b..8e09f6ac35 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -27,7 +27,7 @@ module ActiveModel
#
# class EmailValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << (options[:message] || "is not an email") unless
+ # record.errors.add attribute, (options[:message] || "is not an email") unless
# value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
# end
# end
@@ -48,7 +48,7 @@ module ActiveModel
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << "must start with 'the'" unless value =~ /\Athe/i
+ # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
# end
# end
#
@@ -57,7 +57,7 @@ module ActiveModel
#
# Additionally validator classes may be in another namespace and still used within any class.
#
- # validates :name, :'file/title' => true
+ # validates :name, :'film/title' => true
#
# The validators hash can also handle regular expressions, ranges,
# arrays and strings in shortcut form, e.g.
@@ -70,8 +70,8 @@ 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+ and +:allow_nil+ can be given
- # to one specific validator, as a hash:
+ # 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
#
@@ -101,12 +101,24 @@ 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.
+ def validates!(*attributes)
+ options = attributes.extract_options!
+ options[:strict] = true
+ validates(*(attributes << options))
+ end
+
protected
# 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 ]
+ [ :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 a87b213fe4..72b8562b93 100644
--- a/activemodel/lib/active_model/validations/with.rb
+++ b/activemodel/lib/active_model/validations/with.rb
@@ -32,7 +32,7 @@ module ActiveModel
# class MyValidator < ActiveModel::Validator
# def validate(record)
# if some_complex_logic
- # record.errors[:base] << "This record is invalid"
+ # record.errors.add :base, "This record is invalid"
# end
# end
#
@@ -56,12 +56,14 @@ 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>).
# 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>:
#
@@ -140,4 +142,4 @@ module ActiveModel
end
end
end
-end \ No newline at end of file
+end
diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb
index 5304743389..35ec98c822 100644
--- a/activemodel/lib/active_model/validator.rb
+++ b/activemodel/lib/active_model/validator.rb
@@ -48,8 +48,8 @@ module ActiveModel #:nodoc:
#
# class MyValidator < ActiveModel::Validator
# def validate(record)
- # record.errors[:base] << "This is some custom error message"
- # record.errors[:first_name] << "This is some complex validation"
+ # record.errors.add :base, "This is some custom error message"
+ # record.errors.add :first_name, "This is some complex validation"
# # etc...
# end
# end
@@ -57,7 +57,7 @@ module ActiveModel #:nodoc:
# To add behavior to the initialize method, use the following signature:
#
# class MyValidator < ActiveModel::Validator
- # def initialize(record, options)
+ # def initialize(options)
# super
# @my_custom_field = options[:field_name] || :first_name
# end
@@ -68,7 +68,7 @@ module ActiveModel #:nodoc:
#
# class TitleValidator < ActiveModel::EachValidator
# def validate_each(record, attribute, value)
- # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
+ # record.errors.add attribute, 'must be Mr. Mrs. or Dr.' unless value.in?(['Mr.', 'Mrs.', 'Dr.'])
# end
# end
#
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index 9840e3364c..90f9b78334 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -3,8 +3,6 @@ require 'cases/helper'
class ModelWithAttributes
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
class << self
define_method(:bar) do
'original bar'
@@ -24,14 +22,31 @@ end
class ModelWithAttributes2
include ActiveModel::AttributeMethods
+ attr_accessor :attributes
+
attribute_method_suffix '_test'
+
+private
+ def attribute(name)
+ attributes[name.to_s]
+ end
+
+ alias attribute_test attribute
+
+ def private_method
+ "<3 <3"
+ end
+
+protected
+
+ def protected_method
+ "O_o O_o"
+ end
end
class ModelWithAttributesWithSpaces
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
def attributes
{ :'foo bar' => 'value of foo bar'}
end
@@ -45,8 +60,6 @@ end
class ModelWithWeirdNamesAttributes
include ActiveModel::AttributeMethods
- attribute_method_suffix ''
-
class << self
define_method(:'c?d') do
'original c?d'
@@ -76,6 +89,29 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo", ModelWithAttributes.new.foo
end
+ test '#define_attribute_method does not generate attribute method if already defined in attribute module' do
+ klass = Class.new(ModelWithAttributes)
+ klass.generated_attribute_methods.module_eval do
+ def foo
+ '<3'
+ end
+ end
+ klass.define_attribute_method(:foo)
+
+ assert_equal '<3', klass.new.foo
+ end
+
+ test '#define_attribute_method generates a method that is already defined on the host' do
+ klass = Class.new(ModelWithAttributes) do
+ def foo
+ super
+ end
+ end
+ klass.define_attribute_method(:foo)
+
+ assert_equal 'value of foo', klass.new.foo
+ end
+
test '#define_attribute_method generates attribute method with invalid identifier characters' do
ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b')
@@ -98,20 +134,33 @@ class AttributeMethodsTest < ActiveModel::TestCase
end
test '#define_attr_method generates attribute method' do
- ModelWithAttributes.define_attr_method(:bar, 'bar')
+ assert_deprecated do
+ ModelWithAttributes.define_attr_method(:bar, 'bar')
+ end
assert_respond_to ModelWithAttributes, :bar
- assert_equal "original bar", ModelWithAttributes.original_bar
+
+ assert_deprecated do
+ assert_equal "original bar", ModelWithAttributes.original_bar
+ end
+
assert_equal "bar", ModelWithAttributes.bar
- ModelWithAttributes.define_attr_method(:bar)
+ ActiveSupport::Deprecation.silence do
+ ModelWithAttributes.define_attr_method(:bar)
+ end
assert !ModelWithAttributes.bar
end
test '#define_attr_method generates attribute method with invalid identifier characters' do
- ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d')
+ ActiveSupport::Deprecation.silence do
+ ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d')
+ end
assert_respond_to ModelWithWeirdNamesAttributes, :'c?d'
- assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d')
+
+ ActiveSupport::Deprecation.silence do
+ assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d')
+ end
assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d')
end
@@ -129,4 +178,64 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert !ModelWithAttributes.new.respond_to?(:foo)
assert_raises(NoMethodError) { ModelWithAttributes.new.foo }
end
+
+ test 'acessing a suffixed attribute' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'foo' => 'bar' }
+
+ assert_equal 'bar', m.foo
+ assert_equal 'bar', m.foo_test
+ end
+
+ test 'explicitly specifying an empty prefix/suffix is deprecated' do
+ klass = Class.new(ModelWithAttributes)
+
+ assert_deprecated { klass.attribute_method_suffix '' }
+ assert_deprecated { klass.attribute_method_prefix '' }
+
+ klass.define_attribute_methods([:foo])
+
+ assert_equal 'value of foo', klass.new.foo
+ end
+
+ test 'should not interfere with method_missing if the attr has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ # dispatches to the *method*, not the attribute
+ assert_equal '<3 <3', m.send(:private_method)
+ assert_equal 'O_o O_o', m.send(:protected_method)
+
+ # sees that a method is already defined, so doesn't intervene
+ assert_raises(NoMethodError) { m.private_method }
+ assert_raises(NoMethodError) { m.protected_method }
+ end
+
+ test 'should not interfere with respond_to? if the attribute has a private/protected method' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' }
+
+ assert !m.respond_to?(:private_method)
+ assert m.respond_to?(:private_method, true)
+
+ # This is messed up, but it's how Ruby works at the moment. Apparently it will be changed
+ # in the future.
+ assert m.respond_to?(:protected_method)
+ assert m.respond_to?(:protected_method, true)
+ end
+
+ test 'should use attribute_missing to dispatch a missing attribute' do
+ m = ModelWithAttributes2.new
+ m.attributes = { 'foo' => 'bar' }
+
+ def m.attribute_missing(match, *args, &block)
+ match
+ end
+
+ match = m.foo_test
+
+ assert_equal 'foo', match.attr_name
+ assert_equal 'attribute_test', match.target
+ assert_equal 'foo_test', match.method_name
+ end
end
diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb
index 2eccc4e56d..24552bcaf2 100644
--- a/activemodel/test/cases/conversion_test.rb
+++ b/activemodel/test/cases/conversion_test.rb
@@ -25,8 +25,8 @@ class ConversionTest < ActiveModel::TestCase
end
test "to_path default implementation returns a string giving a relative path" do
- assert_equal "contacts/contact", Contact.new.to_path
- assert_equal "helicopters/helicopter", Helicopter.new.to_path,
- "ActiveModel::Conversion#to_path caching should be class-specific"
+ assert_equal "contacts/contact", Contact.new.to_partial_path
+ assert_equal "helicopters/helicopter", Helicopter.new.to_partial_path,
+ "ActiveModel::Conversion#to_partial_path caching should be class-specific"
end
end
diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb
index 85ca8ca835..8ddedb160a 100644
--- a/activemodel/test/cases/errors_test.rb
+++ b/activemodel/test/cases/errors_test.rb
@@ -33,6 +33,12 @@ class ErrorsTest < ActiveModel::TestCase
assert errors.include?(:foo), 'errors should include :foo'
end
+ def test_has_key?
+ errors = ActiveModel::Errors.new(self)
+ errors[:foo] = 'omg'
+ assert errors.has_key?(:foo), 'errors should have key :foo'
+ end
+
test "should return true if no errors" do
person = Person.new
person.errors[:foo]
@@ -46,7 +52,6 @@ class ErrorsTest < ActiveModel::TestCase
person.validate!
assert_equal ["name can not be nil"], person.errors.full_messages
assert_equal ["can not be nil"], person.errors[:name]
-
end
test 'should be able to assign error' do
@@ -61,6 +66,63 @@ class ErrorsTest < ActiveModel::TestCase
assert_equal ["can not be blank"], person.errors[:name]
end
+ test "should be able to add an error with a symbol" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ message = person.errors.generate_message(:name, :blank)
+ assert_equal [message], person.errors[:name]
+ end
+
+ test "should be able to add an error with a proc" do
+ person = Person.new
+ message = Proc.new { "can not be blank" }
+ person.errors.add(:name, message)
+ assert_equal ["can not be blank"], person.errors[:name]
+ end
+
+ test "added? should be true if that error was added" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ assert person.errors.added?(:name, "can not be blank")
+ end
+
+ test "added? should handle when message is a symbol" do
+ person = Person.new
+ person.errors.add(:name, :blank)
+ assert person.errors.added?(:name, :blank)
+ end
+
+ test "added? should handle when message is a proc" do
+ person = Person.new
+ message = Proc.new { "can not be blank" }
+ person.errors.add(:name, message)
+ assert person.errors.added?(:name, message)
+ end
+
+ test "added? should default message to :invalid" do
+ person = Person.new
+ person.errors.add(:name, :invalid)
+ assert person.errors.added?(:name)
+ end
+
+ test "added? should be true when several errors are present, and we ask for one of them" do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "is invalid")
+ assert person.errors.added?(:name, "can not be blank")
+ end
+
+ test "added? should be false if no errors are present" do
+ person = Person.new
+ assert !person.errors.added?(:name)
+ end
+
+ test "added? should be false when an error is present, but we check for another error" do
+ person = Person.new
+ person.errors.add(:name, "is invalid")
+ assert !person.errors.added?(:name, "can not be blank")
+ end
+
test 'should respond to size' do
person = Person.new
person.errors.add(:name, "can not be blank")
@@ -72,7 +134,6 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "can not be blank")
person.errors.add(:name, "can not be nil")
assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
-
end
test 'to_hash should return an ordered hash' do
@@ -80,4 +141,40 @@ class ErrorsTest < ActiveModel::TestCase
person.errors.add(:name, "can not be blank")
assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash
end
+
+ test 'full_messages should return an array of error messages, with the attribute name included' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a
+ end
+
+ test 'full_message should return the given message if attribute equals :base' do
+ person = Person.new
+ assert_equal "press the button", person.errors.full_message(:base, "press the button")
+ end
+
+ test 'full_message should return the given message with the attribute name included' do
+ person = Person.new
+ assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank")
+ end
+
+ test 'should return a JSON hash representation of the errors' do
+ person = Person.new
+ person.errors.add(:name, "can not be blank")
+ person.errors.add(:name, "can not be nil")
+ person.errors.add(:email, "is invalid")
+ hash = person.errors.as_json
+ assert_equal ["can not be blank", "can not be nil"], hash[:name]
+ assert_equal ["is invalid"], hash[:email]
+ end
+
+ test "generate_message should work without i18n_scope" do
+ person = Person.new
+ assert !Person.respond_to?(:i18n_scope)
+ assert_nothing_raised {
+ person.errors.generate_message(:name, :blank)
+ }
+ end
end
+
diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
index 62a6ec9c9b..676937b5e1 100644
--- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb
@@ -7,7 +7,7 @@ class SanitizerTest < ActiveModel::TestCase
class Authorizer < ActiveModel::MassAssignmentSecurity::PermissionSet
def deny?(key)
- key.in?(['admin'])
+ ['admin', 'id'].include?(key)
end
end
@@ -40,4 +40,12 @@ class SanitizerTest < ActiveModel::TestCase
end
end
+ test "mass assignment insensitive attributes" do
+ original_attributes = {'id' => 1, 'first_name' => 'allowed'}
+
+ assert_nothing_raised do
+ @strict_sanitizer.sanitize(original_attributes, @authorizer)
+ end
+ end
+
end
diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb
index bafe4f3c0c..e8db73ba52 100644
--- a/activemodel/test/cases/naming_test.rb
+++ b/activemodel/test/cases/naming_test.rb
@@ -26,7 +26,7 @@ class NamingTest < ActiveModel::TestCase
end
def test_partial_path
- assert_deprecated(/#partial_path.*#to_path/) do
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
assert_equal 'post/track_backs/track_back', @model_name.partial_path
end
end
@@ -58,7 +58,7 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
end
def test_partial_path
- assert_deprecated(/#partial_path.*#to_path/) do
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
assert_equal 'blog/posts/post', @model_name.partial_path
end
end
@@ -74,10 +74,6 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase
def test_param_key
assert_equal 'post', @model_name.param_key
end
-
- def test_recognizing_namespace
- assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced")
- end
end
class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
@@ -102,7 +98,7 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase
end
def test_partial_path
- assert_deprecated(/#partial_path.*#to_path/) do
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
assert_equal 'blog/posts/post', @model_name.partial_path
end
end
@@ -142,13 +138,13 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
end
def test_partial_path
- assert_deprecated(/#partial_path.*#to_path/) do
+ assert_deprecated(/#partial_path.*#to_partial_path/) do
assert_equal 'articles/article', @model_name.partial_path
end
end
def test_human
- 'Article'
+ assert_equal 'Article', @model_name.human
end
def test_route_key
@@ -160,6 +156,40 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase
end
end
+class NamingUsingRelativeModelNameTest < ActiveModel::TestCase
+ def setup
+ @model_name = Blog::Post.model_name
+ end
+
+ def test_singular
+ assert_equal 'blog_post', @model_name.singular
+ end
+
+ def test_plural
+ assert_equal 'blog_posts', @model_name.plural
+ end
+
+ def test_element
+ assert_equal 'post', @model_name.element
+ end
+
+ def test_collection
+ assert_equal 'blog/posts', @model_name.collection
+ end
+
+ def test_human
+ assert_equal 'Post', @model_name.human
+ end
+
+ def test_route_key
+ assert_equal 'posts', @model_name.route_key
+ end
+
+ def test_param_key
+ assert_equal 'post', @model_name.param_key
+ end
+end
+
class NamingHelpersTest < Test::Unit::TestCase
def setup
@klass = Contact
@@ -217,3 +247,16 @@ class NamingHelpersTest < Test::Unit::TestCase
ActiveModel::Naming.send(method, *args)
end
end
+
+class NameWithAnonymousClassTest < Test::Unit::TestCase
+ def test_anonymous_class_without_name_argument
+ assert_raises(ArgumentError) do
+ ActiveModel::Name.new(Class.new)
+ end
+ end
+
+ def test_anonymous_class_with_name_argument
+ model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous")
+ assert_equal "Anonymous", model_name
+ end
+end
diff --git a/activemodel/test/cases/observing_test.rb b/activemodel/test/cases/observing_test.rb
index 99b1f407ae..f6ec24ae57 100644
--- a/activemodel/test/cases/observing_test.rb
+++ b/activemodel/test/cases/observing_test.rb
@@ -17,6 +17,10 @@ class FooObserver < ActiveModel::Observer
def on_spec(record)
stub.event_with(record) if stub
end
+
+ def around_save(record)
+ yield :in_around_save
+ end
end
class Foo
@@ -133,4 +137,12 @@ class ObserverTest < ActiveModel::TestCase
foo = Foo.new
Foo.send(:notify_observers, :whatever, foo)
end
+
+ test "update passes a block on to the observer" do
+ yielded_value = nil
+ FooObserver.instance.update(:around_save, Foo.new) do |val|
+ yielded_value = val
+ end
+ assert_equal :in_around_save, yielded_value
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 6950c3be1f..4338a3fc53 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -10,15 +10,13 @@ class SecurePasswordTest < ActiveModel::TestCase
end
test "blank password" do
- user = User.new
- user.password = ''
- assert !user.valid?, 'user should be invalid'
+ @user.password = ''
+ assert !@user.valid?, 'user should be invalid'
end
test "nil password" do
- user = User.new
- user.password = nil
- assert !user.valid?, 'user should be invalid'
+ @user.password = nil
+ assert !@user.valid?, 'user should be invalid'
end
test "password must be present" do
diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb
index 5122f08eec..b8dad9d51f 100644
--- a/activemodel/test/cases/serialization_test.rb
+++ b/activemodel/test/cases/serialization_test.rb
@@ -77,6 +77,15 @@ class SerializationTest < ActiveModel::TestCase
assert_equal expected , @user.serializable_hash(:methods => [:bar])
end
+ def test_should_use_read_attribute_for_serialization
+ def @user.read_attribute_for_serialization(n)
+ "Jon"
+ end
+
+ expected = { "name" => "Jon" }
+ assert_equal expected, @user.serializable_hash(:only => :name)
+ end
+
def test_include_option_with_singular_association
expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com",
:address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}}
@@ -114,8 +123,29 @@ class SerializationTest < ActiveModel::TestCase
@user.friends.first.friends = [@user]
expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
:friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male',
- :friends => ["email"=>"david@example.com", "gender"=>"male", "name"=>"David"]},
+ :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]},
{"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]}
assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}})
end
+
+ def test_only_include
+ expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]}
+ assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}})
+ end
+
+ def test_except_include
+ expected = {"name"=>"David", "email"=>"david@example.com",
+ :friends => [{"name" => 'Joe', "email" => 'joe@example.com'},
+ {"name" => "Sue", "email" => 'sue@example.com'}]}
+ assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}})
+ end
+
+ def test_multiple_includes_with_options
+ expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David",
+ :address=>{"street"=>"123 Lane"},
+ :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'},
+ {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]}
+ assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends])
+ end
+
end
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 5e1e7d897a..4ac5fb1779 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -14,9 +14,11 @@ class Contact
end
end
+ remove_method :attributes if method_defined?(:attributes)
+
def attributes
instance_values
- end unless method_defined?(:attributes)
+ end
end
class JsonSerializationTest < ActiveModel::TestCase
@@ -56,6 +58,16 @@ class JsonSerializationTest < ActiveModel::TestCase
end
end
+ test "should include root in json (option) even if the default is set to false" do
+ begin
+ Contact.include_root_in_json = false
+ json = @contact.to_json(:root => true)
+ assert_match %r{^\{"contact":\{}, json
+ ensure
+ Contact.include_root_in_json = true
+ end
+ end
+
test "should not include root in json (option)" do
json = @contact.to_json(:root => false)
@@ -196,4 +208,14 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_no_match %r{"preferences":}, json
end
+ test "custom as_json options should be extendible" do
+ def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end
+ json = @contact.to_json
+
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_no_match %r{"created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}}, json
+ assert_no_match %r{"awesome":}, json
+ assert_no_match %r{"preferences":}, json
+ end
+
end
diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb
index a38ef8e223..38aecf51ff 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -9,6 +9,8 @@ class Contact
attr_accessor :address, :friends
+ remove_method :attributes if method_defined?(:attributes)
+
def attributes
instance_values.except("address", "friends")
end
@@ -33,6 +35,12 @@ class Address
end
end
+class SerializableContact < Contact
+ def serializable_hash(options={})
+ super(options.merge(:only => [:name, :age]))
+ end
+end
+
class XmlSerializationTest < ActiveModel::TestCase
def setup
@contact = Contact.new
@@ -96,6 +104,17 @@ class XmlSerializationTest < ActiveModel::TestCase
assert_match %r{<createdAt}, @xml
end
+ test "should use serialiable hash" do
+ @contact = SerializableContact.new
+ @contact.name = 'aaron stack'
+ @contact.age = 25
+
+ @xml = @contact.to_xml
+ assert_match %r{<name>aaron stack</name>}, @xml
+ assert_match %r{<age type="integer">25</age>}, @xml
+ assert_no_match %r{<awesome>}, @xml
+ end
+
test "should allow skipped types" do
@xml = @contact.to_xml :skip_types => true
assert_match %r{<age>25</age>}, @xml
diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb
index 72a383f128..adab8ccb2b 100644
--- a/activemodel/test/cases/validations/exclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/exclusion_validation_test.rb
@@ -46,12 +46,12 @@ class ExclusionValidationTest < ActiveModel::TestCase
def test_validates_exclusion_of_with_lambda
Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
- p = Topic.new
- p.title = "elephant"
- p.author_name = "sikachu"
- assert p.invalid?
+ t = Topic.new
+ t.title = "elephant"
+ t.author_name = "sikachu"
+ assert t.invalid?
- p.title = "wasabi"
- assert p.valid?
+ t.title = "wasabi"
+ assert t.valid?
end
end
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 2ce714fef0..41a1131bcb 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -101,25 +101,25 @@ class PresenceValidationTest < ActiveModel::TestCase
def test_validates_format_of_with_lambda
Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ }
- p = Topic.new
- p.title = "digit"
- p.content = "Pixies"
- assert p.invalid?
+ t = Topic.new
+ t.title = "digit"
+ t.content = "Pixies"
+ assert t.invalid?
- p.content = "1234"
- assert p.valid?
+ t.content = "1234"
+ assert t.valid?
end
def test_validates_format_of_without_lambda
Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ }
- p = Topic.new
- p.title = "characters"
- p.content = "1234"
- assert p.invalid?
+ t = Topic.new
+ t.title = "characters"
+ t.content = "1234"
+ assert t.invalid?
- p.content = "Pixies"
- assert p.valid?
+ t.content = "Pixies"
+ assert t.valid?
end
def test_validates_format_of_for_ruby_class
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 413da92de4..851d345eab 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -78,12 +78,12 @@ class InclusionValidationTest < ActiveModel::TestCase
def test_validates_inclusion_of_with_lambda
Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) }
- p = Topic.new
- p.title = "wasabi"
- p.author_name = "sikachu"
- assert p.invalid?
+ t = Topic.new
+ t.title = "wasabi"
+ t.author_name = "sikachu"
+ assert t.invalid?
- p.title = "elephant"
- assert p.valid?
+ t.title = "elephant"
+ assert t.valid?
end
end
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 0b50acf913..2f4376bd41 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -297,4 +297,37 @@ class ValidationsTest < ActiveModel::TestCase
assert auto.valid?
end
+
+ def test_strict_validation_in_validates
+ Topic.validates :title, :strict => true, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_not_fails
+ Topic.validates :title, :strict => true, :presence => true
+ assert Topic.new(:title => "hello").valid?
+ end
+
+ def test_strict_validation_particular_validator
+ Topic.validates :title, :presence => {:strict => true}
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_strict_validation_in_custom_validator_helper
+ Topic.validates_presence_of :title, :strict => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
+
+ def test_validates_with_bang
+ Topic.validates! :title, :presence => true
+ assert_raises ActiveModel::StrictValidationFailed do
+ Topic.new.valid?
+ end
+ end
end
diff --git a/activemodel/test/models/blog_post.rb b/activemodel/test/models/blog_post.rb
index d289177259..46eba857df 100644
--- a/activemodel/test/models/blog_post.rb
+++ b/activemodel/test/models/blog_post.rb
@@ -1,10 +1,6 @@
module Blog
- def self._railtie
- Object.new
- end
-
- def self.table_name_prefix
- "blog_"
+ def self.use_relative_model_naming?
+ true
end
class Post