aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG.md49
-rw-r--r--activemodel/activemodel.gemspec1
-rw-r--r--activemodel/lib/active_model.rb1
-rw-r--r--activemodel/lib/active_model/attribute_methods.rb13
-rw-r--r--activemodel/lib/active_model/configuration.rb134
-rw-r--r--activemodel/lib/active_model/dirty.rb59
-rw-r--r--activemodel/lib/active_model/mass_assignment_security.rb14
-rw-r--r--activemodel/lib/active_model/mass_assignment_security/permission_set.rb2
-rw-r--r--activemodel/lib/active_model/observer_array.rb16
-rw-r--r--activemodel/lib/active_model/serializers/json.rb89
-rw-r--r--activemodel/lib/active_model/serializers/xml.rb2
-rw-r--r--activemodel/lib/active_model/translation.rb4
-rw-r--r--activemodel/lib/active_model/validations.rb3
-rw-r--r--activemodel/lib/active_model/validations/exclusion.rb3
-rw-r--r--activemodel/lib/active_model/validations/format.rb23
-rw-r--r--activemodel/lib/active_model/validations/inclusion.rb4
-rw-r--r--activemodel/lib/active_model/validations/validates.rb1
-rw-r--r--activemodel/test/cases/attribute_methods_test.rb22
-rw-r--r--activemodel/test/cases/configuration_test.rb154
-rw-r--r--activemodel/test/cases/mass_assignment_security/permission_set_test.rb6
-rw-r--r--activemodel/test/cases/serializers/json_serialization_test.rb90
-rw-r--r--activemodel/test/cases/serializers/xml_serialization_test.rb2
-rw-r--r--activemodel/test/cases/translation_test.rb5
-rw-r--r--activemodel/test/cases/validations/format_validation_test.rb18
-rw-r--r--activemodel/test/cases/validations/i18n_validation_test.rb4
-rw-r--r--activemodel/test/cases/validations_test.rb5
26 files changed, 275 insertions, 449 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 789cff0673..847ae7f237 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,5 +1,34 @@
## Rails 4.0.0 (unreleased) ##
+* Changed `AM::Serializers::JSON.include_root_in_json' default value to false.
+ Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578.
+
+ class User < ActiveRecord::Base; end
+
+ class Person
+ include ActiveModel::Model
+ include ActiveModel::AttributeMethods
+ include ActiveModel::Serializers::JSON
+
+ attr_accessor :name, :age
+
+ def attributes
+ instance_values
+ end
+ end
+
+ user.as_json
+ => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true}
+ # root is not included
+
+ person.as_json
+ => {"name"=>"Francesco", "age"=>22}
+ # root is not included
+
+ *Francesco Rodriguez*
+
+* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell*
+
* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella*
* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran*
@@ -8,6 +37,26 @@
* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim*
+* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide.
+
+## Rails 3.2.6 (Jun 12, 2012) ##
+
+* No changes.
+
+## Rails 3.2.5 (Jun 1, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.4 (May 31, 2012) ##
+
+* No changes.
+
+
+## Rails 3.2.3 (March 30, 2012) ##
+
+* No changes.
+
## Rails 3.2.2 (March 1, 2012) ##
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index f2d004fb0a..66f324a1a1 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -8,6 +8,7 @@ Gem::Specification.new do |s|
s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3'
+ s.license = 'MIT'
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb
index ded1b752df..f6bacf5ec0 100644
--- a/activemodel/lib/active_model.rb
+++ b/activemodel/lib/active_model.rb
@@ -30,7 +30,6 @@ module ActiveModel
autoload :AttributeMethods
autoload :BlockValidator, 'active_model/validator'
autoload :Callbacks
- autoload :Configuration
autoload :Conversion
autoload :Dirty
autoload :EachValidator, 'active_model/validator'
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb
index 99918fdb96..6e93fdd625 100644
--- a/activemodel/lib/active_model/attribute_methods.rb
+++ b/activemodel/lib/active_model/attribute_methods.rb
@@ -61,8 +61,7 @@ module ActiveModel
CALL_COMPILABLE_REGEXP = /\A[a-zA-Z_]\w*[!?]?\z/
included do
- extend ActiveModel::Configuration
- config_attribute :attribute_method_matchers
+ class_attribute :attribute_method_matchers, instance_writer: false
self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new]
end
@@ -196,7 +195,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
- define_optimized_call self, matcher_new, matcher_old
+ define_proxy_call false, self, matcher_new, matcher_old
end
end
@@ -238,7 +237,7 @@ module ActiveModel
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
+ define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
end
end
end
@@ -293,7 +292,7 @@ module ActiveModel
# 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:
+ def define_proxy_call(include_private, mod, name, send, *extra) #:nodoc:
if name =~ NAME_COMPILABLE_REGEXP
defn = "def #{name}(*args)"
else
@@ -303,7 +302,7 @@ module ActiveModel
extra = (extra.map(&:inspect) << "*args").join(", ")
if send =~ CALL_COMPILABLE_REGEXP
- target = "#{send}(#{extra})"
+ target = "#{"self." unless include_private}#{send}(#{extra})"
else
target = "send(:'#{send}', #{extra})"
end
@@ -315,7 +314,7 @@ module ActiveModel
RUBY
end
- class AttributeMethodMatcher
+ class AttributeMethodMatcher #:nodoc:
attr_reader :prefix, :suffix, :method_missing_target
AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name)
diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb
deleted file mode 100644
index ba5a6a2075..0000000000
--- a/activemodel/lib/active_model/configuration.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'active_support/concern'
-require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/attribute_accessors'
-
-module ActiveModel
- # This API is for Rails' internal use and is not currently considered 'public', so
- # it may change in the future without warning.
- #
- # It creates configuration attributes that can be inherited from a module down
- # to a class that includes the module. E.g.
- #
- # module MyModel
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- #
- # class Post
- # include MyModel
- # end
- #
- # Post.awesome # => true
- #
- # Post.awesome = false
- # Post.awesome # => false
- # MyModel.awesome # => true
- #
- # We assume that the module will have a ClassMethods submodule containing methods
- # to be transferred to the including class' singleton class.
- #
- # Config options can also be defined directly on a class:
- #
- # class Post
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # end
- #
- # So this allows us to define a module that doesn't care about whether it is being
- # included in a class or a module:
- #
- # module Awesomeness
- # extend ActiveSupport::Concern
- #
- # included do
- # extend ActiveModel::Configuration
- # config_attribute :awesome
- # self.awesome = true
- # end
- # end
- #
- # class Post
- # include Awesomeness
- # end
- #
- # module AwesomeModel
- # include Awesomeness
- # end
- module Configuration #:nodoc:
- def config_attribute(name, options = {})
- klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute
- klass.new(self, name, options).define
- end
-
- class Attribute
- attr_reader :host, :name, :options
-
- def initialize(host, name, options)
- @host, @name, @options = host, name, options
- end
-
- def instance_writer?
- options.fetch(:instance_writer, false)
- end
- end
-
- class ClassAttribute < Attribute
- def define
- if options[:global]
- host.cattr_accessor name, :instance_writer => instance_writer?
- else
- host.class_attribute name, :instance_writer => instance_writer?
- end
- end
- end
-
- class ModuleAttribute < Attribute
- def class_methods
- @class_methods ||= begin
- if host.const_defined?(:ClassMethods, false)
- host.const_get(:ClassMethods)
- else
- host.const_set(:ClassMethods, Module.new)
- end
- end
- end
-
- def define
- host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 1
- attr_accessor :#{name}
- def #{name}?; !!#{name}; end
- CODE
-
- name, host = self.name, self.host
-
- class_methods.class_eval do
- define_method(name) { host.send(name) }
- define_method("#{name}?") { !!send(name) }
- end
-
- host.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}; defined?(@#{name}) ? @#{name} : self.class.#{name}; end
- def #{name}?; !!#{name}; end
- CODE
-
- if options[:global]
- class_methods.class_eval do
- define_method("#{name}=") { |val| host.send("#{name}=", val) }
- end
- else
- class_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}=(val)
- singleton_class.class_eval do
- remove_possible_method(:#{name})
- define_method(:#{name}) { val }
- end
- end
- CODE
- end
-
- host.send(:attr_writer, name) if instance_writer?
- end
- end
- end
-end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 3f2fd12db7..7014d8114f 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -11,14 +11,14 @@ module ActiveModel
#
# The requirements for implementing ActiveModel::Dirty are:
#
- # * <tt>include ActiveModel::Dirty</tt> in your object
+ # * <tt>include ActiveModel::Dirty</tt> in your object.
# * Call <tt>define_attribute_methods</tt> passing each method you want to
- # track
+ # track.
# * Call <tt>attr_name_will_change!</tt> before each change to the tracked
- # attribute
+ # attribute.
#
# If you wish to also track previous changes on save or update, you need to
- # add
+ # add:
#
# @previously_changed = changes
#
@@ -27,7 +27,6 @@ module ActiveModel
# A minimal implementation could be:
#
# class Person
- #
# include ActiveModel::Dirty
#
# define_attribute_methods :name
@@ -45,47 +44,49 @@ module ActiveModel
# @previously_changed = changes
# @changed_attributes.clear
# end
- #
# end
#
- # == Examples:
- #
# A newly instantiated object is unchanged:
+ #
# person = Person.find_by_name('Uncle Bob')
# person.changed? # => false
#
# Change the name:
+ #
# person.name = 'Bob'
# person.changed? # => true
# person.name_changed? # => true
- # person.name_was # => 'Uncle Bob'
- # person.name_change # => ['Uncle Bob', 'Bob']
+ # person.name_was # => "Uncle Bob"
+ # person.name_change # => ["Uncle Bob", "Bob"]
# person.name = 'Bill'
- # person.name_change # => ['Uncle Bob', 'Bill']
+ # person.name_change # => ["Uncle Bob", "Bill"]
#
# Save the changes:
+ #
# person.save
# person.changed? # => false
# person.name_changed? # => false
#
# Assigning the same value leaves the attribute unchanged:
+ #
# person.name = 'Bill'
# person.name_changed? # => false
# person.name_change # => nil
#
# Which attributes have changed?
+ #
# person.name = 'Bob'
- # person.changed # => ['name']
- # person.changes # => { 'name' => ['Bill', 'Bob'] }
+ # person.changed # => ["name"]
+ # person.changes # => {"name" => ["Bill", "Bob"]}
#
# If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt>
# to mark that the attribute is changing. Otherwise ActiveModel can't track changes to
# in-place attributes.
#
# person.name_will_change!
- # person.name_change # => ['Bill', 'Bill']
+ # person.name_change # => ["Bill", "Bill"]
# person.name << 'y'
- # person.name_change # => ['Bill', 'Billy']
+ # person.name_change # => ["Bill", "Billy"]
module Dirty
extend ActiveSupport::Concern
include ActiveModel::AttributeMethods
@@ -95,7 +96,8 @@ module ActiveModel
attribute_method_affix :prefix => 'reset_', :suffix => '!'
end
- # Returns true if any attribute have unsaved changes, false otherwise.
+ # Returns +true+ if any attribute have unsaved changes, +false+ otherwise.
+ #
# person.changed? # => false
# person.name = 'bob'
# person.changed? # => true
@@ -103,32 +105,41 @@ module ActiveModel
changed_attributes.present?
end
- # List of attributes with unsaved changes.
+ # Returns an array with the name of the attributes with unsaved changes.
+ #
# person.changed # => []
# person.name = 'bob'
- # person.changed # => ['name']
+ # person.changed # => ["name"]
def changed
changed_attributes.keys
end
- # Map of changed attrs => [original value, new value].
+ # Returns a hash of changed attributes indicating their original
+ # and new values like <tt>attr => [original value, new value]</tt>.
+ #
# person.changes # => {}
# person.name = 'bob'
- # person.changes # => { 'name' => ['bill', 'bob'] }
+ # person.changes # => { "name" => ["bill", "bob"] }
def changes
HashWithIndifferentAccess[changed.map { |attr| [attr, attribute_change(attr)] }]
end
- # Map of attributes that were changed when the model was saved.
- # person.name # => 'bob'
+ # Returns a hash of attributes that were changed before the model was saved.
+ #
+ # person.name # => "bob"
# person.name = 'robert'
# person.save
- # person.previous_changes # => {'name' => ['bob, 'robert']}
+ # person.previous_changes # => {"name" => ["bob", "robert"]}
def previous_changes
@previously_changed
end
- # Map of change <tt>attr => original value</tt>.
+ # Returns a hash of the attributes with unsaved changes indicating their original
+ # values like <tt>attr => original value</tt>.
+ #
+ # person.name # => "bob"
+ # person.name = 'robert'
+ # person.changed_attributes # => {"name" => "bob"}
def changed_attributes
@changed_attributes ||= {}
end
diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb
index 893fbf92c3..8f2c0bf00a 100644
--- a/activemodel/lib/active_model/mass_assignment_security.rb
+++ b/activemodel/lib/active_model/mass_assignment_security.rb
@@ -9,13 +9,11 @@ module ActiveModel
extend ActiveSupport::Concern
included do
- extend ActiveModel::Configuration
+ class_attribute :_accessible_attributes, instance_writer: false
+ class_attribute :_protected_attributes, instance_writer: false
+ class_attribute :_active_authorizer, instance_writer: false
- config_attribute :_accessible_attributes
- config_attribute :_protected_attributes
- config_attribute :_active_authorizer
-
- config_attribute :_mass_assignment_sanitizer
+ class_attribute :_mass_assignment_sanitizer, instance_writer: false
self.mass_assignment_sanitizer = :logger
end
@@ -62,7 +60,7 @@ module ActiveModel
# Attributes named in this macro are protected from mass-assignment
# whenever attributes are sanitized before assignment. A role for the
# attributes is optional, if no role is provided then :default is used.
- # A role can be defined by using the :as option.
+ # A role can be defined by using the :as option with a symbol or an array of symbols as the value.
#
# Mass-assignment to these attributes will simply be ignored, to assign
# to them you can use direct writer methods. This is meant to protect
@@ -128,7 +126,7 @@ module ActiveModel
#
# Like +attr_protected+, a role for the attributes is optional,
# if no role is provided then :default is used. A role can be defined by
- # using the :as option.
+ # using the :as option with a symbol or an array of symbols as the value.
#
# This is the opposite of the +attr_protected+ macro: Mass-assignment
# will only set attributes in this list, to assign to the rest of
diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
index 9661349503..415ab0ad17 100644
--- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
+++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb
@@ -5,7 +5,7 @@ module ActiveModel
class PermissionSet < Set
def +(values)
- super(values.map(&:to_s))
+ super(values.compact.map(&:to_s))
end
def include?(key)
diff --git a/activemodel/lib/active_model/observer_array.rb b/activemodel/lib/active_model/observer_array.rb
index 3d463885be..8de6918d18 100644
--- a/activemodel/lib/active_model/observer_array.rb
+++ b/activemodel/lib/active_model/observer_array.rb
@@ -17,6 +17,11 @@ module ActiveModel
# Disables one or more observers. This supports multiple forms:
#
+ # ORM.observers.disable :all
+ # # => disables all observers for all models subclassed from
+ # # an ORM base class that includes ActiveModel::Observing
+ # # e.g. ActiveRecord::Base
+ #
# ORM.observers.disable :user_observer
# # => disables the UserObserver
#
@@ -27,9 +32,6 @@ module ActiveModel
# ORM.observers.disable :observer_1, :observer_2
# # => disables Observer1 and Observer2 for all models.
#
- # ORM.observers.disable :all
- # # => disables all observers for all models.
- #
# User.observers.disable :all do
# # all user observers are disabled for
# # just the duration of the block
@@ -40,6 +42,11 @@ module ActiveModel
# Enables one or more observers. This supports multiple forms:
#
+ # ORM.observers.enable :all
+ # # => enables all observers for all models subclassed from
+ # # an ORM base class that includes ActiveModel::Observing
+ # # e.g. ActiveRecord::Base
+ #
# ORM.observers.enable :user_observer
# # => enables the UserObserver
#
@@ -51,9 +58,6 @@ module ActiveModel
# ORM.observers.enable :observer_1, :observer_2
# # => enables Observer1 and Observer2 for all models.
#
- # ORM.observers.enable :all
- # # => enables all observers for all models.
- #
# User.observers.enable :all do
# # all user observers are enabled for
# # just the duration of the block
diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb
index 63ab8e7edc..b4baf3a946 100644
--- a/activemodel/lib/active_model/serializers/json.rb
+++ b/activemodel/lib/active_model/serializers/json.rb
@@ -10,83 +10,82 @@ module ActiveModel
included do
extend ActiveModel::Naming
- extend ActiveModel::Configuration
- config_attribute :include_root_in_json
- self.include_root_in_json = true
+ class_attribute :include_root_in_json
+ self.include_root_in_json = false
end
# Returns a hash representing the model. Some configuration can be
# passed through +options+.
#
# The option <tt>include_root_in_json</tt> controls the top-level behavior
- # of +as_json+. If true (the default) +as_json+ will emit a single root
- # node named after the object's type. For example:
+ # of +as_json+. If true +as_json+ will emit a single root node named after
+ # the object's type. The default value for <tt>include_root_in_json</tt>
+ # option is +false+.
#
# user = User.find(1)
# user.as_json
- # # => { "user": {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true} }
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
+ #
+ # ActiveRecord::Base.include_root_in_json = true
#
- # ActiveRecord::Base.include_root_in_json = false
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
- # This behavior can also be achieved by setting the <tt>:root</tt> option to +false+ as in:
+ # This behavior can also be achieved by setting the <tt>:root</tt> option
+ # to +true+ as in:
#
# user = User.find(1)
- # user.as_json(root: false)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
- #
- # The remainder of the examples in this section assume include_root_in_json is set to
- # <tt>false</tt>.
+ # user.as_json(root: true)
+ # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true } }
#
# Without any +options+, the returned Hash will include all the model's
- # attributes. For example:
+ # attributes.
#
# user = User.find(1)
# user.as_json
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true}
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true}
#
- # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
- # included, and work similar to the +attributes+ method. For example:
+ # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
+ # the attributes included, and work similar to the +attributes+ method.
#
- # user.as_json(:only => [ :id, :name ])
- # # => {"id": 1, "name": "Konata Izumi"}
+ # user.as_json(only: [:id, :name])
+ # # => { "id" => 1, "name" => "Konata Izumi" }
#
- # user.as_json(:except => [ :id, :created_at, :age ])
- # # => {"name": "Konata Izumi", "awesome": true}
+ # user.as_json(except: [:id, :created_at, :age])
+ # # => { "name" => "Konata Izumi", "awesome" => true }
#
# To include the result of some method calls on the model use <tt>:methods</tt>:
#
- # user.as_json(:methods => :permalink)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "permalink": "1-konata-izumi"}
+ # user.as_json(methods: :permalink)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "permalink" => "1-konata-izumi" }
#
# To include associations use <tt>:include</tt>:
#
- # user.as_json(:include => :posts)
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
- # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
+ # user.as_json(include: :posts)
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
+ # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
#
# Second level and higher order associations work as well:
#
- # user.as_json(:include => { :posts => {
- # :include => { :comments => {
- # :only => :body } },
- # :only => :title } })
- # # => {"id": 1, "name": "Konata Izumi", "age": 16,
- # "created_at": "2006/08/01", "awesome": true,
- # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
- # "title": "Welcome to the weblog"},
- # {"comments": [{"body": "Don't think too hard"}],
- # "title": "So I was thinking"}]}
+ # user.as_json(include: { posts: {
+ # include: { comments: {
+ # only: :body } },
+ # only: :title } })
+ # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
+ # # "created_at" => "2006/08/01", "awesome" => true,
+ # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
+ # # "title" => "Welcome to the weblog" },
+ # # { "comments" => [ { "body" => "Don't think too hard" } ],
+ # # "title" => "So I was thinking" } ] }
def as_json(options = nil)
root = include_root_in_json
root = options[:root] if options.try(:key?, :root)
diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb
index b78f1ff3f3..2b3e9ce134 100644
--- a/activemodel/lib/active_model/serializers/xml.rb
+++ b/activemodel/lib/active_model/serializers/xml.rb
@@ -172,7 +172,7 @@ module ActiveModel
# <id type="integer">1</id>
# <name>David</name>
# <age type="integer">16</age>
- # <created-at type="datetime">2011-01-30T22:29:23Z</created-at>
+ # <created-at type="dateTime">2011-01-30T22:29:23Z</created-at>
# </user>
#
# The <tt>:only</tt> and <tt>:except</tt> options can be used to limit the attributes
diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb
index 6f0ca92e2a..7a86701f73 100644
--- a/activemodel/lib/active_model/translation.rb
+++ b/activemodel/lib/active_model/translation.rb
@@ -42,9 +42,9 @@ module ActiveModel
# Specify +options+ with additional translating options.
def human_attribute_name(attribute, options = {})
options = { :count => 1 }.merge!(options)
- parts = attribute.to_s.split(".", 2)
+ parts = attribute.to_s.split(".")
attribute = parts.pop
- namespace = parts.pop
+ namespace = parts.join("/") unless parts.empty?
attributes_scope = "#{self.i18n_scope}.attributes"
if namespace
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb
index 611e9ffd55..06eebf79d9 100644
--- a/activemodel/lib/active_model/validations.rb
+++ b/activemodel/lib/active_model/validations.rb
@@ -52,8 +52,7 @@ module ActiveModel
attr_accessor :validation_context
define_callbacks :validate, :scope => :name
- extend ActiveModel::Configuration
- config_attribute :_validators
+ class_attribute :_validators
self._validators = Hash.new { |h,k| h[k] = [] }
end
diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb
index 4f09679541..edd42d85f2 100644
--- a/activemodel/lib/active_model/validations/exclusion.rb
+++ b/activemodel/lib/active_model/validations/exclusion.rb
@@ -30,8 +30,7 @@ module ActiveModel
# * <tt>:in</tt> - An enumerable object of items that the value shouldn't be
# part of. This can be supplied as a proc or lambda which returns an
# enumerable. If the enumerable is a range the test is performed with
- # <tt>Range#cover?</tt> (backported in Active Support for 1.8), otherwise
- # with <tt>include?</tt>.
+ # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is
# reserved").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute
diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb
index dd87e312f9..ffdf842d94 100644
--- a/activemodel/lib/active_model/validations/format.rb
+++ b/activemodel/lib/active_model/validations/format.rb
@@ -32,11 +32,21 @@ module ActiveModel
def record_error(record, attribute, name, value)
record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value))
end
-
+
+ def regexp_using_multiline_anchors?(regexp)
+ regexp.source.start_with?("^") ||
+ (regexp.source.end_with?("$") && !regexp.source.end_with?("\\$"))
+ end
+
def check_options_validity(options, name)
option = options[name]
if option && !option.is_a?(Regexp) && !option.respond_to?(:call)
raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}"
+ elsif option && option.is_a?(Regexp) &&
+ regexp_using_multiline_anchors?(option) && options[:multiline] != true
+ raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \
+ "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \
+ ":multiline => true option?"
end
end
end
@@ -47,7 +57,7 @@ module ActiveModel
# attribute matches the regular expression:
#
# class Person < ActiveRecord::Base
- # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
+ # validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, :on => :create
# end
#
# Alternatively, you can require that the specified attribute does _not_
@@ -63,12 +73,16 @@ module ActiveModel
# class Person < ActiveRecord::Base
# # Admin can have number as a first letter in their screen name
# validates_format_of :screen_name,
- # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\Z/i : /\A[a-z][a-z0-9_\-]*\Z/i }
+ # :with => lambda{ |person| person.admin? ? /\A[a-z0-9][a-z0-9_\-]*\z/i : /\A[a-z][a-z0-9_\-]*\z/i }
# end
#
# Note: use <tt>\A</tt> and <tt>\Z</tt> to match the start and end of the
# string, <tt>^</tt> and <tt>$</tt> match the start/end of a line.
#
+ # Due to frequent misuse of <tt>^</tt> and <tt>$</tt>, you need to pass the
+ # :multiline => true option in case you use any of these two anchors in the provided
+ # regular expression. In most cases, you should be using <tt>\A</tt> and <tt>\z</tt>.
+ #
# You must pass either <tt>:with</tt> or <tt>:without</tt> as an option.
# In addition, both must be a regular expression or a proc or lambda, or
# else an exception will be raised.
@@ -98,6 +112,9 @@ module ActiveModel
# method, proc or string should return or evaluate to a true or false value.
# * <tt>:strict</tt> - Specifies whether validation should be strict.
# See <tt>ActiveModel::Validation#validates!</tt> for more information.
+ # * <tt>:multiline</tt> - Set to true if your regular expression contains
+ # anchors that match the beginning or end of lines as opposed to the
+ # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>.
def validates_format_of(*attr_names)
validates_with FormatValidator, _merge_attributes(attr_names)
end
diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb
index ffdbed0fc1..8810f2a3c1 100644
--- a/activemodel/lib/active_model/validations/inclusion.rb
+++ b/activemodel/lib/active_model/validations/inclusion.rb
@@ -28,8 +28,8 @@ module ActiveModel
# Configuration options:
# * <tt>:in</tt> - An enumerable object of available items. This can be
# supplied as a proc or lambda which returns an enumerable. If the enumerable
- # is a range the test is performed with <tt>Range#cover?</tt>
- # (backported in Active Support for 1.8), otherwise with <tt>include?</tt>.
+ # is a range the test is performed with <tt>Range#cover?</tt>, otherwise with
+ # <tt>include?</tt>.
# * <tt>:message</tt> - Specifies a custom error message (default is: "is not
# included in the list").
# * <tt>:allow_nil</tt> - If set to true, skips this validation if the attribute
diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb
index d94c4e3f4f..6c13d2b4a2 100644
--- a/activemodel/lib/active_model/validations/validates.rb
+++ b/activemodel/lib/active_model/validations/validates.rb
@@ -88,6 +88,7 @@ module ActiveModel
defaults.merge!(:attributes => attributes)
validations.each do |key, options|
+ next unless options
key = "#{key.to_s.camelize}Validator"
begin
diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb
index a9db29ee21..e2f2cecc09 100644
--- a/activemodel/test/cases/attribute_methods_test.rb
+++ b/activemodel/test/cases/attribute_methods_test.rb
@@ -76,6 +76,19 @@ private
end
end
+class ModelWithRubyKeywordNamedAttributes
+ include ActiveModel::AttributeMethods
+
+ def attributes
+ { :begin => 'value of begin', :end => 'value of end' }
+ end
+
+private
+ def attribute(name)
+ attributes[name.to_sym]
+ end
+end
+
class ModelWithoutAttributesMethod
include ActiveModel::AttributeMethods
end
@@ -155,6 +168,15 @@ class AttributeMethodsTest < ActiveModel::TestCase
assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar
end
+ test '#alias_attribute works with attributes named as a ruby keyword' do
+ ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end])
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin)
+ ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end)
+
+ assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from
+ assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to
+ end
+
test '#undefine_attribute_methods removes attribute methods' do
ModelWithAttributes.define_attribute_methods(:foo)
ModelWithAttributes.undefine_attribute_methods
diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb
deleted file mode 100644
index a172fa26a3..0000000000
--- a/activemodel/test/cases/configuration_test.rb
+++ /dev/null
@@ -1,154 +0,0 @@
-require 'cases/helper'
-
-class ConfigurationOnModuleTest < ActiveModel::TestCase
- def setup
- @mod = mod = Module.new do
- extend ActiveSupport::Concern
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :boolean
-
- config_attribute :lol, instance_writer: true
- end
-
- @klass = Class.new do
- include mod
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "default" do
- assert_equal "default", @mod.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.new.omg
- end
-
- test "setting" do
- @mod.omg = "lol"
- assert_equal "lol", @mod.omg
- end
-
- test "setting on class including the module" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @klass.new.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on subclass of class including the module" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- assert_equal "default", @mod.omg
- end
-
- test "setting on instance" do
- assert !@klass.new.respond_to?(:omg=)
-
- @klass.lol = "lol"
- obj = @klass.new
- assert_equal "lol", obj.lol
- obj.lol = "omg"
- assert_equal "omg", obj.lol
- assert_equal "lol", @klass.lol
- assert_equal "lol", @klass.new.lol
- obj.lol = false
- assert !obj.lol?
- end
-
- test "global attribute" do
- assert_equal "default", @mod.wtf
- assert_equal "default", @klass.wtf
-
- @mod.wtf = "wtf"
-
- assert_equal "wtf", @mod.wtf
- assert_equal "wtf", @klass.wtf
-
- @klass.wtf = "lol"
-
- assert_equal "lol", @mod.wtf
- assert_equal "lol", @klass.wtf
- end
-
- test "boolean" do
- assert_equal false, @mod.boolean?
- assert_equal false, @klass.new.boolean?
- @mod.boolean = true
- assert_equal true, @mod.boolean?
- assert_equal true, @klass.new.boolean?
- end
-end
-
-class ConfigurationOnClassTest < ActiveModel::TestCase
- def setup
- @klass = Class.new do
- extend ActiveModel::Configuration
-
- config_attribute :omg
- self.omg = "default"
-
- config_attribute :wtf, global: true
- self.wtf = "default"
-
- config_attribute :omg2, instance_writer: true
- config_attribute :wtf2, instance_writer: true, global: true
- end
-
- @subklass = Class.new(@klass)
- end
-
- test "defaults" do
- assert_equal "default", @klass.omg
- assert_equal "default", @klass.wtf
- assert_equal "default", @subklass.omg
- assert_equal "default", @subklass.wtf
- end
-
- test "changing" do
- @klass.omg = "lol"
- assert_equal "lol", @klass.omg
- assert_equal "lol", @subklass.omg
- end
-
- test "changing in subclass" do
- @subklass.omg = "lol"
- assert_equal "lol", @subklass.omg
- assert_equal "default", @klass.omg
- end
-
- test "changing global" do
- @klass.wtf = "wtf"
- assert_equal "wtf", @klass.wtf
- assert_equal "wtf", @subklass.wtf
-
- @subklass.wtf = "lol"
- assert_equal "lol", @klass.wtf
- assert_equal "lol", @subklass.wtf
- end
-
- test "instance_writer" do
- obj = @klass.new
-
- @klass.omg2 = "omg"
- @klass.wtf2 = "wtf"
-
- assert_equal "omg", obj.omg2
- assert_equal "wtf", obj.wtf2
-
- obj.omg2 = "lol"
- obj.wtf2 = "lol"
-
- assert_equal "lol", obj.omg2
- assert_equal "lol", obj.wtf2
- assert_equal "omg", @klass.omg2
- assert_equal "lol", @klass.wtf2
- end
-end
diff --git a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
index d005b638e4..8082c49852 100644
--- a/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
+++ b/activemodel/test/cases/mass_assignment_security/permission_set_test.rb
@@ -13,6 +13,12 @@ class PermissionSetTest < ActiveModel::TestCase
assert new_list.include?('admin'), "did not add collection to #{@permission_list.inspect}}"
end
+ test "+ compacts added collection values" do
+ added_collection = [ nil ]
+ new_list = @permission_list + added_collection
+ assert_equal new_list, @permission_list, "did not add collection to #{@permission_list.inspect}}"
+ end
+
test "include? normalizes multi-parameter keys" do
multi_param_key = 'admin(1)'
new_list = @permission_list += [ 'admin' ]
diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb
index 7160635eb4..e2690f1827 100644
--- a/activemodel/test/cases/serializers/json_serialization_test.rb
+++ b/activemodel/test/cases/serializers/json_serialization_test.rb
@@ -31,10 +31,15 @@ class JsonSerializationTest < ActiveModel::TestCase
@contact.preferences = { 'shows' => 'anime' }
end
- test "should include root in json" do
+ def teardown
+ # set to the default value
+ Contact.include_root_in_json = false
+ end
+
+ test "should not include root in json (class method)" do
json = @contact.to_json
- assert_match %r{^\{"contact":\{}, json
+ assert_no_match %r{^\{"contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
assert_match %r{"age":16}, json
assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
@@ -42,41 +47,31 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_match %r{"preferences":\{"shows":"anime"\}}, json
end
- test "should not include root in json (class method)" do
- begin
- Contact.include_root_in_json = false
- json = @contact.to_json
-
- assert_no_match %r{^\{"contact":\{}, json
- assert_match %r{"name":"Konata Izumi"}, json
- assert_match %r{"age":16}, json
- assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
- assert_match %r{"awesome":true}, json
- assert_match %r{"preferences":\{"shows":"anime"\}}, json
- ensure
- Contact.include_root_in_json = true
- end
+ test "should include root in json if include_root_in_json is true" do
+ Contact.include_root_in_json = true
+ json = @contact.to_json
+
+ assert_match %r{^\{"contact":\{}, json
+ assert_match %r{"name":"Konata Izumi"}, json
+ assert_match %r{"age":16}, json
+ assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))}))
+ assert_match %r{"awesome":true}, json
+ assert_match %r{"preferences":\{"shows":"anime"\}}, json
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
+ json = @contact.to_json(root: true)
+ assert_match %r{^\{"contact":\{}, json
end
test "should not include root in json (option)" do
-
- json = @contact.to_json(:root => false)
+ json = @contact.to_json(root: false)
assert_no_match %r{^\{"contact":\{}, json
end
test "should include custom root in json" do
- json = @contact.to_json(:root => 'json_contact')
+ json = @contact.to_json(root: 'json_contact')
assert_match %r{^\{"json_contact":\{}, json
assert_match %r{"name":"Konata Izumi"}, json
@@ -107,7 +102,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "should allow attribute filtering with except" do
- json = @contact.to_json(:except => [:name, :age])
+ json = @contact.to_json(except: [:name, :age])
assert_no_match %r{"name":"Konata Izumi"}, json
assert_no_match %r{"age":16}, json
@@ -122,10 +117,10 @@ class JsonSerializationTest < ActiveModel::TestCase
def @contact.favorite_quote; "Constraints are liberating"; end
# Single method.
- assert_match %r{"label":"Has cheezburger"}, @contact.to_json(:only => :name, :methods => :label)
+ assert_match %r{"label":"Has cheezburger"}, @contact.to_json(only: :name, methods: :label)
# Both methods.
- methods_json = @contact.to_json(:only => :name, :methods => [:label, :favorite_quote])
+ methods_json = @contact.to_json(only: :name, methods: [:label, :favorite_quote])
assert_match %r{"label":"Has cheezburger"}, methods_json
assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json
end
@@ -143,14 +138,15 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "serializable_hash should not modify options passed in argument" do
- options = { :except => :name }
+ options = { except: :name }
@contact.serializable_hash(options)
assert_nil options[:only]
assert_equal :name, options[:except]
end
- test "as_json should return a hash" do
+ test "as_json should return a hash if include_root_in_json is true" do
+ Contact.include_root_in_json = true
json = @contact.as_json
assert_kind_of Hash, json
@@ -160,7 +156,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
end
- test "from_json should set the object's attributes" do
+ test "from_json should work without a root (class attribute)" do
json = @contact.to_json
result = Contact.new.from_json(json)
@@ -172,7 +168,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "from_json should work without a root (method parameter)" do
- json = @contact.to_json(:root => false)
+ json = @contact.to_json
result = Contact.new.from_json(json, false)
assert_equal result.name, @contact.name
@@ -182,24 +178,19 @@ class JsonSerializationTest < ActiveModel::TestCase
assert_equal result.preferences, @contact.preferences
end
- test "from_json should work without a root (class attribute)" do
- begin
- Contact.include_root_in_json = false
- json = @contact.to_json
- result = Contact.new.from_json(json)
-
- assert_equal result.name, @contact.name
- assert_equal result.age, @contact.age
- assert_equal Time.parse(result.created_at), @contact.created_at
- assert_equal result.awesome, @contact.awesome
- assert_equal result.preferences, @contact.preferences
- ensure
- Contact.include_root_in_json = true
- end
+ test "from_json should work with a root (method parameter)" do
+ json = @contact.to_json(root: :true)
+ result = Contact.new.from_json(json, true)
+
+ assert_equal result.name, @contact.name
+ assert_equal result.age, @contact.age
+ assert_equal Time.parse(result.created_at), @contact.created_at
+ assert_equal result.awesome, @contact.awesome
+ assert_equal result.preferences, @contact.preferences
end
test "custom as_json should be honored when generating json" do
- def @contact.as_json(options); { :name => name, :created_at => created_at }; end
+ def @contact.as_json(options); { name: name, created_at: created_at }; end
json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json
@@ -209,7 +200,7 @@ class JsonSerializationTest < ActiveModel::TestCase
end
test "custom as_json options should be extendible" do
- def @contact.as_json(options = {}); super(options.merge(:only => [:name])); end
+ def @contact.as_json(options = {}); super(options.merge(only: [:name])); end
json = @contact.to_json
assert_match %r{"name":"Konata Izumi"}, json
@@ -217,5 +208,4 @@ class JsonSerializationTest < ActiveModel::TestCase
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 5fa227e0e0..7eb48abc3c 100644
--- a/activemodel/test/cases/serializers/xml_serialization_test.rb
+++ b/activemodel/test/cases/serializers/xml_serialization_test.rb
@@ -140,7 +140,7 @@ class XmlSerializationTest < ActiveModel::TestCase
end
test "should serialize datetime" do
- assert_match %r{<created-at type=\"datetime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
+ assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml
end
test "should serialize boolean" do
diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb
index 4999583802..fd833cdd06 100644
--- a/activemodel/test/cases/translation_test.rb
+++ b/activemodel/test/cases/translation_test.rb
@@ -56,6 +56,11 @@ class ActiveModelI18nTests < ActiveModel::TestCase
assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute')
end
+ def test_translated_deeply_nested_model_attributes
+ I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/contacts/addresses" => {:street => 'Deeply Nested Address Street'}}}
+ assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street')
+ end
+
def test_translated_nested_model_attributes
I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}}
assert_equal 'Person Address Street', Person.human_attribute_name('addresses.street')
diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb
index 41a1131bcb..308a3c6cef 100644
--- a/activemodel/test/cases/validations/format_validation_test.rb
+++ b/activemodel/test/cases/validations/format_validation_test.rb
@@ -11,7 +11,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format
- Topic.validates_format_of(:title, :content, :with => /^Validation\smacros \w+!$/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, :with => /\AValidation\smacros \w+!\z/, :message => "is bad data")
t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!")
assert t.invalid?, "Shouldn't be valid"
@@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_allow_blank
- Topic.validates_format_of(:title, :with => /^Validation\smacros \w+!$/, :allow_blank => true)
+ Topic.validates_format_of(:title, :with => /\AValidation\smacros \w+!\z/, :allow_blank => true)
assert Topic.new("title" => "Shouldn't be valid").invalid?
assert Topic.new("title" => "").valid?
assert Topic.new("title" => nil).valid?
@@ -36,7 +36,7 @@ class PresenceValidationTest < ActiveModel::TestCase
# testing ticket #3142
def test_validate_format_numeric
- Topic.validates_format_of(:title, :content, :with => /^[1-9][0-9]*$/, :message => "is bad data")
+ Topic.validates_format_of(:title, :content, :with => /\A[1-9][0-9]*\z/, :message => "is bad data")
t = Topic.new("title" => "72x", "content" => "6789")
assert t.invalid?, "Shouldn't be valid"
@@ -63,11 +63,21 @@ class PresenceValidationTest < ActiveModel::TestCase
end
def test_validate_format_with_formatted_message
- Topic.validates_format_of(:title, :with => /^Valid Title$/, :message => "can't be %{value}")
+ Topic.validates_format_of(:title, :with => /\AValid Title\z/, :message => "can't be %{value}")
t = Topic.new(:title => 'Invalid title')
assert t.invalid?
assert_equal ["can't be Invalid title"], t.errors[:title]
end
+
+ def test_validate_format_of_with_multiline_regexp_should_raise_error
+ assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /^Valid Title$/) }
+ end
+
+ def test_validate_format_of_with_multiline_regexp_and_option
+ assert_nothing_raised(ArgumentError) do
+ Topic.validates_format_of(:title, :with => /^Valid Title$/, :multiline => true)
+ end
+ end
def test_validate_format_with_not_option
Topic.validates_format_of(:title, :without => /foo/, :message => "should not contain foo")
diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb
index 6b6aad3bd1..bb751cf9c5 100644
--- a/activemodel/test/cases/validations/i18n_validation_test.rb
+++ b/activemodel/test/cases/validations/i18n_validation_test.rb
@@ -141,7 +141,7 @@ class I18nValidationTest < ActiveModel::TestCase
COMMON_CASES.each do |name, validation_options, generate_message_options|
test "validates_format_of on generated message #{name}" do
- Person.validates_format_of :title, validation_options.merge(:with => /^[1-9][0-9]*$/)
+ Person.validates_format_of :title, validation_options.merge(:with => /\A[1-9][0-9]*\z/)
@person.title = '72x'
@person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x'))
@person.valid?
@@ -291,7 +291,7 @@ class I18nValidationTest < ActiveModel::TestCase
# validates_format_of w/o mocha
set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge|
- Person.validates_format_of :title, options_to_merge.merge(:with => /^[1-9][0-9]*$/)
+ Person.validates_format_of :title, options_to_merge.merge(:with => /\A[1-9][0-9]*\z/)
end
# validates_inclusion_of w/o mocha
diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb
index 1f5023bf76..8ea9745fbf 100644
--- a/activemodel/test/cases/validations_test.rb
+++ b/activemodel/test/cases/validations_test.rb
@@ -330,6 +330,11 @@ class ValidationsTest < ActiveModel::TestCase
end
end
+ def test_validates_with_false_hash_value
+ Topic.validates :title, :presence => false
+ assert Topic.new.valid?
+ end
+
def test_strict_validation_error_message
Topic.validates :title, :strict => true, :presence => true