diff options
Diffstat (limited to 'activemodel/lib')
30 files changed, 370 insertions, 259 deletions
diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index d0e2a6f39c..85514e63fd 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2011 David Heinemeier Hansson +# Copyright (c) 2004-2012 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -32,6 +32,7 @@ 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 ef0b95424e..52f270ff33 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -57,72 +57,16 @@ 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 + extend ActiveModel::Configuration + config_attribute :attribute_method_matchers self.attribute_method_matchers = [ClassMethods::AttributeMethodMatcher.new] end module ClassMethods - # Defines an "attribute" method (like +inheritance_column+ or +table_name+). - # A new (class) method will be created with the given name. If a value is - # specified, the new method will return that value (as a string). - # Otherwise, the given block will be used to compute the value of the - # method. - # - # The original method will be aliased, with the new name being prefixed - # with "original_". This allows the new method to access the original - # value. - # - # Example: - # - # class Person - # - # include ActiveModel::AttributeMethods - # - # cattr_accessor :primary_key - # cattr_accessor :inheritance_column - # - # define_attr_method :primary_key, "sysid" - # define_attr_method( :inheritance_column ) do - # original_inheritance_column + "_id" - # end - # - # end - # - # Provides you with: - # - # Person.primary_key - # # => "sysid" - # Person.inheritance_column = 'address' - # Person.inheritance_column - # # => 'address_id' - def define_attr_method(name, value=nil, &block) - sing = singleton_class - sing.class_eval <<-eorb, __FILE__, __LINE__ + 1 - if method_defined?('original_#{name}') - undef :'original_#{name}' - end - alias_method :'original_#{name}', :'#{name}' - eorb - if block_given? - sing.send :define_method, name, &block - else - # If we can compile the method name, do it. Otherwise use define_method. - # This is an important *optimization*, please don't change it. define_method - # has slower dispatch and consumes more memory. - if name =~ COMPILABLE_REGEXP - sing.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{name}; #{value.nil? ? 'nil' : value.to_s.inspect}; end - RUBY - else - value = value.to_s if value - sing.send(:define_method, name) { value } - end - end - end - # Declares a method available for all attributes with the given prefix. # Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method. # @@ -240,18 +184,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 @@ -293,17 +226,7 @@ module ActiveModel if respond_to?(generate_method) send(generate_method, attr_name) else - if method_name =~ COMPILABLE_REGEXP - defn = "def #{method_name}(*args)" - else - defn = "define_method(:'#{method_name}') do |*args|" - end - - generated_attribute_methods.module_eval <<-RUBY, __FILE__, __LINE__ + 1 - #{defn} - send(:#{matcher.method_missing_target}, '#{attr_name}', *args) - end - RUBY + define_optimized_call generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s end end end @@ -342,11 +265,11 @@ 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 @@ -359,6 +282,31 @@ module ActiveModel 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 @@ -377,14 +325,14 @@ module ActiveModel end @prefix, @suffix = options[:prefix] || '', options[:suffix] || '' - @regex = /^(#{Regexp.escape(@prefix)})(.+?)(#{Regexp.escape(@suffix)})$/ + @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ @method_missing_target = "#{@prefix}attribute#{@suffix}" @method_name = "#{prefix}%s#{suffix}" end def match(method_name) if @regex =~ method_name - AttributeMethodMatch.new(method_missing_target, $2, method_name) + AttributeMethodMatch.new(method_missing_target, $1, method_name) else nil end @@ -446,7 +394,7 @@ module ActiveModel protected def attribute_method?(attr_name) - attributes.include?(attr_name) + respond_to_without_attributes?(:attributes) && attributes.include?(attr_name) end private diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 37d0c9a0b9..ebb4b51aa3 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/array/wrap' require 'active_support/callbacks' module ActiveModel @@ -41,7 +40,7 @@ module ActiveModel # You can choose not to have all three callbacks by passing a hash to the # define_model_callbacks method. # - # define_model_callbacks :create, :only => :after, :before + # define_model_callbacks :create, :only => [:after, :before] # # Would only create the after_create and before_create callback methods in your # class. @@ -89,11 +88,12 @@ module ActiveModel options = callbacks.extract_options! options = { :terminator => "result == false", + :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name], :only => [:before, :around, :after] }.merge(options) - types = Array.wrap(options.delete(:only)) + types = Array(options.delete(:only)) callbacks.each do |callback| define_callbacks(callback, options) @@ -125,7 +125,7 @@ module ActiveModel def self.after_#{callback}(*args, &block) options = args.extract_options! options[:prepend] = true - options[:if] = Array.wrap(options[:if]) << "!halted && value != false" + options[:if] = Array(options[:if]) << "value != false" set_callback(:#{callback}, :after, *(args << options), &block) end CALLBACK diff --git a/activemodel/lib/active_model/configuration.rb b/activemodel/lib/active_model/configuration.rb new file mode 100644 index 0000000000..1757c12ebf --- /dev/null +++ b/activemodel/lib/active_model/configuration.rb @@ -0,0 +1,134 @@ +require 'active_support/concern' +require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/class/attribute_accessors' + +module ActiveModel + # This API is for Rails' internal use and is not currently considered 'public', so + # it may change in the future without warning. + # + # It creates configuration attributes that can be inherited from a module down + # to a class that includes the module. E.g. + # + # module MyModel + # extend ActiveModel::Configuration + # config_attribute :awesome + # self.awesome = true + # end + # + # class Post + # include MyModel + # end + # + # Post.awesome # => true + # + # Post.awesome = false + # Post.awesome # => false + # MyModel.awesome # => true + # + # We assume that the module will have a ClassMethods submodule containing methods + # to be transferred to the including class' singleton class. + # + # Config options can also be defined directly on a class: + # + # class Post + # extend ActiveModel::Configuration + # config_attribute :awesome + # end + # + # So this allows us to define a module that doesn't care about whether it is being + # included in a class or a module: + # + # module Awesomeness + # extend ActiveSupport::Concern + # + # included do + # extend ActiveModel::Configuration + # config_attribute :awesome + # self.awesome = true + # end + # end + # + # class Post + # include Awesomeness + # end + # + # module AwesomeModel + # include Awesomeness + # end + module Configuration #:nodoc: + def config_attribute(name, options = {}) + klass = self.is_a?(Class) ? ClassAttribute : ModuleAttribute + klass.new(self, name, options).define + end + + class Attribute + attr_reader :host, :name, :options + + def initialize(host, name, options) + @host, @name, @options = host, name, options + end + + def instance_writer? + options.fetch(:instance_writer, false) + end + end + + class ClassAttribute < Attribute + def define + if options[:global] + host.cattr_accessor name, :instance_writer => instance_writer? + else + host.class_attribute name, :instance_writer => instance_writer? + end + end + end + + class ModuleAttribute < Attribute + def class_methods + @class_methods ||= begin + if host.const_defined?(:ClassMethods, false) + host.const_get(:ClassMethods) + else + host.const_set(:ClassMethods, Module.new) + end + end + end + + def define + host.singleton_class.class_eval <<-CODE, __FILE__, __LINE__ + 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 + 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__ + def #{name}=(val) + singleton_class.class_eval do + remove_possible_method(:#{name}) + define_method(:#{name}) { val } + end + end + CODE + end + + host.send(:attr_writer, name) if instance_writer? + end + end + end +end diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 80a3ba51c3..c7c805f1a2 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -39,11 +39,9 @@ module ActiveModel # Returns an Enumerable of all key attributes if any is set, regardless # if the object is persisted or not. - # - # Note the default implementation uses persisted? just because all objects - # in Ruby 1.8.x responds to <tt>:id</tt>. def to_key - persisted? ? [id] : nil + key = respond_to?(:id) && id + key ? [key] : nil end # Returns a string representing the object's key suitable for use in URLs, diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 166cccf161..026f077ee7 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -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 8337b04c0d..e548aa975d 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -1,16 +1,14 @@ # -*- coding: utf-8 -*- -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/string/inflections' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/hash/reverse_merge' -require 'active_support/ordered_hash' module ActiveModel # == Active Model Errors # - # Provides a modified +OrderedHash+ that you can include in your object + # Provides a modified +Hash+ that you can include in your object # for handling error messages and interacting with Action Pack helpers. # # A minimal implementation could be: @@ -76,7 +74,12 @@ module ActiveModel # end def initialize(base) @base = base - @messages = ActiveSupport::OrderedHash.new + @messages = {} + end + + def initialize_dup(other) + @messages = other.messages.dup + super end # Clear the messages @@ -100,6 +103,11 @@ module ActiveModel messages[key] = value end + # Delete messages for +key+ + def delete(key) + messages.delete(key) + end + # When passed a symbol or a name of a method, returns an array of errors # for the method. # @@ -114,7 +122,7 @@ module ActiveModel # p.errors[:name] = "must be set" # p.errors[:name] # => ['must be set'] def []=(attribute, error) - self[attribute.to_sym] << error + self[attribute] << error end # Iterates through each error key, value pair in the error messages hash. @@ -176,8 +184,9 @@ module ActiveModel end # Returns true if no errors are found, false otherwise. + # If the error message is a string it can be empty. def empty? - all? { |k, v| v && v.empty? } + all? { |k, v| v && v.empty? && !v.is_a?(String) } end alias_method :blank?, :empty? @@ -196,7 +205,7 @@ module ActiveModel to_a.to_xml options.reverse_merge(:root => "errors", :skip_types => true) end - # Returns an ActiveSupport::OrderedHash that can be used as the JSON representation for this object. + # Returns an Hash that can be used as the JSON representation for this object. def as_json(options=nil) to_hash end @@ -209,12 +218,12 @@ module ActiveModel # +attribute+. # If no +message+ is supplied, <tt>:invalid</tt> is assumed. # - # If +message+ is a symbol, it will be translated using the appropriate scope (see +translate_error+). + # If +message+ is a symbol, it will be translated using the appropriate scope (see +generate_message+). # If +message+ is a proc, it will be called, allowing for things like <tt>Time.now</tt> to be used within an error. def add(attribute, message = nil, options = {}) message = normalize_message(attribute, message, options) if options[:strict] - raise ActiveModel::StrictValidationFailed, message + raise ActiveModel::StrictValidationFailed, full_message(attribute, message) end self[attribute] << message diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index bfe7ea1869..49ea894150 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -3,9 +3,13 @@ module ActiveModel # == Active Model Lint Tests # # You can test whether an object is compliant with the Active Model API by - # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will include - # tests that tell you whether your object is fully compliant, or if not, - # which aspects of the API are not implemented. + # including <tt>ActiveModel::Lint::Tests</tt> in your TestCase. It will + # include tests that tell you whether your object is fully compliant, + # or if not, which aspects of the API are not implemented. + # + # Note an object is not required to implement all APIs in order to work + # with Action Pack. This module only intends to provide guidance in case + # you want all features out of the box. # # These tests do not attempt to determine the semantic correctness of the # returned values. For instance, you could implement valid? to always @@ -19,7 +23,8 @@ module ActiveModel # == Responds to <tt>to_key</tt> # # Returns an Enumerable of all (primary) key attributes - # or nil if model.persisted? is false + # or nil if model.persisted? is false. This is used by + # dom_id to generate unique ids for the object. def test_to_key assert model.respond_to?(:to_key), "The model should respond to to_key" def model.persisted?() false end @@ -53,15 +58,6 @@ module ActiveModel assert_kind_of String, model.to_partial_path end - # == Responds to <tt>valid?</tt> - # - # Returns a boolean that specifies whether the object is in a valid or invalid - # state. - def test_valid? - assert model.respond_to?(:valid?), "The model should respond to valid?" - assert_boolean model.valid?, "valid?" - end - # == Responds to <tt>persisted?</tt> # # Returns a boolean that specifies whether the object has been persisted yet. @@ -90,25 +86,15 @@ module ActiveModel # == Errors Testing # - # Returns an object that has :[] and :full_messages defined on it. See below - # for more details. - # - # Returns an Array of Strings that are the errors for the attribute in - # question. If localization is used, the Strings should be localized - # for the current locale. If no error is present, this method should - # return an empty Array. + # Returns an object that implements [](attribute) defined which returns an + # Array of Strings that are the errors for the attribute in question. + # If localization is used, the Strings should be localized for the current + # locale. If no error is present, this method should return an empty Array. def test_errors_aref assert model.respond_to?(:errors), "The model should respond to errors" assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array" end - # Returns an Array of all error messages for the object. Each message - # should contain information about the field, if applicable. - def test_errors_full_messages - assert model.respond_to?(:errors), "The model should respond to errors" - assert model.errors.full_messages.is_a?(Array), "errors#full_messages should return an Array" - end - private def model assert @model.respond_to?(:to_model), "The object should respond_to to_model" diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 44425b4a28..ba49c6beaa 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -23,5 +23,6 @@ en: equal_to: "must be equal to %{count}" less_than: "must be less than %{count}" less_than_or_equal_to: "must be less than or equal to %{count}" + other_than: "must be other than %{count}" odd: "must be odd" even: "must be even" diff --git a/activemodel/lib/active_model/mass_assignment_security.rb b/activemodel/lib/active_model/mass_assignment_security.rb index 3f9feb7631..95de039676 100644 --- a/activemodel/lib/active_model/mass_assignment_security.rb +++ b/activemodel/lib/active_model/mass_assignment_security.rb @@ -1,6 +1,5 @@ require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/string/inflections' -require 'active_support/core_ext/array/wrap' require 'active_model/mass_assignment_security/permission_set' require 'active_model/mass_assignment_security/sanitizer' @@ -10,11 +9,13 @@ module ActiveModel extend ActiveSupport::Concern included do - class_attribute :_accessible_attributes - class_attribute :_protected_attributes - class_attribute :_active_authorizer + extend ActiveModel::Configuration - class_attribute :_mass_assignment_sanitizer + config_attribute :_accessible_attributes + config_attribute :_protected_attributes + config_attribute :_active_authorizer + + config_attribute :_mass_assignment_sanitizer self.mass_assignment_sanitizer = :logger end @@ -56,7 +57,7 @@ module ActiveModel # You can specify your own sanitizer object eg. MySanitizer.new. # See <tt>ActiveModel::MassAssignmentSecurity::LoggerSanitizer</tt> for example implementation. # - # + # module ClassMethods # Attributes named in this macro are protected from mass-assignment # whenever attributes are sanitized before assignment. A role for the @@ -71,10 +72,11 @@ module ActiveModel # class Customer # include ActiveModel::MassAssignmentSecurity # - # attr_accessor :name, :credit_rating + # attr_accessor :name, :email, :logins_count # - # attr_protected :credit_rating, :last_login - # attr_protected :last_login, :as => :admin + # attr_protected :logins_count + # # Suppose that admin can not change email for customer + # attr_protected :logins_count, :email, :as => :admin # # def assign_attributes(values, options = {}) # sanitize_for_mass_assignment(values, options[:as]).each do |k, v| @@ -86,21 +88,21 @@ module ActiveModel # When using the :default role : # # customer = Customer.new - # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :default) + # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5 }, :as => :default) # customer.name # => "David" - # customer.credit_rating # => nil - # customer.last_login # => nil - # - # customer.credit_rating = "Average" - # customer.credit_rating # => "Average" + # customer.email # => "a@b.com" + # customer.logins_count # => nil # # And using the :admin role : # # customer = Customer.new - # customer.assign_attributes({ "name" => "David", "credit_rating" => "Excellent", :last_login => 1.day.ago }, :as => :admin) + # customer.assign_attributes({ "name" => "David", "email" => "a@b.com", :logins_count => 5}, :as => :admin) # customer.name # => "David" - # customer.credit_rating # => "Excellent" - # customer.last_login # => nil + # customer.email # => nil + # customer.logins_count # => nil + # + # customer.email = "c@d.com" + # customer.email # => "c@d.com" # # To start from an all-closed default and enable attributes as needed, # have a look at +attr_accessible+. @@ -113,7 +115,7 @@ module ActiveModel self._protected_attributes = protected_attributes_configs.dup - Array.wrap(role).each do |name| + Array(role).each do |name| self._protected_attributes[name] = self.protected_attributes(name) + args end @@ -175,7 +177,7 @@ module ActiveModel self._accessible_attributes = accessible_attributes_configs.dup - Array.wrap(role).each do |name| + Array(role).each do |name| self._accessible_attributes[name] = self.accessible_attributes(name) + args end @@ -224,12 +226,12 @@ module ActiveModel protected - def sanitize_for_mass_assignment(attributes, role = :default) + def sanitize_for_mass_assignment(attributes, role = nil) _mass_assignment_sanitizer.sanitize(attributes, mass_assignment_authorizer(role)) end - def mass_assignment_authorizer(role = :default) - self.class.active_authorizer[role] + def mass_assignment_authorizer(role) + self.class.active_authorizer[role || :default] end end end diff --git a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb index a1fcdf1a38..9661349503 100644 --- a/activemodel/lib/active_model/mass_assignment_security/permission_set.rb +++ b/activemodel/lib/active_model/mass_assignment_security/permission_set.rb @@ -13,7 +13,7 @@ module ActiveModel end def deny?(key) - raise NotImplementedError, "#deny?(key) suppose to be overwritten" + raise NotImplementedError, "#deny?(key) supposed to be overwritten" end protected diff --git a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb index bbdddfb50d..cfeb4aa7cd 100644 --- a/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb +++ b/activemodel/lib/active_model/mass_assignment_security/sanitizer.rb @@ -1,11 +1,6 @@ -require 'active_support/core_ext/module/delegation' - module ActiveModel module MassAssignmentSecurity class Sanitizer - def initialize(target=nil) - end - # Returns all attributes not denied by the authorizer. def sanitize(attributes, authorizer) sanitized_attributes = attributes.reject { |key, value| authorizer.deny?(key) } @@ -26,11 +21,13 @@ module ActiveModel end class LoggerSanitizer < Sanitizer - delegate :logger, :to => :@target - def initialize(target) @target = target - super + super() + end + + def logger + @target.logger end def logger? @@ -38,14 +35,18 @@ module ActiveModel end def process_removed_attributes(attrs) - logger.debug "WARNING: Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger? + logger.warn "Can't mass-assign protected attributes: #{attrs.join(', ')}" if logger? end end class StrictSanitizer < Sanitizer + def initialize(target = nil) + super() + end + def process_removed_attributes(attrs) return if (attrs - insensitive_attributes).empty? - raise ActiveModel::MassAssignmentSecurity::Error, "Can't mass-assign protected attributes: #{attrs.join(', ')}" + raise ActiveModel::MassAssignmentSecurity::Error.new(attrs) end def insensitive_attributes @@ -54,6 +55,9 @@ module ActiveModel end class Error < StandardError + def initialize(attrs) + super("Can't mass-assign protected attributes: #{attrs.join(', ')}") + end end end end diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index f16459ede2..755e54efcd 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -5,26 +5,35 @@ require 'active_support/core_ext/module/deprecation' module ActiveModel class Name < String - attr_reader :singular, :plural, :element, :collection, :partial_path, :route_key, :param_key, :i18n_key + attr_reader :singular, :plural, :element, :collection, :partial_path, + :singular_route_key, :route_key, :param_key, :i18n_key + alias_method :cache_key, :collection 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 + @i18n_key = self.underscore.to_sym + + @route_key = (namespace ? ActiveSupport::Inflector.pluralize(@param_key) : @plural.dup) + @singular_route_key = ActiveSupport::Inflector.singularize(@route_key).freeze + @route_key << "_index" if @plural == @singular + @route_key.freeze end # Transform the model name into a more humane format, using I18n. By default, @@ -68,8 +77,8 @@ module ActiveModel # BookCover.model_name # => "BookCover" # BookCover.model_name.human # => "Book cover" # - # BookCover.model_name.i18n_key # => "book_cover" - # BookModule::BookCover.model_name.i18n_key # => "book_module.book_cover" + # BookCover.model_name.i18n_key # => :book_cover + # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover" # # Providing the functionality that ActiveModel::Naming provides in your object # is required to pass the Active Model Lint test. So either extending the provided @@ -79,7 +88,9 @@ module ActiveModel # used to retrieve all kinds of naming-related information. def model_name @_model_name ||= begin - namespace = self.parents.detect { |n| n.respond_to?(:_railtie) } + namespace = self.parents.detect do |n| + n.respond_to?(:use_relative_model_naming?) && n.use_relative_model_naming? + end ActiveModel::Name.new(self, namespace) end end @@ -112,10 +123,25 @@ module ActiveModel # namespaced models regarding whether it's inside isolated engine. # # For isolated engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> post + # + # For shared engine: + # ActiveModel::Naming.route_key(Blog::Post) #=> blog_post + def self.singular_route_key(record_or_class) + model_name_from_record_or_class(record_or_class).singular_route_key + end + + # Returns string to use while generating route names. It differs for + # namespaced models regarding whether it's inside isolated engine. + # + # For isolated engine: # ActiveModel::Naming.route_key(Blog::Post) #=> posts # # For shared engine: # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts + # + # The route key also considers if the noun is uncountable and, in + # such cases, automatically appends _index. def self.route_key(record_or_class) model_name_from_record_or_class(record_or_class).route_key end diff --git a/activemodel/lib/active_model/observing.rb b/activemodel/lib/active_model/observing.rb index 7a910d18e7..32f2aa46bd 100644 --- a/activemodel/lib/active_model/observing.rb +++ b/activemodel/lib/active_model/observing.rb @@ -1,6 +1,5 @@ require 'singleton' require 'active_model/observer_array' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/remove_method' require 'active_support/core_ext/string/inflections' @@ -64,7 +63,7 @@ module ActiveModel # raises an +ArgumentError+ exception. def add_observer(observer) unless observer.respond_to? :update - raise ArgumentError, "observer needs to respond to `update'" + raise ArgumentError, "observer needs to respond to 'update'" end observer_instances << observer end @@ -187,8 +186,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. @@ -201,7 +199,7 @@ module ActiveModel # end # end def observed_classes - Array.wrap(observed_class) + Array(observed_class) end # The class observed by default is inferred from the observer's class name: diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index db78864c67..e7a57cf691 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -29,7 +29,7 @@ module ActiveModel # user.save # => true # user.authenticate("notright") # => false # user.authenticate("mUc3m00RsqyRe") # => user - # User.find_by_name("david").try(:authenticate, "notright") # => nil + # User.find_by_name("david").try(:authenticate, "notright") # => false # User.find_by_name("david").try(:authenticate, "mUc3m00RsqyRe") # => user def has_secure_password # Load bcrypt-ruby only when has_secure_password is used. diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index a4b58ab456..51f078e662 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -17,7 +17,7 @@ module ActiveModel # attr_accessor :name # # def attributes - # {'name' => name} + # {'name' => nil} # end # # end @@ -29,8 +29,11 @@ module ActiveModel # person.name = "Bob" # person.serializable_hash # => {"name"=>"Bob"} # - # You need to declare some sort of attributes hash which contains the attributes - # you want to serialize and their current value. + # You need to declare an attributes hash which contains the attributes + # you want to serialize. When called, serializable hash will use + # instance methods that match the name of the attributes hash's keys. + # In order to override this behavior, take a look at the private + # method read_attribute_for_serialization. # # Most of the time though, you will want to include the JSON or XML # serializations. Both of these modules automatically include the @@ -47,7 +50,7 @@ module ActiveModel # attr_accessor :name # # def attributes - # {'name' => name} + # {'name' => nil} # end # # end @@ -73,16 +76,16 @@ module ActiveModel attribute_names = attributes.keys.sort if only = options[:only] - attribute_names &= Array.wrap(only).map(&:to_s) + attribute_names &= Array(only).map(&:to_s) elsif except = options[:except] - attribute_names -= Array.wrap(except).map(&:to_s) + attribute_names -= Array(except).map(&:to_s) end hash = {} attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) } - method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } - method_names.each { |n| hash[n] = send(n) } + method_names = Array(options[:methods]).select { |n| respond_to?(n) } + method_names.each { |n| hash[n.to_s] = send(n) } serializable_add_includes(options) do |association, records, opts| hash[association] = if records.is_a?(Enumerable) diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index c845440120..63ab8e7edc 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -10,8 +10,9 @@ module ActiveModel included do extend ActiveModel::Naming + extend ActiveModel::Configuration - class_attribute :include_root_in_json + config_attribute :include_root_in_json self.include_root_in_json = true end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index d61d9d7119..5084298210 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/conversions' @@ -56,7 +55,7 @@ module ActiveModel end def serializable_collection - methods = Array.wrap(options[:methods]).map(&:to_s) + methods = Array(options[:methods]).map(&:to_s) serializable_hash.map do |name, value| name = name.to_s if methods.include?(name) @@ -146,7 +145,7 @@ module ActiveModel def add_procs if procs = options.delete(:procs) - Array.wrap(procs).each do |proc| + Array(procs).each do |proc| if proc.arity == 1 proc.call(options) else diff --git a/activemodel/lib/active_model/test_case.rb b/activemodel/lib/active_model/test_case.rb index 6328807ad7..5004855d56 100644 --- a/activemodel/lib/active_model/test_case.rb +++ b/activemodel/lib/active_model/test_case.rb @@ -1,16 +1,4 @@ module ActiveModel #:nodoc: class TestCase < ActiveSupport::TestCase #:nodoc: - def with_kcode(kcode) - if RUBY_VERSION < '1.9' - orig_kcode, $KCODE = $KCODE, kcode - begin - yield - ensure - $KCODE = orig_kcode - end - else - yield - end - end end end diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 6d64c81b5f..02b7c54d61 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -43,13 +43,25 @@ module ActiveModel # # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) - defaults = lookup_ancestors.map do |klass| - :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}" + defaults = [] + parts = attribute.to_s.split(".", 2) + attribute = parts.pop + namespace = parts.pop + + if namespace + lookup_ancestors.each do |klass| + defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}/#{namespace}.#{attribute}" + end + defaults << :"#{self.i18n_scope}.attributes.#{namespace}.#{attribute}" + else + lookup_ancestors.each do |klass| + defaults << :"#{self.i18n_scope}.attributes.#{klass.model_name.i18n_key}.#{attribute}" + end end defaults << :"attributes.#{attribute}" defaults << options.delete(:default) if options[:default] - defaults << attribute.to_s.humanize + defaults << attribute.humanize options.reverse_merge! :count => 1, :default => defaults I18n.translate(defaults.shift, options) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 8ed392abca..0e15155b85 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/array/extract_options' -require 'active_support/core_ext/array/wrap' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/hash/keys' require 'active_support/core_ext/hash/except' @@ -34,7 +33,7 @@ module ActiveModel # person.first_name = 'zoolander' # person.valid? # => false # person.invalid? # => true - # person.errors # => #<OrderedHash {:first_name=>["starts with z."]}> + # person.errors # => #<Hash {:first_name=>["starts with z."]}> # # Note that <tt>ActiveModel::Validations</tt> automatically adds an +errors+ method # to your instances initialized with a new <tt>ActiveModel::Errors</tt> object, so @@ -42,9 +41,9 @@ module ActiveModel # module Validations extend ActiveSupport::Concern - include ActiveSupport::Callbacks included do + extend ActiveModel::Callbacks extend ActiveModel::Translation extend HelperMethods @@ -53,7 +52,8 @@ module ActiveModel attr_accessor :validation_context define_callbacks :validate, :scope => :name - class_attribute :_validators + extend ActiveModel::Configuration + config_attribute :_validators self._validators = Hash.new { |h,k| h[k] = [] } end @@ -132,7 +132,7 @@ module ActiveModel options = args.extract_options! if options.key?(:on) options = options.dup - options[:if] = Array.wrap(options[:if]) + options[:if] = Array(options[:if]) options[:if].unshift("validation_context == :#{options[:on]}") end args << options diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 22a77320dc..c39c85e1af 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -23,14 +23,14 @@ module ActiveModel included do include ActiveSupport::Callbacks - define_callbacks :validation, :terminator => "result == false", :scope => [:kind, :name] + define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name] end module ClassMethods def before_validation(*args, &block) options = args.last if options.is_a?(Hash) && options[:on] - options[:if] = Array.wrap(options[:if]) + options[:if] = Array(options[:if]) options[:if].unshift("self.validation_context == :#{options[:on]}") end set_callback(:validation, :before, *args, &block) @@ -39,8 +39,7 @@ module ActiveModel def after_validation(*args, &block) options = args.extract_options! options[:prepend] = true - options[:if] = Array.wrap(options[:if]) - options[:if] << "!halted" + options[:if] = Array(options[:if]) options[:if].unshift("self.validation_context == :#{options[:on]}") if options[:on] set_callback(:validation, :after, *(args << options), &block) end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 6573a7d264..e8526303e2 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -37,7 +37,7 @@ module ActiveModel # attribute. # # NOTE: This check is performed only if +password_confirmation+ is not - # +nil+, and by default only on save. To require confirmation, make sure + # +nil+. To require confirmation, make sure # to add a presence check for the confirmation attribute: # # validates_presence_of :password_confirmation, :if => :password_changed? diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index eb7aac709d..f1beddf6d4 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -6,14 +6,12 @@ module ActiveModel MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze - DEFAULT_TOKENIZER = lambda { |value| value.split(//) } RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long] def initialize(options) if range = (options.delete(:in) || options.delete(:within)) raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range) - options[:minimum], options[:maximum] = range.begin, range.end - options[:maximum] -= 1 if range.exclude_end? + options[:minimum], options[:maximum] = range.min, range.max end super @@ -23,27 +21,24 @@ module ActiveModel keys = CHECKS.keys & options.keys if keys.empty? - raise ArgumentError, 'Range unspecified. Specify the :within, :maximum, :minimum, or :is option.' + raise ArgumentError, 'Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option.' end keys.each do |key| value = options[key] - unless value.is_a?(Integer) && value >= 0 - raise ArgumentError, ":#{key} must be a nonnegative Integer" + unless value.is_a?(Integer) && value >= 0 or value == Float::INFINITY + raise ArgumentError, ":#{key} must be a nonnegative Integer or Infinity" end end end def validate_each(record, attribute, value) - value = (options[:tokenizer] || DEFAULT_TOKENIZER).call(value) if value.kind_of?(String) + value = tokenize(value) + value_length = value.respond_to?(:length) ? value.length : value.to_s.length CHECKS.each do |key, validity_check| next unless check_value = options[key] - - value ||= [] if key == :maximum - - value_length = value.respond_to?(:length) ? value.length : value.to_s.length next if value_length.send(validity_check, check_value) errors_options = options.except(*RESERVED_OPTIONS) @@ -55,6 +50,14 @@ module ActiveModel record.errors.add(attribute, MESSAGES[key], errors_options) end end + + private + + def tokenize(value) + if options[:tokenizer] && value.kind_of?(String) + options[:tokenizer].call(value) + end || value + end end module HelperMethods @@ -96,7 +99,7 @@ module ActiveModel # * <tt>:tokenizer</tt> - Specifies how to split up the attribute string. (e.g. <tt>:tokenizer => lambda {|str| str.scan(/\w+/)}</tt> to # count words as in above example.) # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information def validates_length_of(*attr_names) validates_with LengthValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 34d447a0fa..bb9f9679fc 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -5,7 +5,7 @@ module ActiveModel class NumericalityValidator < EachValidator CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=, :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=, - :odd => :odd?, :even => :even? }.freeze + :odd => :odd?, :even => :even?, :other_than => :!= }.freeze RESERVED_OPTIONS = CHECKS.keys + [:only_integer] @@ -99,6 +99,7 @@ module ActiveModel # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied value. # * <tt>:less_than</tt> - Specifies the value must be less than the supplied value. # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less than or equal the supplied value. + # * <tt>:other_than</tt> - Specifies the value must be other than the supplied value. # * <tt>:odd</tt> - Specifies the value must be an odd number. # * <tt>:even</tt> - Specifies the value must be an even number. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should @@ -107,7 +108,7 @@ module ActiveModel # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. - # * <tt>:strict</tt> - Specifies whether validation should be strict. + # * <tt>:strict</tt> - Specifies whether validation should be strict. # See <tt>ActiveModel::Validation#validates!</tt> for more information # # The following checks can also be supplied with a proc or a symbol which corresponds to a method: diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index 35af7152db..9a643a6f5c 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -25,14 +25,14 @@ module ActiveModel # This is due to the way Object#blank? handles boolean values: <tt>false.blank? # => true</tt>. # # Configuration options: - # * <tt>message</tt> - A custom error message (default is: "can't be blank"). + # * <tt>:message</tt> - A custom error message (default is: "can't be blank"). # * <tt>:on</tt> - Specifies when this validation is active. Runs in all # validation contexts by default (+nil+), other options are <tt>:create</tt> # and <tt>:update</tt>. - # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should + # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. - # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should + # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. # * <tt>:strict</tt> - Specifies whether validation should be strict. diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index fbceb81e8f..d94c4e3f4f 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -1,7 +1,6 @@ require 'active_support/core_ext/hash/slice' module ActiveModel - # == Active Model validates method module Validations module ClassMethods @@ -57,9 +56,9 @@ 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, + # The validators hash can also handle regular expressions, ranges, # arrays and strings in shortcut form, e.g. # # validates :email, :format => /@/ @@ -70,7 +69,7 @@ module ActiveModel # validator's initializer as +options[:in]+ while other types including # regular expressions and strings are passed as +options[:with]+ # - # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+ + # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+ and +:strict+ # can be given to one specific validator, as a hash: # # validates :password, :presence => { :if => :password_required? }, :confirmation => true @@ -80,7 +79,7 @@ module ActiveModel # validates :password, :presence => true, :confirmation => true, :if => :password_required? # def validates(*attributes) - defaults = attributes.extract_options! + defaults = attributes.extract_options!.dup validations = defaults.slice!(*_validates_default_keys) raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? @@ -101,12 +100,12 @@ module ActiveModel end end - # This method is used to define validation that can not be corrected by end user - # and is considered exceptional. - # So each validator defined with bang or <tt>:strict</tt> option set to <tt>true</tt> - # will always raise <tt>ActiveModel::InternalValidationFailed</tt> instead of adding error - # when validation fails - # See <tt>validates</tt> for more information about validation itself. + # This method is used to define validations that cannot be corrected by end + # users and are considered exceptional. So each validator defined with bang + # or <tt>:strict</tt> option set to <tt>true</tt> will always raise + # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error + # when validation fails. + # See <tt>validates</tt> for more information about the validation itself. def validates!(*attributes) options = attributes.extract_options! options[:strict] = true @@ -118,7 +117,7 @@ module ActiveModel # When creating custom validators, it might be useful to be able to specify # additional default keys. This can be done by overwriting this method. def _validates_default_keys - [ :if, :unless, :on, :allow_blank, :allow_nil , :strict] + [:if, :unless, :on, :allow_blank, :allow_nil , :strict] end def _parse_validates_options(options) #:nodoc: diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 93a340eb39..72b8562b93 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -56,7 +56,7 @@ module ActiveModel # if the validation should occur (e.g. <tt>:if => :allow_validation</tt>, # or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. - # * <tt>unless</tt> - Specifies a method, proc or string to call to + # * <tt>:unless</tt> - Specifies a method, proc or string to call to # determine if the validation should not occur # (e.g. <tt>:unless => :skip_validation</tt>, or # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 35ec98c822..2953126c3c 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,4 +1,3 @@ -require 'active_support/core_ext/array/wrap' require "active_support/core_ext/module/anonymous" require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/inclusion' @@ -137,7 +136,7 @@ module ActiveModel #:nodoc: # +options+ reader, however the <tt>:attributes</tt> option will be removed # and instead be made available through the +attributes+ reader. def initialize(options) - @attributes = Array.wrap(options.delete(:attributes)) + @attributes = Array(options.delete(:attributes)) raise ":attributes cannot be blank" if @attributes.empty? super check_validity! diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index dbda55ca7c..e195c12a4d 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,7 +1,7 @@ module ActiveModel module VERSION #:nodoc: - MAJOR = 3 - MINOR = 2 + MAJOR = 4 + MINOR = 0 TINY = 0 PRE = "beta" |