diff options
Diffstat (limited to 'activemodel/lib')
-rw-r--r-- | activemodel/lib/active_model.rb | 1 | ||||
-rw-r--r-- | activemodel/lib/active_model/attribute_methods.rb | 13 | ||||
-rw-r--r-- | activemodel/lib/active_model/configuration.rb | 134 | ||||
-rw-r--r-- | activemodel/lib/active_model/dirty.rb | 59 | ||||
-rw-r--r-- | activemodel/lib/active_model/mass_assignment_security.rb | 14 | ||||
-rw-r--r-- | activemodel/lib/active_model/mass_assignment_security/permission_set.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/observer_array.rb | 16 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/json.rb | 89 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/translation.rb | 4 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations.rb | 3 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/exclusion.rb | 3 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/format.rb | 23 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/inclusion.rb | 4 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/validates.rb | 1 |
15 files changed, 130 insertions, 238 deletions
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 |