diff options
Diffstat (limited to 'activemodel')
57 files changed, 944 insertions, 545 deletions
diff --git a/activemodel/CHANGELOG b/activemodel/CHANGELOG deleted file mode 100644 index bf077bef35..0000000000 --- a/activemodel/CHANGELOG +++ /dev/null @@ -1,109 +0,0 @@ -* Added ActiveModel::Errors#added? to check if a specific error has been added [Martin Svalin] - -* Add ability to define strict validation(with :strict => true option) that always raises exception when fails [Bogdan Gusiev] - -* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" [Grant Hutchins, Peter Jaros] - -* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior [Bogdan Gusiev] - -*Rails 3.1.0 (August 30, 2011)* - -* Alternate I18n namespace lookup is no longer supported. - Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead [José Valim] - -* attr_accessible and friends now accepts :as as option to specify a role [Josh Kalderimis] - -* Add support for proc or lambda as an option for InclusionValidator, - ExclusionValidator, and FormatValidator [Prem Sichanugrist] - - You can now supply Proc, lambda, or anything that respond to #call in those - validations, and it will be called with current record as an argument. - That given proc or lambda must returns an object which respond to #include? for - InclusionValidator and ExclusionValidator, and returns a regular expression - object for FormatValidator. - -* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting [DHH] - -* ActiveModel::AttributeMethods allows attributes to be defined on demand [Alexander Uvarov] - -* Add support for selectively enabling/disabling observers [Myron Marston] - - -*Rails 3.0.7 (April 18, 2011)* - -*No changes. - - -*Rails 3.0.6 (April 5, 2011) - -* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 [Robert Pankowecki, Santiago Pastorino] - -* Fix length validation for fixnums #6556 [Andriy Tyurnikov] - -* Fix i18n key collision with namespaced models #6448 [yves.senn] - - -*Rails 3.0.5 (February 26, 2011)* - -* No changes. - - -*Rails 3.0.4 (February 8, 2011)* - -* No changes. - - -*Rails 3.0.3 (November 16, 2010)* - -* No changes. - - -*Rails 3.0.2 (November 15, 2010)* - -* No changes - - -*Rails 3.0.1 (October 15, 2010)* - -* No Changes, just a version bump. - - -*Rails 3.0.0 (August 29, 2010)* - -* Added ActiveModel::MassAssignmentSecurity [Eric Chapweske, Josh Kalderimis] - -* JSON supports a custom root option: to_json(:root => 'custom') #4515 [Jatinder Singh] - -* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use - persisted? instead. A model is persisted if it's not a new_record? and it was - not destroyed? [MG] - -* Added validations reflection in ActiveModel::Validations [JV] - - Model.validators - Model.validators_on(:field) - -* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for - AMo objects with composite keys [MG] - -* ActiveModel::Observer#add_observer! - - It has a custom hook to define after_find that should really be in a - ActiveRecord::Observer subclass: - - def add_observer!(klass) - klass.add_observer(self) - klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) - end - -* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 [DHH] - -* Add validates_format_of :without => /regexp/ option. #430 [Elliot Winkler, Peer Allan] - - Example : - - validates_format_of :subdomain, :without => /www|admin|mail/ - -* Introduce validates_with to encapsulate attribute validations in a class. #2630 [Jeff Dean] - -* Extracted from Active Record and Active Resource. diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md new file mode 100644 index 0000000000..258c3681f6 --- /dev/null +++ b/activemodel/CHANGELOG.md @@ -0,0 +1,118 @@ +* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim* + +## Rails 3.2.0 (January 20, 2012) ## + +* Deprecated `define_attr_method` in `ActiveModel::AttributeMethods`, because this only existed to + support methods like `set_table_name` in Active Record, which are themselves being deprecated. + + *Jon Leighton* + +* Add ActiveModel::Errors#added? to check if a specific error has been added *Martin Svalin* + +* Add ability to define strict validation(with :strict => true option) that always raises exception when fails *Bogdan Gusiev* + +* Deprecate "Model.model_name.partial_path" in favor of "model.to_partial_path" *Grant Hutchins, Peter Jaros* + +* Provide mass_assignment_sanitizer as an easy API to replace the sanitizer behavior. Also support both :logger (default) and :strict sanitizer behavior *Bogdan Gusiev* + +## Rails 3.1.0 (August 30, 2011) ## + +* Alternate I18n namespace lookup is no longer supported. + Instead of "activerecord.models.admins.post", do "activerecord.models.admins/post" instead *José Valim* + +* attr_accessible and friends now accepts :as as option to specify a role *Josh Kalderimis* + +* Add support for proc or lambda as an option for InclusionValidator, + ExclusionValidator, and FormatValidator *Prem Sichanugrist* + + You can now supply Proc, lambda, or anything that respond to #call in those + validations, and it will be called with current record as an argument. + That given proc or lambda must returns an object which respond to #include? for + InclusionValidator and ExclusionValidator, and returns a regular expression + object for FormatValidator. + +* Added ActiveModel::SecurePassword to encapsulate dead-simple password usage with BCrypt encryption and salting *DHH* + +* ActiveModel::AttributeMethods allows attributes to be defined on demand *Alexander Uvarov* + +* Add support for selectively enabling/disabling observers *Myron Marston* + + +## Rails 3.0.7 (April 18, 2011) ## + +* No changes. + + +* Rails 3.0.6 (April 5, 2011) + +* Fix when database column name has some symbolic characters (e.g. Oracle CASE# VARCHAR2(20)) #5818 #6850 *Robert Pankowecki, Santiago Pastorino* + +* Fix length validation for fixnums #6556 *Andriy Tyurnikov* + +* Fix i18n key collision with namespaced models #6448 *yves.senn* + + +## Rails 3.0.5 (February 26, 2011) ## + +* No changes. + + +## Rails 3.0.4 (February 8, 2011) ## + +* No changes. + + +## Rails 3.0.3 (November 16, 2010) ## + +* No changes. + + +## Rails 3.0.2 (November 15, 2010) ## + +* No changes + + +## Rails 3.0.1 (October 15, 2010) ## + +* No Changes, just a version bump. + + +## Rails 3.0.0 (August 29, 2010) ## + +* Added ActiveModel::MassAssignmentSecurity *Eric Chapweske, Josh Kalderimis* + +* JSON supports a custom root option: to_json(:root => 'custom') #4515 *Jatinder Singh* + +* #new_record? and #destroyed? were removed from ActiveModel::Lint. Use + persisted? instead. A model is persisted if it's not a new_record? and it was + not destroyed? *MG* + +* Added validations reflection in ActiveModel::Validations *JV* + + Model.validators + Model.validators_on(:field) + +* #to_key was added to ActiveModel::Lint so we can generate DOM IDs for + AMo objects with composite keys *MG* + +* ActiveModel::Observer#add_observer! + + It has a custom hook to define after_find that should really be in a + ActiveRecord::Observer subclass: + + def add_observer!(klass) + klass.add_observer(self) + klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find) + end + +* Change the ActiveModel::Base.include_root_in_json default to true for Rails 3 *DHH* + +* Add validates_format_of :without => /regexp/ option. #430 *Elliot Winkler, Peer Allan* + + Example : + + validates_format_of :subdomain, :without => /www|admin|mail/ + +* Introduce validates_with to encapsulate attribute validations in a class. #2630 *Jeff Dean* + +* Extracted from Active Record and Active Resource. diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE index 7ad1051066..810daf856c 100644 --- a/activemodel/MIT-LICENSE +++ b/activemodel/MIT-LICENSE @@ -1,4 +1,4 @@ -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 diff --git a/activemodel/README.rdoc b/activemodel/README.rdoc index 67701bc422..a7ba27ba73 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -9,10 +9,12 @@ Prior to Rails 3.0, if a plugin or gem developer wanted to have an object interact with Action Pack helpers, it was required to either copy chunks of code from Rails, or monkey patch entire helpers to make them handle objects that did not exactly conform to the Active Record interface. This would result -in code duplication and fragile applications that broke on upgrades. +in code duplication and fragile applications that broke on upgrades. Active +Model solves this by defining an explicit API. You can read more about the +API in ActiveModel::Lint::Tests. -Active Model solves this. You can include functionality from the following -modules: +Active Model also provides the following functionality to have ORM-like +behavior out of the box: * Add attribute magic to objects @@ -87,10 +89,9 @@ modules: errors.add(:name, "can not be nil") if name.nil? end - def ErrorsPerson.human_attribute_name(attr, options = {}) + def self.human_attribute_name(attr, options = {}) "Name" end - end person.errors.full_messages @@ -163,7 +164,7 @@ modules: * Custom validators - class Person + class ValidatorPerson include ActiveModel::Validations validates_with HasNameValidator attr_accessor :name @@ -171,7 +172,7 @@ modules: class HasNameValidator < ActiveModel::Validator def validate(record) - record.errors[:name] = "must exist" if record.name.blank? + record.errors[:name] = "must exist" if record.name.blank? end end @@ -182,7 +183,7 @@ modules: p.valid? # => true {Learn more}[link:classes/ActiveModel/Validator.html] - + == Download and installation @@ -197,7 +198,9 @@ Source code can be downloaded as part of the Rails project on GitHub == License -Active Model is released under the MIT license. +Active Model is released under the MIT license: + +* http://www.opensource.org/licenses/MIT == Support diff --git a/activemodel/Rakefile b/activemodel/Rakefile index c4b020196d..fc5aaf9f8f 100755 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -8,6 +8,7 @@ Rake::TestTask.new do |t| t.libs << "test" t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.warning = true + t.verbose = true end namespace :test do diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 260ad01b65..60c1d16934 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -7,16 +7,15 @@ Gem::Specification.new do |s| s.summary = 'A toolkit for building modeling frameworks (part of Rails).' s.description = 'A toolkit for building modeling frameworks like Active Record and Active Resource. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' - s.required_ruby_version = '>= 1.8.7' + s.required_ruby_version = '>= 1.9.3' s.author = 'David Heinemeier Hansson' s.email = 'david@loudthinking.com' s.homepage = 'http://www.rubyonrails.org' - s.files = Dir['CHANGELOG', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] + s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*'] s.require_path = 'lib' s.add_dependency('activesupport', version) s.add_dependency('builder', '~> 3.0.0') - s.add_dependency('i18n', '~> 0.6') end 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" diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index 67471ed497..9406328d3e 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -76,7 +76,15 @@ private end end +class ModelWithoutAttributesMethod + include ActiveModel::AttributeMethods +end + class AttributeMethodsTest < ActiveModel::TestCase + test 'method missing works correctly even if attributes method is not defined' do + assert_raises(NoMethodError) { ModelWithoutAttributesMethod.new.foo } + end + test 'unrelated classes should not share attribute method matchers' do assert_not_equal ModelWithAttributes.send(:attribute_method_matchers), ModelWithAttributes2.send(:attribute_method_matchers) @@ -133,24 +141,6 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') end - test '#define_attr_method generates attribute method' do - ModelWithAttributes.define_attr_method(:bar, 'bar') - - assert_respond_to ModelWithAttributes, :bar - assert_equal "original bar", ModelWithAttributes.original_bar - assert_equal "bar", ModelWithAttributes.bar - ModelWithAttributes.define_attr_method(:bar) - assert !ModelWithAttributes.bar - end - - test '#define_attr_method generates attribute method with invalid identifier characters' do - ModelWithWeirdNamesAttributes.define_attr_method(:'c?d', 'c?d') - - assert_respond_to ModelWithWeirdNamesAttributes, :'c?d' - assert_equal "original c?d", ModelWithWeirdNamesAttributes.send('original_c?d') - assert_equal "c?d", ModelWithWeirdNamesAttributes.send('c?d') - end - test '#alias_attribute works with attributes with spaces in their names' do ModelWithAttributesWithSpaces.define_attribute_methods([:'foo bar']) ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') diff --git a/activemodel/test/cases/configuration_test.rb b/activemodel/test/cases/configuration_test.rb new file mode 100644 index 0000000000..a172fa26a3 --- /dev/null +++ b/activemodel/test/cases/configuration_test.rb @@ -0,0 +1,154 @@ +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/errors_test.rb b/activemodel/test/cases/errors_test.rb index 8ddedb160a..7a610e0c2c 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -27,12 +27,27 @@ class ErrorsTest < ActiveModel::TestCase end end + def test_delete + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'omg' + errors.delete(:foo) + assert_empty errors[:foo] + end + def test_include? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' assert errors.include?(:foo), 'errors should include :foo' end + def test_dup + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'bar' + errors_dup = errors.dup + errors_dup[:bar] = 'omg' + assert_not_same errors_dup.messages, errors.messages + end + def test_has_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' @@ -136,10 +151,10 @@ class ErrorsTest < ActiveModel::TestCase assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a end - test 'to_hash should return an ordered hash' do + test 'to_hash should return a hash' do person = Person.new person.errors.add(:name, "can not be blank") - assert_instance_of ActiveSupport::OrderedHash, person.errors.to_hash + assert_instance_of ::Hash, person.errors.to_hash end test 'full_messages should return an array of error messages, with the attribute name included' do diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 2e860272a4..4347b17cbc 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -10,4 +10,4 @@ require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true -require 'test/unit' +require 'minitest/autorun' diff --git a/activemodel/test/cases/lint_test.rb b/activemodel/test/cases/lint_test.rb index 68372160cd..8faf93c056 100644 --- a/activemodel/test/cases/lint_test.rb +++ b/activemodel/test/cases/lint_test.rb @@ -7,14 +7,10 @@ class LintTest < ActiveModel::TestCase extend ActiveModel::Naming include ActiveModel::Conversion - def valid?() true end def persisted?() false end def errors - obj = Object.new - def obj.[](key) [] end - def obj.full_messages() [] end - obj + Hash.new([]) end end diff --git a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb index 676937b5e1..3660b9b1e5 100644 --- a/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb +++ b/activemodel/test/cases/mass_assignment_security/sanitizer_test.rb @@ -1,5 +1,5 @@ require "cases/helper" -require 'logger' +require 'active_support/logger' require 'active_support/core_ext/object/inclusion' class SanitizerTest < ActiveModel::TestCase @@ -28,7 +28,7 @@ class SanitizerTest < ActiveModel::TestCase test "debug mass assignment removal with LoggerSanitizer" do original_attributes = { 'first_name' => 'allowed', 'admin' => 'denied' } log = StringIO.new - self.logger = Logger.new(log) + self.logger = ActiveSupport::Logger.new(log) @logger_sanitizer.sanitize(original_attributes, @authorizer) assert_match(/admin/, log.string, "Should log removed attributes: #{log.string}") end diff --git a/activemodel/test/cases/mass_assignment_security_test.rb b/activemodel/test/cases/mass_assignment_security_test.rb index be07e59a2f..a197dbe748 100644 --- a/activemodel/test/cases/mass_assignment_security_test.rb +++ b/activemodel/test/cases/mass_assignment_security_test.rb @@ -19,6 +19,13 @@ class MassAssignmentSecurityTest < ActiveModel::TestCase assert_equal expected, sanitized end + def test_attribute_protection_when_role_is_nil + user = User.new + expected = { "name" => "John Smith", "email" => "john@smith.com" } + sanitized = user.sanitize_for_mass_assignment(expected.merge("admin" => true), nil) + assert_equal expected, sanitized + end + def test_only_moderator_role_attribute_accessible user = SpecialUser.new expected = { "name" => "John Smith", "email" => "john@smith.com" } diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 5f943729dd..1e14d83bcb 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -34,6 +34,10 @@ class NamingTest < ActiveModel::TestCase def test_human assert_equal 'Track back', @model_name.human end + + def test_i18n_key + assert_equal :"post/track_back", @model_name.i18n_key + end end class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase @@ -75,8 +79,8 @@ class NamingWithNamespacedModelInIsolatedNamespaceTest < ActiveModel::TestCase assert_equal 'post', @model_name.param_key end - def test_recognizing_namespace - assert_equal 'Post', Blog::Post.model_name.instance_variable_get("@unnamespaced") + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key end end @@ -118,6 +122,10 @@ class NamingWithNamespacedModelInSharedNamespaceTest < ActiveModel::TestCase def test_param_key assert_equal 'blog_post', @model_name.param_key end + + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key + end end class NamingWithSuppliedModelNameTest < ActiveModel::TestCase @@ -158,15 +166,58 @@ class NamingWithSuppliedModelNameTest < ActiveModel::TestCase def test_param_key assert_equal 'article', @model_name.param_key end + + def test_i18n_key + assert_equal :"article", @model_name.i18n_key + end +end + +class NamingUsingRelativeModelNameTest < ActiveModel::TestCase + def setup + @model_name = Blog::Post.model_name + end + + def test_singular + assert_equal 'blog_post', @model_name.singular + end + + def test_plural + assert_equal 'blog_posts', @model_name.plural + end + + def test_element + assert_equal 'post', @model_name.element + end + + def test_collection + assert_equal 'blog/posts', @model_name.collection + end + + def test_human + assert_equal 'Post', @model_name.human + end + + def test_route_key + assert_equal 'posts', @model_name.route_key + end + + def test_param_key + assert_equal 'post', @model_name.param_key + end + + def test_i18n_key + assert_equal :"blog/post", @model_name.i18n_key + end end -class NamingHelpersTest < Test::Unit::TestCase +class NamingHelpersTest < ActiveModel::TestCase def setup @klass = Contact @record = @klass.new @singular = 'contact' @plural = 'contacts' @uncountable = Sheep + @singular_route_key = 'contact' @route_key = 'contacts' @param_key = 'contact' end @@ -193,10 +244,12 @@ class NamingHelpersTest < Test::Unit::TestCase def test_route_key assert_equal @route_key, route_key(@record) + assert_equal @singular_route_key, singular_route_key(@record) end def test_route_key_for_class assert_equal @route_key, route_key(@klass) + assert_equal @singular_route_key, singular_route_key(@klass) end def test_param_key @@ -212,8 +265,26 @@ class NamingHelpersTest < Test::Unit::TestCase assert !uncountable?(@klass), "Expected 'contact' to be countable" end + def test_uncountable_route_key + assert_equal "sheep", singular_route_key(@uncountable) + assert_equal "sheep_index", route_key(@uncountable) + end + private def method_missing(method, *args) ActiveModel::Naming.send(method, *args) end end + +class NameWithAnonymousClassTest < ActiveModel::TestCase + def test_anonymous_class_without_name_argument + assert_raises(ArgumentError) do + ActiveModel::Name.new(Class.new) + end + end + + def test_anonymous_class_with_name_argument + model_name = ActiveModel::Name.new(Class.new, nil, "Anonymous") + assert_equal "Anonymous", model_name + end +end diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index b8dad9d51f..3b201a70f5 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -43,38 +43,38 @@ class SerializationTest < ActiveModel::TestCase end def test_method_serializable_hash_should_work - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash end def test_method_serializable_hash_should_work_with_only_option - expected = {"name"=>"David"} - assert_equal expected , @user.serializable_hash(:only => [:name]) + expected = {"name"=>"David"} + assert_equal expected, @user.serializable_hash(:only => [:name]) end def test_method_serializable_hash_should_work_with_except_option - expected = {"gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:except => [:name]) + expected = {"gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:except => [:name]) end def test_method_serializable_hash_should_work_with_methods_option - expected = {"name"=>"David", "gender"=>"male", :foo=>"i_am_foo", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:foo]) + expected = {"name"=>"David", "gender"=>"male", "foo"=>"i_am_foo", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:methods => [:foo]) end def test_method_serializable_hash_should_work_with_only_and_methods - expected = {:foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:only => [], :methods => [:foo]) + expected = {"foo"=>"i_am_foo"} + assert_equal expected, @user.serializable_hash(:only => [], :methods => [:foo]) end def test_method_serializable_hash_should_work_with_except_and_methods - expected = {"gender"=>"male", :foo=>"i_am_foo"} - assert_equal expected , @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) + expected = {"gender"=>"male", "foo"=>"i_am_foo"} + assert_equal expected, @user.serializable_hash(:except => [:name, :email], :methods => [:foo]) end def test_should_not_call_methods_that_dont_respond - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} - assert_equal expected , @user.serializable_hash(:methods => [:bar]) + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com"} + assert_equal expected, @user.serializable_hash(:methods => [:bar]) end def test_should_use_read_attribute_for_serialization @@ -87,65 +87,64 @@ class SerializationTest < ActiveModel::TestCase end def test_include_option_with_singular_association - expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} - assert_equal expected , @user.serializable_hash(:include => :address) + expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} + assert_equal expected, @user.serializable_hash(:include => :address) end def test_include_option_with_plural_association - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => :friends) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => :friends) end def test_include_option_with_empty_association @user.friends = [] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} - assert_equal expected , @user.serializable_hash(:include => :friends) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", :friends=>[]} + assert_equal expected, @user.serializable_hash(:include => :friends) end def test_multiple_includes - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [:address, :friends]) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}, + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => [:address, :friends]) end def test_include_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}} - assert_equal expected , @user.serializable_hash(:include => {:address => {:only => "street"}}) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane"}} + assert_equal expected, @user.serializable_hash(:include => {:address => {:only => "street"}}) end def test_nested_include @user.friends.first.friends = [@user] - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', - :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male', + :friends => [{"email"=>"david@example.com", "gender"=>"male", "name"=>"David"}]}, {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female', :friends => []}]} - assert_equal expected , @user.serializable_hash(:include => {:friends => {:include => :friends}}) + assert_equal expected, @user.serializable_hash(:include => {:friends => {:include => :friends}}) end def test_only_include expected = {"name"=>"David", :friends => [{"name" => "Joe"}, {"name" => "Sue"}]} - assert_equal expected , @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) + assert_equal expected, @user.serializable_hash(:only => :name, :include => {:friends => {:only => :name}}) end def test_except_include expected = {"name"=>"David", "email"=>"david@example.com", - :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, - {"name" => "Sue", "email" => 'sue@example.com'}]} - assert_equal expected , @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) + :friends => [{"name" => 'Joe', "email" => 'joe@example.com'}, + {"name" => "Sue", "email" => 'sue@example.com'}]} + assert_equal expected, @user.serializable_hash(:except => :gender, :include => {:friends => {:except => :gender}}) end def test_multiple_includes_with_options - expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", - :address=>{"street"=>"123 Lane"}, - :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, - {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} - assert_equal expected , @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) + expected = {"email"=>"david@example.com", "gender"=>"male", "name"=>"David", + :address=>{"street"=>"123 Lane"}, + :friends=>[{"name"=>'Joe', "email"=>'joe@example.com', "gender"=>'male'}, + {"name"=>'Sue', "email"=>'sue@example.com', "gender"=>'female'}]} + assert_equal expected, @user.serializable_hash(:include => [{:address => {:only => "street"}}, :friends]) end - end diff --git a/activemodel/test/cases/serializers/json_serialization_test.rb b/activemodel/test/cases/serializers/json_serialization_test.rb index a754d610b9..7160635eb4 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -14,9 +14,11 @@ class Contact end end + remove_method :attributes if method_defined?(:attributes) + def attributes instance_values - end unless method_defined?(:attributes) + end end class JsonSerializationTest < ActiveModel::TestCase @@ -128,13 +130,13 @@ class JsonSerializationTest < ActiveModel::TestCase assert_match %r{"favorite_quote":"Constraints are liberating"}, methods_json end - test "should return OrderedHash for errors" do + test "should return Hash for errors" do contact = Contact.new contact.errors.add :name, "can't be blank" contact.errors.add :name, "is too short (minimum is 2 characters)" contact.errors.add :age, "must be 16 or over" - hash = ActiveSupport::OrderedHash.new + hash = {} hash[:name] = ["can't be blank", "is too short (minimum is 2 characters)"] hash[:age] = ["must be 16 or over"] assert_equal hash.to_json, contact.errors.to_json diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index fc73d9dcd8..38aecf51ff 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -9,6 +9,8 @@ class Contact attr_accessor :address, :friends + remove_method :attributes if method_defined?(:attributes) + def attributes instance_values.except("address", "friends") end diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index 1b1d972d5c..54e86d48db 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -56,6 +56,16 @@ class ActiveModelI18nTests < ActiveModel::TestCase assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') 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') + end + + def test_translated_nested_model_attributes_with_namespace_fallback + I18n.backend.store_translations 'en', :activemodel => {:attributes => {:addresses => {:street => 'Cool Address Street'}}} + assert_equal 'Cool Address Street', Person.human_attribute_name('addresses.street') + end + def test_translated_model_names I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} } assert_equal 'person model', Person.model_name.human diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 1cf09758f9..e4f602bd80 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -5,11 +5,10 @@ class Dog include ActiveModel::Validations include ActiveModel::Validations::Callbacks - attr_accessor :name - attr_writer :history + attr_accessor :name, :history - def history - @history ||= [] + def initialize + @history = [] end end diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb index 72a383f128..adab8ccb2b 100644 --- a/activemodel/test/cases/validations/exclusion_validation_test.rb +++ b/activemodel/test/cases/validations/exclusion_validation_test.rb @@ -46,12 +46,12 @@ class ExclusionValidationTest < ActiveModel::TestCase def test_validates_exclusion_of_with_lambda Topic.validates_exclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } - p = Topic.new - p.title = "elephant" - p.author_name = "sikachu" - assert p.invalid? + t = Topic.new + t.title = "elephant" + t.author_name = "sikachu" + assert t.invalid? - p.title = "wasabi" - assert p.valid? + t.title = "wasabi" + assert t.valid? end end diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 2ce714fef0..41a1131bcb 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -101,25 +101,25 @@ class PresenceValidationTest < ActiveModel::TestCase def test_validates_format_of_with_lambda Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ } - p = Topic.new - p.title = "digit" - p.content = "Pixies" - assert p.invalid? + t = Topic.new + t.title = "digit" + t.content = "Pixies" + assert t.invalid? - p.content = "1234" - assert p.valid? + t.content = "1234" + assert t.valid? end def test_validates_format_of_without_lambda Topic.validates_format_of :content, :without => lambda{ |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ } - p = Topic.new - p.title = "characters" - p.content = "1234" - assert p.invalid? + t = Topic.new + t.title = "characters" + t.content = "1234" + assert t.invalid? - p.content = "Pixies" - assert p.valid? + t.content = "Pixies" + assert t.valid? end def test_validates_format_of_for_ruby_class diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 413da92de4..851d345eab 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -78,12 +78,12 @@ class InclusionValidationTest < ActiveModel::TestCase def test_validates_inclusion_of_with_lambda Topic.validates_inclusion_of :title, :in => lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } - p = Topic.new - p.title = "wasabi" - p.author_name = "sikachu" - assert p.invalid? + t = Topic.new + t.title = "wasabi" + t.author_name = "sikachu" + assert t.invalid? - p.title = "elephant" - assert p.valid? + t.title = "elephant" + assert t.valid? end end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 44048a9c1d..113bfd6337 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -260,74 +260,64 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, :minimum => 5 - t = Topic.new("title" => "一二三四五", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二三四五", "content" => "whatever") + assert t.valid? - t.title = "一二三四" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] - end + t.title = "一二三四" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is too short (minimum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_maximum_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, :maximum => 5 - t = Topic.new("title" => "一二三四五", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二三四五", "content" => "whatever") + assert t.valid? - t.title = "一二34五六" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"] - end + t.title = "一二34五六" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is too long (maximum is 5 characters)"], t.errors["title"] end def test_validates_length_of_using_within_utf8 - with_kcode('UTF8') do - Topic.validates_length_of(:title, :content, :within => 3..5) - - t = Topic.new("title" => "一二", "content" => "12三四五六七") - assert t.invalid? - assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title] - assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content] - t.title = "一二三" - t.content = "12三" - assert t.valid? - end + Topic.validates_length_of(:title, :content, :within => 3..5) + + t = Topic.new("title" => "一二", "content" => "12三四五六七") + assert t.invalid? + assert_equal ["is too short (minimum is 3 characters)"], t.errors[:title] + assert_equal ["is too long (maximum is 5 characters)"], t.errors[:content] + t.title = "一二三" + t.content = "12三" + assert t.valid? end def test_optionally_validates_length_of_using_within_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, :within => 3..5, :allow_nil => true - t = Topic.new(:title => "一二三四五") - assert t.valid?, t.errors.inspect + t = Topic.new(:title => "一二三四五") + assert t.valid?, t.errors.inspect - t = Topic.new(:title => "一二三") - assert t.valid?, t.errors.inspect + t = Topic.new(:title => "一二三") + assert t.valid?, t.errors.inspect - t.title = nil - assert t.valid?, t.errors.inspect - end + t.title = nil + assert t.valid?, t.errors.inspect end def test_validates_length_of_using_is_utf8 - with_kcode('UTF8') do - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, :is => 5 - t = Topic.new("title" => "一二345", "content" => "whatever") - assert t.valid? + t = Topic.new("title" => "一二345", "content" => "whatever") + assert t.valid? - t.title = "一二345六" - assert t.invalid? - assert t.errors[:title].any? - assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] - end + t.title = "一二345六" + assert t.invalid? + assert t.errors[:title].any? + assert_equal ["is the wrong length (should be 5 characters)"], t.errors["title"] end def test_validates_length_of_with_block @@ -367,4 +357,22 @@ class LengthValidationTest < ActiveModel::TestCase ensure Person.reset_callbacks(:validate) end + + def test_validates_length_of_for_infinite_maxima + Topic.validates_length_of(:title, :within => 5..Float::INFINITY) + + t = Topic.new("title" => "1234") + assert t.invalid? + assert t.errors[:title].any? + + t.title = "12345" + assert t.valid? + + Topic.validates_length_of(:author_name, :maximum => Float::INFINITY) + + assert t.valid? + + t.author_name = "A very long author name that should still be valid." * 100 + assert t.valid? + end end diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 08f6169ca5..6742a4bab0 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -106,6 +106,13 @@ class NumericalityValidationTest < ActiveModel::TestCase valid!([2]) end + def test_validates_numericality_with_other_than + Topic.validates_numericality_of :approved, :other_than => 0 + + invalid!([0, 0.0]) + valid!([-1, 42]) + end + def test_validates_numericality_with_proc Topic.send(:define_method, :min_approved, lambda { 5 }) Topic.validates_numericality_of :approved, :greater_than_or_equal_to => Proc.new {|topic| topic.min_approved } diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index 779f6c8448..575154ffbd 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -16,6 +16,12 @@ class ValidatesTest < ActiveModel::TestCase PersonWithValidator.reset_callbacks(:validate) end + def test_validates_with_messages_empty + Person.validates :title, :presence => {:message => "" } + person = Person.new + assert !person.valid?, 'person should not be valid.' + end + def test_validates_with_built_in_validation Person.validates :title, :numericality => true person = Person.new diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 2f4376bd41..a716d0896e 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -58,8 +58,7 @@ class ValidationsTest < ActiveModel::TestCase r = Reply.new r.valid? - errors = [] - r.errors.each {|attr, messages| errors << [attr.to_s, messages] } + errors = r.errors.collect {|attr, messages| [attr.to_s, messages]} assert errors.include?(["title", "is Empty"]) assert errors.include?(["content", "is Empty"]) @@ -181,7 +180,7 @@ class ValidationsTest < ActiveModel::TestCase assert_match %r{<error>Title can't be blank</error>}, xml assert_match %r{<error>Content can't be blank</error>}, xml - hash = ActiveSupport::OrderedHash.new + hash = {} hash[:title] = ["can't be blank"] hash[:content] = ["can't be blank"] assert_equal t.errors.to_json, hash.to_json @@ -311,7 +310,7 @@ class ValidationsTest < ActiveModel::TestCase end def test_strict_validation_particular_validator - Topic.validates :title, :presence => {:strict => true} + Topic.validates :title, :presence => { :strict => true } assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end @@ -330,4 +329,19 @@ class ValidationsTest < ActiveModel::TestCase Topic.new.valid? end end + + def test_strict_validation_error_message + Topic.validates :title, :strict => true, :presence => true + + exception = assert_raises(ActiveModel::StrictValidationFailed) do + Topic.new.valid? + end + assert_equal "Title can't be blank", exception.message + end + + def test_does_not_modify_options_argument + options = { :presence => true } + Topic.validates :title, options + assert_equal({ :presence => true }, options) + end end diff --git a/activemodel/test/models/blog_post.rb b/activemodel/test/models/blog_post.rb index d289177259..46eba857df 100644 --- a/activemodel/test/models/blog_post.rb +++ b/activemodel/test/models/blog_post.rb @@ -1,10 +1,6 @@ module Blog - def self._railtie - Object.new - end - - def self.table_name_prefix - "blog_" + def self.use_relative_model_naming? + true end class Post |