diff options
Diffstat (limited to 'activemodel')
80 files changed, 1989 insertions, 1392 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 09e6ede064..b86e988841 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,123 +1 @@ -## Rails 4.0.0 (unreleased) ## - -* Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the - absence of attributes. - - class Person - include ActiveModel::Validations - - attr_accessor :first_name - validates_absence_of :first_name - end - - person = Person.new - person.first_name = "John" - person.valid? - # => false - person.errors.messages - # => {:first_name=>["must be blank"]} - - *Roberto Vasquez Angel* - -* `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!` - - *Renato Mascarenhas* - -* Observers was extracted from Active Model as `rails-observers` gem. - - *Rafael Mendonça França* - -* Specify type of singular association during serialization *Steve Klabnik* - -* Fixed length validator to correctly handle nil values. Fixes #7180. - - *Michal Zima* - -* Removed dispensable `require` statements. Make sure to require `active_model` before requiring - individual parts of the framework. - - *Yves Senn* - -* Use BCrypt's `MIN_COST` in the test environment for speedier tests when using `has_secure_pasword`. - - *Brian Cardarella + Jeremy Kemper + Trevor Turk* - -* Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to - protect attributes from mass assignment when non-permitted attributes are passed. - - *DHH + Guillermo Iguaran* - -* `ActiveModel::MassAssignmentSecurity` has been extracted from Active Model and the - `protected_attributes` gem should be added to Gemfile in order to use - `attr_accessible` and `attr_protected` macros in your models. - - *Guillermo Iguaran* - -* Due to a change in builder, nil values and empty strings now generates - closed tags, so instead of this: - - <pseudonyms nil=\"true\"></pseudonyms> - - It generates this: - - <pseudonyms nil=\"true\"/> - - *Carlos Antonio da Silva* - -* Changed inclusion and exclusion validators to accept a symbol for `:in` option. - - This allows to use dynamic inclusion/exclusion values using methods, besides the current lambda/proc support. - - *Gabriel Sobrinho* - -* `AM::Validation#validates` ability to pass custom exception to `:strict` option. - - *Bogdan Gusiev* - -* Changed `ActiveModel::Serializers::Xml::Serializer#add_associations` to by default - propagate `:skip_types, :dasherize, :camelize` keys to included associations. - It can be overriden on each association by explicitly specifying the option on one - or more associations - - *Anthony Alberto* - -* Changed `AM::Serializers::JSON.include_root_in_json' default value to false. - Now, AM Serializers and AR objects have the same default behaviour. Fixes #6578. - - class User < ActiveRecord::Base; end - - class Person - include ActiveModel::Model - include ActiveModel::AttributeMethods - include ActiveModel::Serializers::JSON - - attr_accessor :name, :age - - def attributes - instance_values - end - end - - user.as_json - => {"id"=>1, "name"=>"Konata Izumi", "age"=>16, "awesome"=>true} - # root is not included - - person.as_json - => {"name"=>"Francesco", "age"=>22} - # root is not included - - *Francesco Rodriguez* - -* Passing false hash values to `validates` will no longer enable the corresponding validators *Steve Purcell* - -* `ConfirmationValidator` error messages will attach to `:#{attribute}_confirmation` instead of `attribute` *Brian Cardarella* - -* Added ActiveModel::Model, a mixin to make Ruby objects work with AP out of box *Guillermo Iguaran* - -* `AM::Errors#to_json`: support `:full_messages` parameter *Bogdan Gusiev* - -* Trim down Active Model API by removing `valid?` and `errors.full_messages` *José Valim* - -* When `^` or `$` are used in the regular expression provided to `validates_format_of` and the :multiline option is not set to true, an exception will be raised. This is to prevent security vulnerabilities when using `validates_format_of`. The problem is described in detail in the Rails security guide *Jan Berdajs + Egor Homakov* - -Please check [3-2-stable](https://github.com/rails/rails/blob/3-2-stable/activemodel/CHANGELOG.md) for previous changes. +Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/MIT-LICENSE b/activemodel/MIT-LICENSE index 5c668d9624..3ec7a617cf 100644 --- a/activemodel/MIT-LICENSE +++ b/activemodel/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2013 David Heinemeier Hansson +Copyright (c) 2004-2015 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 1b1fe2fa2b..f6beff14e1 100644 --- a/activemodel/README.rdoc +++ b/activemodel/README.rdoc @@ -2,7 +2,7 @@ Active Model provides a known set of interfaces for usage in model classes. They allow for Action Pack helpers to interact with non-Active Record models, -for example. Active Model also helps building custom ORMs for use outside of +for example. Active Model also helps with building custom ORMs for use outside of the Rails framework. Prior to Rails 3.0, if a plugin or gem developer wanted to have an object @@ -24,8 +24,8 @@ to integrate with Action Pack out of the box: <tt>ActiveModel::Model</tt>. end person = Person.new(name: 'bob', age: '18') - person.name # => 'bob' - person.age # => '18' + person.name # => 'bob' + person.age # => '18' person.valid? # => true It includes model name introspections, conversions, translations and @@ -49,7 +49,8 @@ behavior out of the box: send("#{attr}=", nil) end end - + + person = Person.new person.clear_name person.clear_age @@ -78,16 +79,31 @@ behavior out of the box: class Person include ActiveModel::Dirty - attr_accessor :name + define_attribute_methods :name + + def name + @name + end + + def name=(val) + name_will_change! unless val == @name + @name = val + end + + def save + # do persistence work + changes_applied + end end person = Person.new - person.name # => nil - person.changed? # => false + person.name # => nil + person.changed? # => false person.name = 'bob' - person.changed? # => true - person.changed # => ['name'] - person.changes # => { 'name' => [nil, 'bob'] } + person.changed? # => true + person.changed # => ['name'] + person.changes # => { 'name' => [nil, 'bob'] } + person.save person.name = 'robert' person.save person.previous_changes # => {'name' => ['bob, 'robert']} @@ -109,16 +125,19 @@ behavior out of the box: attr_reader :errors def validate! - errors.add(:name, "can not be nil") if name.nil? + errors.add(:name, "cannot be nil") if name.nil? end def self.human_attribute_name(attr, options = {}) "Name" end end - + + person = Person.new + person.name = nil + person.validate! person.errors.full_messages - # => ["Name can not be nil"] + # => ["Name cannot be nil"] {Learn more}[link:classes/ActiveModel/Errors.html] @@ -128,7 +147,7 @@ behavior out of the box: extend ActiveModel::Naming end - NamedPerson.model_name # => "NamedPerson" + NamedPerson.model_name.name # => "NamedPerson" NamedPerson.model_name.human # => "Named person" {Learn more}[link:classes/ActiveModel/Naming.html] @@ -180,41 +199,41 @@ behavior out of the box: * Validation support - class Person - include ActiveModel::Validations + class Person + include ActiveModel::Validations - attr_accessor :first_name, :last_name + attr_accessor :first_name, :last_name - validates_each :first_name, :last_name do |record, attr, value| - record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z - end - end + validates_each :first_name, :last_name do |record, attr, value| + record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z + end + end - person = Person.new - person.first_name = 'zoolander' - person.valid? # => false + person = Person.new + person.first_name = 'zoolander' + person.valid? # => false {Learn more}[link:classes/ActiveModel/Validations.html] * Custom validators + + class HasNameValidator < ActiveModel::Validator + def validate(record) + record.errors[:name] = "must exist" if record.name.blank? + end + end - class ValidatorPerson - include ActiveModel::Validations - validates_with HasNameValidator - attr_accessor :name - end - - class HasNameValidator < ActiveModel::Validator - def validate(record) - record.errors[:name] = "must exist" if record.name.blank? - end - end + class ValidatorPerson + include ActiveModel::Validations + validates_with HasNameValidator + attr_accessor :name + end - p = ValidatorPerson.new - p.valid? # => false - p.errors.full_messages # => ["Name must exist"] - p.name = "Bob" - p.valid? # => true + p = ValidatorPerson.new + p.valid? # => false + p.errors.full_messages # => ["Name must exist"] + p.name = "Bob" + p.valid? # => true {Learn more}[link:classes/ActiveModel/Validator.html] @@ -243,6 +262,11 @@ API documentation is at * http://api.rubyonrails.org -Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here: +Bug reports can be filed for the Ruby on Rails project here: * https://github.com/rails/rails/issues + +Feature requests should be discussed on the rails-core mailing list here: + +* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core + diff --git a/activemodel/Rakefile b/activemodel/Rakefile index fc5aaf9f8f..c30a559ef5 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -9,18 +9,17 @@ Rake::TestTask.new do |t| t.test_files = Dir.glob("#{dir}/test/cases/**/*_test.rb").sort t.warning = true t.verbose = true + t.ruby_opts = ["--dev"] if defined?(JRUBY_VERSION) end namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("#{dir}/test/**/*_test.rb").all? do |file| - sh(ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) + sh(Gem.ruby, '-w', "-I#{dir}/lib", "-I#{dir}/test", file) end or raise "Failures" end end -require 'rake/packagetask' require 'rubygems/package_task' spec = eval(File.read("#{dir}/activemodel.gemspec")) @@ -29,7 +28,7 @@ Gem::PackageTask.new(spec) do |p| p.gem_spec = spec end -desc "Release to gemcutter" +desc "Release to rubygems" task :release => :package do require 'rake/gemcutter' Rake::Gemcutter::Tasks.new(spec).define diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 51655fe3da..3c6eb56296 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -5,9 +5,9 @@ Gem::Specification.new do |s| s.name = 'activemodel' s.version = version s.summary = 'A toolkit for building modeling frameworks (part of Rails).' - s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.' + s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.' - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.2.0' s.license = 'MIT' @@ -20,5 +20,5 @@ Gem::Specification.new do |s| s.add_dependency 'activesupport', version - s.add_dependency 'builder', '~> 3.1.0' + s.add_dependency 'builder', '~> 3.1' end diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb deleted file mode 100644 index a56ec4db39..0000000000 --- a/activemodel/examples/validations.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'active_model' - -class Person - include ActiveModel::Conversion - include ActiveModel::Validations - - validates_presence_of :name - - attr_accessor :name - - def initialize(attributes = {}) - @name = attributes[:name] - end - - def persist - @persisted = true - end - - def persisted? - @persisted - end -end - -person1 = Person.new -p person1.valid? # => false -p person1.errors.messages # => {:name=>["can't be blank"]} - -person2 = Person.new(:name => "matz") -p person2.valid? # => true diff --git a/activemodel/lib/active_model.rb b/activemodel/lib/active_model.rb index 3bd5531356..46d60db756 100644 --- a/activemodel/lib/active_model.rb +++ b/activemodel/lib/active_model.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2004-2013 David Heinemeier Hansson +# Copyright (c) 2004-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -37,7 +37,6 @@ module ActiveModel autoload :ForbiddenAttributesProtection autoload :Lint autoload :Model - autoload :DeprecatedMassAssignmentSecurity autoload :Name, 'active_model/naming' autoload :Naming autoload :SecurePassword @@ -49,6 +48,7 @@ module ActiveModel eager_autoload do autoload :Errors + autoload :StrictValidationFailed, 'active_model/errors' end module Serializers @@ -60,9 +60,9 @@ module ActiveModel end end - def eager_load! + def self.eager_load! super - ActiveModel::Serializer.eager_load! + ActiveModel::Serializers.eager_load! end end diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 6d11c0fbdc..96be551264 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -1,4 +1,5 @@ require 'thread_safe' +require 'mutex_m' module ActiveModel # Raised when an attribute is not defined. @@ -12,19 +13,23 @@ module ActiveModel # # => ActiveModel::MissingAttributeError: missing attribute: user_id class MissingAttributeError < NoMethodError end - # == Active \Model Attribute Methods + + # == Active \Model \Attribute \Methods # - # <tt>ActiveModel::AttributeMethods</tt> provides a way to add prefixes and - # suffixes to your methods as well as handling the creation of Active Record - # like class methods such as +table_name+. + # Provides a way to add prefixes and suffixes to your methods as + # well as handling the creation of <tt>ActiveRecord::Base</tt>-like + # class methods such as +table_name+. # - # The requirements to implement ActiveModel::AttributeMethods are to: + # The requirements to implement <tt>ActiveModel::AttributeMethods</tt> are to: # - # * <tt>include ActiveModel::AttributeMethods</tt> in your object. - # * Call each Attribute Method module method you want to add, such as - # +attribute_method_suffix+ or +attribute_method_prefix+. + # * <tt>include ActiveModel::AttributeMethods</tt> in your class. + # * Call each of its method you want to add, such as +attribute_method_suffix+ + # or +attribute_method_prefix+. # * Call +define_attribute_methods+ after the other methods are called. # * Define the various generic +_attribute+ methods that you have declared. + # * Define an +attributes+ method which returns a hash with each + # attribute name in your model as hash key and the attribute value as hash value. + # Hash keys must be strings. # # A minimal implementation could be: # @@ -38,6 +43,10 @@ module ActiveModel # # attr_accessor :name # + # def attributes + # { 'name' => @name } + # end + # # private # # def attribute_contrived?(attr) @@ -52,13 +61,6 @@ module ActiveModel # send("#{attr}=", 'Default Name') # end # end - # - # Note that whenever you include ActiveModel::AttributeMethods in your class, - # it requires you to implement an +attributes+ method which returns a hash - # with each attribute name in your model as hash key and the attribute value as - # hash value. - # - # Hash keys must be strings. module AttributeMethods extend ActiveSupport::Concern @@ -166,20 +168,19 @@ module ActiveModel # private # # def reset_attribute_to_default!(attr) - # ... + # send("#{attr}=", 'Default Name') # end # end # # person = Person.new # person.name # => 'Gem' # person.reset_name_to_default! - # person.name # => 'Gemma' + # person.name # => 'Default Name' def attribute_method_affix(*affixes) self.attribute_method_matchers += affixes.map! { |affix| AttributeMethodMatcher.new prefix: affix[:prefix], suffix: affix[:suffix] } undefine_attribute_methods end - # Allows you to make aliases for attributes. # # class Person @@ -213,6 +214,16 @@ module ActiveModel end end + # Is +new_name+ an alias? + def attribute_alias?(new_name) + attribute_aliases.key? new_name.to_s + end + + # Returns the original name for the alias +name+ + def attribute_alias(name) + attribute_aliases[name.to_s] + end + # Declares the attributes that should be prefixed and suffixed by # ActiveModel::AttributeMethods. # @@ -234,7 +245,7 @@ module ActiveModel # private # # def clear_attribute(attr) - # ... + # send("#{attr}=", nil) # end # end def define_attribute_methods(*attr_names) @@ -317,9 +328,10 @@ module ActiveModel attribute_method_matchers_cache.clear end - # Returns true if the attribute methods defined have been generated. def generated_attribute_methods #:nodoc: - @generated_attribute_methods ||= Module.new.tap { |mod| include mod } + @generated_attribute_methods ||= Module.new { + extend Mutex_m + }.tap { |mod| include mod } end protected @@ -332,23 +344,21 @@ module ActiveModel # invoked often in a typical rails, both of which invoke the method # +match_attribute_method?+. The latter method iterates through an # array doing regular expression matches, which results in a lot of - # object creations. Most of the times it returns a +nil+ match. As the + # object creations. Most of the time it returns a +nil+ match. As the # match result is always the same given a +method_name+, this cache is # 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 #:nodoc: - @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(:initial_capacity => 4) + @attribute_method_matchers_cache ||= ThreadSafe::Cache.new(initial_capacity: 4) end - def attribute_method_matcher(method_name) #:nodoc: + def attribute_method_matchers_matching(method_name) #:nodoc: attribute_method_matchers_cache.compute_if_absent(method_name) do # Must try to match prefixes/suffixes first, or else the matcher with no prefix/suffix # will match every time. matchers = attribute_method_matchers.partition(&:plain?).reverse.flatten(1) - match = nil - matchers.detect { |method| match = method.match(method_name) } - match + matchers.map { |method| method.match(method_name) }.compact end end @@ -383,14 +393,6 @@ module ActiveModel AttributeMethodMatch = Struct.new(:target, :attr_name, :method_name) def initialize(options = {}) - if options[:prefix] == '' || options[:suffix] == '' - message = "Specifying an empty prefix/suffix for an attribute method is no longer " \ - "necessary. If the un-prefixed/suffixed version of the method has not been " \ - "defined when `define_attribute_methods` is called, it will be defined " \ - "automatically." - ActiveSupport::Deprecation.warn message - end - @prefix, @suffix = options.fetch(:prefix, ''), options.fetch(:suffix, '') @regex = /^(?:#{Regexp.escape(@prefix)})(.*)(?:#{Regexp.escape(@suffix)})$/ @method_missing_target = "#{@prefix}attribute#{@suffix}" @@ -413,17 +415,16 @@ module ActiveModel end end - # Allows access to the object attributes, which are held in the - # <tt>@attributes</tt> hash, as though they were first-class methods. So a - # Person class with a name attribute can use Person#name and Person#name= - # and never directly use the attributes hash -- except for multiple assigns - # with ActiveRecord#attributes=. A Milestone class can also ask - # Milestone#completed? to test that the completed attribute is not +nil+ - # or 0. + # Allows access to the object attributes, which are held in the hash + # returned by <tt>attributes</tt>, as though they were first-class + # methods. So a +Person+ class with a +name+ attribute can for example use + # <tt>Person#name</tt> and <tt>Person#name=</tt> and never directly use + # the attributes hash -- except for multiple assigns with + # <tt>ActiveRecord::Base#attributes=</tt>. # - # It's also possible to instantiate related objects, so a Client class - # belonging to the clients table with a +master_id+ foreign key can - # instantiate master through Client#master. + # It's also possible to instantiate related objects, so a <tt>Client</tt> + # class belonging to the +clients+ table with a +master_id+ foreign key + # can instantiate master through <tt>Client#master</tt>. def method_missing(method, *args, &block) if respond_to_without_attributes?(method, true) super @@ -433,17 +434,17 @@ module ActiveModel end end - # attribute_missing is like method_missing, but for attributes. When method_missing is - # called we check to see if there is a matching attribute method. If so, we call - # attribute_missing to dispatch the attribute. This method can be overloaded to - # customize the behavior. + # +attribute_missing+ is like +method_missing+, but for attributes. When + # +method_missing+ is called we check to see if there is a matching + # attribute method. If so, we tell +attribute_missing+ to dispatch the + # attribute. This method can be overloaded to customize the behavior. def attribute_missing(match, *args, &block) __send__(match.target, match.attr_name, *args, &block) end - # A Person object with a name attribute can ask <tt>person.respond_to?(:name)</tt>, - # <tt>person.respond_to?(:name=)</tt>, and <tt>person.respond_to?(:name?)</tt> - # which will all return +true+. + # A +Person+ instance with a +name+ attribute can ask + # <tt>person.respond_to?(:name)</tt>, <tt>person.respond_to?(:name=)</tt>, + # and <tt>person.respond_to?(:name?)</tt> which will all return +true+. alias :respond_to_without_attributes? :respond_to? def respond_to?(method, include_private_methods = false) if super @@ -466,8 +467,8 @@ module ActiveModel # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) - match = self.class.send(:attribute_method_matcher, method_name) - match if match && attribute_method?(match.attr_name) + matches = self.class.send(:attribute_method_matchers_matching, method_name) + matches.detect { |match| attribute_method?(match.attr_name) } end def missing_attribute(attr_name, stack) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index c52e4947ae..b3d70dc515 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/array/extract_options' + module ActiveModel # == Active \Model \Callbacks # @@ -28,7 +30,7 @@ module ActiveModel # end # # Then in your class, you can use the +before_create+, +after_create+ and - # +around_create+ methods, just as you would in an Active Record module. + # +around_create+ methods, just as you would in an Active Record model. # # before_create :action_before_create # @@ -95,13 +97,16 @@ module ActiveModel # # obj is the MyModel instance that the callback is being called on # end # end + # + # NOTE: +method_name+ passed to `define_model_callbacks` must not end with + # `!`, `?` or `=`. def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - :terminator => "result == false", - :skip_after_callbacks_if_terminated => true, - :scope => [:kind, :name], - :only => [:before, :around, :after] + terminator: ->(_,result) { result == false }, + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name], + only: [:before, :around, :after] }.merge!(options) types = Array(options.delete(:only)) @@ -118,30 +123,27 @@ module ActiveModel private def _define_before_model_callback(klass, callback) #:nodoc: - klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1 - def self.before_#{callback}(*args, &block) - set_callback(:#{callback}, :before, *args, &block) - end - CALLBACK + klass.define_singleton_method("before_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :before, *args, &block) + end end def _define_around_model_callback(klass, callback) #:nodoc: - klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1 - def self.around_#{callback}(*args, &block) - set_callback(:#{callback}, :around, *args, &block) - end - CALLBACK + klass.define_singleton_method("around_#{callback}") do |*args, &block| + set_callback(:"#{callback}", :around, *args, &block) + end end def _define_after_model_callback(klass, callback) #:nodoc: - klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1 - def self.after_#{callback}(*args, &block) - options = args.extract_options! - options[:prepend] = true - options[:if] = Array(options[:if]) << "value != false" - set_callback(:#{callback}, :after, *(args << options), &block) - end - CALLBACK + klass.define_singleton_method("after_#{callback}") do |*args, &block| + options = args.extract_options! + options[:prepend] = true + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional + set_callback(:"#{callback}", :after, *(args << options), &block) + end end end end diff --git a/activemodel/lib/active_model/conversion.rb b/activemodel/lib/active_model/conversion.rb index 1f5d23dd8e..9c9b6f4a77 100644 --- a/activemodel/lib/active_model/conversion.rb +++ b/activemodel/lib/active_model/conversion.rb @@ -1,5 +1,5 @@ module ActiveModel - # == Active \Model Conversions + # == Active \Model \Conversion # # Handles default conversions: to_model, to_key, to_param, and to_partial_path. # @@ -40,13 +40,15 @@ module ActiveModel self end - # Returns an Enumerable of all key attributes if any is set, regardless if - # the object is persisted or not. If there no key attributes, returns +nil+. + # Returns an Array of all key attributes if any is set, regardless if + # the object is persisted or not. Returns +nil+ if there are no key attributes. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Conversion + # attr_accessor :id # end # - # person = Person.create + # person = Person.create(id: 1) # person.to_key # => [1] def to_key key = respond_to?(:id) && id @@ -56,13 +58,18 @@ module ActiveModel # Returns a +string+ representing the object's key suitable for use in URLs, # or +nil+ if <tt>persisted?</tt> is +false+. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Conversion + # attr_accessor :id + # def persisted? + # true + # end # end # - # person = Person.create + # person = Person.create(id: 1) # person.to_param # => "1" def to_param - persisted? ? to_key.join('-') : nil + (persisted? && key = to_key) ? key.join('-') : nil end # Returns a +string+ identifying the path associated with the object. @@ -83,8 +90,8 @@ module ActiveModel # internal method and should not be accessed directly. def _to_partial_path #:nodoc: @_to_partial_path ||= begin - element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(self)) - collection = ActiveSupport::Inflector.tableize(self) + element = ActiveSupport::Inflector.underscore(ActiveSupport::Inflector.demodulize(name)) + collection = ActiveSupport::Inflector.tableize(name) "#{collection}/#{element}".freeze end end diff --git a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb b/activemodel/lib/active_model/deprecated_mass_assignment_security.rb deleted file mode 100644 index 1f409c87b9..0000000000 --- a/activemodel/lib/active_model/deprecated_mass_assignment_security.rb +++ /dev/null @@ -1,21 +0,0 @@ -module ActiveModel - module DeprecatedMassAssignmentSecurity # :nodoc: - extend ActiveSupport::Concern - - module ClassMethods # :nodoc: - def attr_protected(*args) - raise "`attr_protected` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params" \ - "(strong_parameters) or add `protected_attributes` to your " \ - "Gemfile to use old one." - end - - def attr_accessible(*args) - raise "`attr_accessible` is extracted out of Rails into a gem. " \ - "Please use new recommended protection model for params" \ - "(strong_parameters) or add `protected_attributes` to your " \ - "Gemfile to use old one." - end - end - end -end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index 6e67cd2285..337b61c55c 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -1,5 +1,6 @@ require 'active_support/hash_with_indifferent_access' require 'active_support/core_ext/object/duplicable' +require 'active_support/core_ext/string/filters' module ActiveModel # == Active \Model \Dirty @@ -12,15 +13,12 @@ module ActiveModel # * <tt>include ActiveModel::Dirty</tt> in your object. # * Call <tt>define_attribute_methods</tt> passing each method you want to # track. - # * Call <tt>attr_name_will_change!</tt> before each change to the tracked + # * Call <tt>[attr_name]_will_change!</tt> before each change to the tracked # attribute. - # - # If you wish to also track previous changes on save or update, you need to - # add: - # - # @previously_changed = changes - # - # inside of your save or update method. + # * Call <tt>changes_applied</tt> after the changes are persisted. + # * Call <tt>clear_changes_information</tt> when you want to reset the changes + # information. + # * Call <tt>restore_attributes</tt> when you want to restore previous data. # # A minimal implementation could be: # @@ -39,14 +37,25 @@ module ActiveModel # end # # def save - # @previously_changed = changes - # @changed_attributes.clear + # # do persistence work + # + # changes_applied + # end + # + # def reload! + # # get the values from the persistence layer + # + # clear_changes_information + # end + # + # def rollback! + # restore_attributes # end # end # # A newly instantiated object is unchanged: # - # person = Person.find_by_name('Uncle Bob') + # person = Person.find_by(name: 'Uncle Bob') # person.changed? # => false # # Change the name: @@ -54,6 +63,7 @@ module ActiveModel # person.name = 'Bob' # person.changed? # => true # person.name_changed? # => true + # person.name_changed?(from: "Uncle Bob", to: "Bob") # => true # person.name_was # => "Uncle Bob" # person.name_change # => ["Uncle Bob", "Bob"] # person.name = 'Bill' @@ -65,6 +75,19 @@ module ActiveModel # person.changed? # => false # person.name_changed? # => false # + # Reset the changes: + # + # person.previous_changes # => {"name" => ["Uncle Bob", "Bill"]} + # person.reload! + # person.previous_changes # => {} + # + # Rollback the changes: + # + # person.name = "Uncle Bob" + # person.rollback! + # person.name # => "Bill" + # person.name_changed? # => false + # # Assigning the same value leaves the attribute unchanged: # # person.name = 'Bill' @@ -77,9 +100,11 @@ module ActiveModel # person.changed # => ["name"] # person.changes # => {"name" => ["Bill", "Bob"]} # - # If an attribute is modified in-place then make use of <tt>[attribute_name]_will_change!</tt> - # to mark that the attribute is changing. Otherwise ActiveModel can't track - # changes to in-place attributes. + # If an attribute is modified in-place then make use of + # +[attribute_name]_will_change!+ to mark that the attribute is changing. + # Otherwise Active Model can't track changes to in-place attributes. Note + # that Active Record can detect in-place modifications automatically. You do + # not need to call +[attribute_name]_will_change!+ on Active Record models. # # person.name_will_change! # person.name_change # => ["Bill", "Bill"] @@ -91,7 +116,8 @@ module ActiveModel included do attribute_method_suffix '_changed?', '_change', '_will_change!', '_was' - attribute_method_affix :prefix => 'reset_', :suffix => '!' + attribute_method_affix prefix: 'reset_', suffix: '!' + attribute_method_affix prefix: 'restore_', suffix: '!' end # Returns +true+ if any attribute have unsaved changes, +false+ otherwise. @@ -129,7 +155,7 @@ module ActiveModel # person.save # person.previous_changes # => {"name" => ["bob", "robert"]} def previous_changes - @previously_changed + @previously_changed ||= ActiveSupport::HashWithIndifferentAccess.new end # Returns a hash of the attributes with unsaved changes indicating their original @@ -139,14 +165,52 @@ module ActiveModel # person.name = 'robert' # person.changed_attributes # => {"name" => "bob"} def changed_attributes - @changed_attributes ||= {} + @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new + end + + # Handle <tt>*_changed?</tt> for +method_missing+. + def attribute_changed?(attr, options = {}) #:nodoc: + result = changes_include?(attr) + result &&= options[:to] == __send__(attr) if options.key?(:to) + result &&= options[:from] == changed_attributes[attr] if options.key?(:from) + result + end + + # Handle <tt>*_was</tt> for +method_missing+. + def attribute_was(attr) # :nodoc: + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + # Restore all previous data of the provided attributes. + def restore_attributes(attributes = changed) + attributes.each { |attr| restore_attribute! attr } end private - # Handle <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr) - changed_attributes.include?(attr) + def changes_include?(attr_name) + attributes_changed_by_setter.include?(attr_name) + end + + # Removes current changes and makes them accessible through +previous_changes+. + def changes_applied # :doc: + @previously_changed = changes + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new + end + + # Clear all dirty data: current changes and previous changes. + def clear_changes_information # :doc: + @previously_changed = ActiveSupport::HashWithIndifferentAccess.new + @changed_attributes = ActiveSupport::HashWithIndifferentAccess.new + end + + def reset_changes + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `#reset_changes` is deprecated and will be removed on Rails 5. + Please use `#clear_changes_information` instead. + MSG + + clear_changes_information end # Handle <tt>*_change</tt> for +method_missing+. @@ -154,11 +218,6 @@ module ActiveModel [changed_attributes[attr], __send__(attr)] if attribute_changed?(attr) end - # Handle <tt>*_was</tt> for +method_missing+. - def attribute_was(attr) - attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) - end - # Handle <tt>*_will_change!</tt> for +method_missing+. def attribute_will_change!(attr) return if attribute_changed?(attr) @@ -169,15 +228,39 @@ module ActiveModel rescue TypeError, NoMethodError end - changed_attributes[attr] = value + set_attribute_was(attr, value) end # Handle <tt>reset_*!</tt> for +method_missing+. def reset_attribute!(attr) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `#reset_#{attr}!` is deprecated and will be removed on Rails 5. + Please use `#restore_#{attr}!` instead. + MSG + + restore_attribute!(attr) + end + + # Handle <tt>restore_*!</tt> for +method_missing+. + def restore_attribute!(attr) if attribute_changed?(attr) __send__("#{attr}=", changed_attributes[attr]) - changed_attributes.delete(attr) + clear_attribute_changes([attr]) end end + + # This is necessary because `changed_attributes` might be overridden in + # other implemntations (e.g. in `ActiveRecord`) + alias_method :attributes_changed_by_setter, :changed_attributes # :nodoc: + + # Force an attribute to have a particular "before" value + def set_attribute_was(attr, old_value) + attributes_changed_by_setter[attr] = old_value + end + + # Remove changes information for the provided attributes. + def clear_attribute_changes(attributes) # :doc: + attributes_changed_by_setter.except!(*attributes) + end end end diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index 963e52bff3..55687cb3c7 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -7,12 +7,11 @@ module ActiveModel # == Active \Model \Errors # # Provides a modified +Hash+ that you can include in your object - # for handling error messages and interacting with Action Pack helpers. + # for handling error messages and interacting with Action View helpers. # # A minimal implementation could be: # # class Person - # # # Required dependency for ActiveModel::Errors # extend ActiveModel::Naming # @@ -24,7 +23,7 @@ module ActiveModel # attr_reader :errors # # def validate! - # errors.add(:name, "can not be nil") if name == nil + # errors.add(:name, "cannot be nil") if name.nil? # end # # # The following methods are needed to be minimally implemented @@ -40,7 +39,6 @@ module ActiveModel # def Person.lookup_ancestors # [self] # end - # # end # # The last three methods are required in your object for Errors to be @@ -52,9 +50,9 @@ module ActiveModel # # The above allows you to do: # - # p = Person.new - # person.validate! # => ["can not be nil"] - # person.errors.full_messages # => ["name can not be nil"] + # person = Person.new + # person.validate! # => ["cannot be nil"] + # person.errors.full_messages # => ["name cannot be nil"] # # etc.. class Errors include Enumerable @@ -82,7 +80,7 @@ module ActiveModel # Clear the error messages. # - # person.errors.full_messages # => ["name can not be nil"] + # person.errors.full_messages # => ["name cannot be nil"] # person.errors.clear # person.errors.full_messages # => [] def clear @@ -92,19 +90,21 @@ module ActiveModel # Returns +true+ if the error messages include an error for the given key # +attribute+, +false+ otherwise. # - # person.errors.messages # => {:name=>["can not be nil"]} + # person.errors.messages # => {:name=>["cannot be nil"]} # person.errors.include?(:name) # => true # person.errors.include?(:age) # => false def include?(attribute) - (v = messages[attribute]) && v.any? + messages[attribute].present? end # aliases include? alias :has_key? :include? + # aliases include? + alias :key? :include? # Get messages for +key+. # - # person.errors.messages # => {:name=>["can not be nil"]} - # person.errors.get(:name) # => ["can not be nil"] + # person.errors.messages # => {:name=>["cannot be nil"]} + # person.errors.get(:name) # => ["cannot be nil"] # person.errors.get(:age) # => nil def get(key) messages[key] @@ -112,7 +112,7 @@ module ActiveModel # Set messages for +key+ to +value+. # - # person.errors.get(:name) # => ["can not be nil"] + # person.errors.get(:name) # => ["cannot be nil"] # person.errors.set(:name, ["can't be nil"]) # person.errors.get(:name) # => ["can't be nil"] def set(key, value) @@ -121,8 +121,8 @@ module ActiveModel # Delete messages for +key+. Returns the deleted messages. # - # person.errors.get(:name) # => ["can not be nil"] - # person.errors.delete(:name) # => ["can not be nil"] + # person.errors.get(:name) # => ["cannot be nil"] + # person.errors.delete(:name) # => ["cannot be nil"] # person.errors.get(:name) # => nil def delete(key) messages.delete(key) @@ -131,8 +131,8 @@ module ActiveModel # When passed a symbol or a name of a method, returns an array of errors # for the method. # - # person.errors[:name] # => ["can not be nil"] - # person.errors['name'] # => ["can not be nil"] + # person.errors[:name] # => ["cannot be nil"] + # person.errors['name'] # => ["cannot be nil"] def [](attribute) get(attribute.to_sym) || set(attribute.to_sym, []) end @@ -177,15 +177,15 @@ module ActiveModel # Returns all message values. # - # person.errors.messages # => {:name=>["can not be nil", "must be specified"]} - # person.errors.values # => [["can not be nil", "must be specified"]] + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} + # person.errors.values # => [["cannot be nil", "must be specified"]] def values messages.values end # Returns all message keys. # - # person.errors.messages # => {:name=>["can not be nil", "must be specified"]} + # person.errors.messages # => {:name=>["cannot be nil", "must be specified"]} # person.errors.keys # => [:name] def keys messages.keys @@ -213,7 +213,7 @@ module ActiveModel # Returns +true+ if no errors are found, +false+ otherwise. # If the error message is a string it can be empty. # - # person.errors.full_messages # => ["name can not be nil"] + # person.errors.full_messages # => ["name cannot be nil"] # person.errors.empty? # => false def empty? all? { |k, v| v && v.empty? && !v.is_a?(String) } @@ -233,15 +233,15 @@ module ActiveModel # # <error>name must be specified</error> # # </errors> def to_xml(options={}) - to_a.to_xml({ :root => "errors", :skip_types => true }.merge!(options)) + to_a.to_xml({ root: "errors", skip_types: true }.merge!(options)) end # Returns a Hash that can be used as the JSON representation for this # object. You can pass the <tt>:full_messages</tt> option. This determines # if the json object should contain full messages or not (false by default). # - # person.as_json # => {:name=>["can not be nil"]} - # person.as_json(full_messages: true) # => {:name=>["name can not be nil"]} + # person.errors.as_json # => {:name=>["cannot be nil"]} + # person.errors.as_json(full_messages: true) # => {:name=>["name cannot be nil"]} def as_json(options=nil) to_hash(options && options[:full_messages]) end @@ -249,15 +249,13 @@ module ActiveModel # Returns a Hash of attributes with their error messages. If +full_messages+ # is +true+, it will contain full messages (see +full_message+). # - # person.to_hash # => {:name=>["can not be nil"]} - # person.to_hash(true) # => {:name=>["name can not be nil"]} + # person.errors.to_hash # => {:name=>["cannot be nil"]} + # person.errors.to_hash(true) # => {:name=>["name cannot be nil"]} def to_hash(full_messages = false) if full_messages - messages = {} - self.messages.each do |attribute, array| + self.messages.each_with_object({}) do |(attribute, array), messages| messages[attribute] = array.map { |message| full_message(attribute, message) } end - messages else self.messages.dup end @@ -281,17 +279,24 @@ module ActiveModel # If +message+ is a proc, it will be called, allowing for things like # <tt>Time.now</tt> to be used within an error. # - # If the <tt>:strict</tt> option is set to true will raise + # If the <tt>:strict</tt> option is set to +true+, it will raise # ActiveModel::StrictValidationFailed instead of adding the error. # <tt>:strict</tt> option can also be set to any other exception. # - # person.errors.add(:name, nil, strict: true) + # person.errors.add(:name, :invalid, strict: true) # # => ActiveModel::StrictValidationFailed: name is invalid - # person.errors.add(:name, nil, strict: NameIsInvalid) + # person.errors.add(:name, :invalid, strict: NameIsInvalid) # # => NameIsInvalid: name is invalid # # person.errors.messages # => {} - def add(attribute, message = nil, options = {}) + # + # +attribute+ should be set to <tt>:base</tt> if the error is not + # directly associated with a single attribute. + # + # person.errors.add(:base, "either name or email must be present") + # person.errors.messages + # # => {:base=>["either name or email must be present"]} + def add(attribute, message = :invalid, options = {}) message = normalize_message(attribute, message, options) if exception = options[:strict] exception = ActiveModel::StrictValidationFailed if exception == true @@ -333,7 +338,7 @@ module ActiveModel # # person.errors.add :name, :blank # person.errors.added? :name, :blank # => true - def added?(attribute, message = nil, options = {}) + def added?(attribute, message = :invalid, options = {}) message = normalize_message(attribute, message, options) self[attribute].include? message end @@ -352,17 +357,31 @@ module ActiveModel map { |attribute, message| full_message(attribute, message) } end + # Returns all the full error messages for a given attribute in an array. + # + # class Person + # validates_presence_of :name, :email + # validates_length_of :name, in: 5..30 + # end + # + # person = Person.create() + # person.errors.full_messages_for(:name) + # # => ["Name is too short (minimum is 5 characters)", "Name can't be blank"] + def full_messages_for(attribute) + (get(attribute) || []).map { |message| full_message(attribute, message) } + end + # Returns a full message for a given attribute. # # person.errors.full_message(:name, 'is invalid') # => "Name is invalid" def full_message(attribute, message) return message if attribute == :base attr_name = attribute.to_s.tr('.', '_').humanize - attr_name = @base.class.human_attribute_name(attribute, :default => attr_name) + attr_name = @base.class.human_attribute_name(attribute, default: attr_name) I18n.t(:"errors.format", { - :default => "%{attribute} %{message}", - :attribute => attr_name, - :message => message + default: "%{attribute} %{message}", + attribute: attr_name, + message: message }) end @@ -414,10 +433,10 @@ module ActiveModel value = (attribute != :base ? @base.send(:read_attribute_for_validation, attribute) : nil) options = { - :default => defaults, - :model => @base.class.model_name.human, - :attribute => @base.class.human_attribute_name(attribute), - :value => value + default: defaults, + model: @base.model_name.human, + attribute: @base.class.human_attribute_name(attribute), + value: value }.merge!(options) I18n.translate(key, options) @@ -425,8 +444,6 @@ module ActiveModel private def normalize_message(attribute, message, options) - message ||= :invalid - case message when Symbol generate_message(attribute, message, options.except(*CALLBACKS_OPTIONS)) diff --git a/activemodel/lib/active_model/forbidden_attributes_protection.rb b/activemodel/lib/active_model/forbidden_attributes_protection.rb index 7468f95548..b4fa378601 100644 --- a/activemodel/lib/active_model/forbidden_attributes_protection.rb +++ b/activemodel/lib/active_model/forbidden_attributes_protection.rb @@ -23,5 +23,6 @@ module ActiveModel attributes end end + alias :sanitize_forbidden_attributes :sanitize_for_mass_assignment end end diff --git a/activemodel/lib/active_model/gem_version.rb b/activemodel/lib/active_model/gem_version.rb new file mode 100644 index 0000000000..2403242ce6 --- /dev/null +++ b/activemodel/lib/active_model/gem_version.rb @@ -0,0 +1,15 @@ +module ActiveModel + # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt> + def self.gem_version + Gem::Version.new VERSION::STRING + end + + module VERSION + MAJOR = 5 + MINOR = 0 + TINY = 0 + PRE = "alpha" + + STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") + end +end diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1be2913f0b..38087521a2 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -13,7 +13,7 @@ module ActiveModel # # These tests do not attempt to determine the semantic correctness of the # returned values. For instance, you could implement <tt>valid?</tt> to - # always return true, and the tests would pass. It is up to you to ensure + # always return +true+, and the tests would pass. It is up to you to ensure # that the values are semantically meaningful. # # Objects you pass in are expected to return a compliant object from a call @@ -73,16 +73,19 @@ module ActiveModel # == \Naming # - # Model.model_name must return a string with some convenience methods: - # <tt>:human</tt>, <tt>:singular</tt> and <tt>:plural</tt>. Check - # ActiveModel::Naming for more information. + # Model.model_name and Model#model_name must return a string with some + # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and + # <tt>:plural</tt>. Check ActiveModel::Naming for more information. def test_model_naming - assert model.class.respond_to?(:model_name), "The model should respond to model_name" + assert model.class.respond_to?(:model_name), "The model class should respond to model_name" model_name = model.class.model_name assert model_name.respond_to?(:to_str) assert model_name.human.respond_to?(:to_str) assert model_name.singular.respond_to?(:to_str) assert model_name.plural.respond_to?(:to_str) + + assert model.respond_to?(:model_name), "The model instance should respond to model_name" + assert_equal model.model_name, model.class.model_name end # == \Errors Testing @@ -98,7 +101,7 @@ module ActiveModel private def model - assert @model.respond_to?(:to_model), "The object should respond_to to_model" + assert @model.respond_to?(:to_model), "The object should respond to to_model" @model.to_model end diff --git a/activemodel/lib/active_model/locale/en.yml b/activemodel/lib/active_model/locale/en.yml index 540e8132d3..bf07945fe1 100644 --- a/activemodel/lib/active_model/locale/en.yml +++ b/activemodel/lib/active_model/locale/en.yml @@ -14,9 +14,15 @@ en: empty: "can't be empty" blank: "can't be blank" present: "must be blank" - too_long: "is too long (maximum is %{count} characters)" - too_short: "is too short (minimum is %{count} characters)" - wrong_length: "is the wrong length (should be %{count} characters)" + too_long: + one: "is too long (maximum is 1 character)" + other: "is too long (maximum is %{count} characters)" + too_short: + one: "is too short (minimum is 1 character)" + other: "is too short (minimum is %{count} characters)" + wrong_length: + one: "is the wrong length (should be 1 character)" + other: "is the wrong length (should be %{count} characters)" not_a_number: "is not a number" not_an_integer: "must be an integer" greater_than: "must be greater than %{count}" diff --git a/activemodel/lib/active_model/model.rb b/activemodel/lib/active_model/model.rb index 62383a03e8..d51d6ddcc9 100644 --- a/activemodel/lib/active_model/model.rb +++ b/activemodel/lib/active_model/model.rb @@ -1,6 +1,6 @@ module ActiveModel - # == Active \Model Basic \Model + # == Active \Model \Basic \Model # # Includes the required interface for an object to interact with # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules. @@ -16,8 +16,8 @@ module ActiveModel # end # # person = Person.new(name: 'bob', age: '18') - # person.name # => 'bob' - # person.age # => 18 + # person.name # => "bob" + # person.age # => "18" # # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt> # to return +false+, which is the most common case. You may want to override @@ -56,13 +56,13 @@ module ActiveModel # refer to the specific modules included in <tt>ActiveModel::Model</tt> # (see below). module Model - def self.included(base) #:nodoc: - base.class_eval do - extend ActiveModel::Naming - extend ActiveModel::Translation - include ActiveModel::Validations - include ActiveModel::Conversion - end + extend ActiveSupport::Concern + include ActiveModel::Validations + include ActiveModel::Conversion + + included do + extend ActiveModel::Naming + extend ActiveModel::Translation end # Initializes a new model with the given +params+. @@ -74,11 +74,13 @@ module ActiveModel # # person = Person.new(name: 'bob', age: '18') # person.name # => "bob" - # person.age # => 18 + # person.age # => "18" def initialize(params={}) params.each do |attr, value| self.public_send("#{attr}=", value) end if params + + super() end # Indicates if the model is persisted. Default is +false+. diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb index 6887f6d781..4e6b02c246 100644 --- a/activemodel/lib/active_model/naming.rb +++ b/activemodel/lib/active_model/naming.rb @@ -129,7 +129,7 @@ module ActiveModel # # Equivalent to +to_s+. delegate :==, :===, :<=>, :=~, :"!~", :eql?, :to_s, - :to_str, :to => :name + :to_str, to: :name # Returns a new ActiveModel::Name instance. By default, the +namespace+ # and +name+ option will take the namespace and name of the given class @@ -183,7 +183,7 @@ module ActiveModel defaults << options[:default] if options[:default] defaults << @human - options = { :scope => [@klass.i18n_scope, :models], :count => 1, :default => defaults }.merge!(options.except(:default)) + options = { scope: [@klass.i18n_scope, :models], count: 1, default: defaults }.merge!(options.except(:default)) I18n.translate(defaults.shift, options) end @@ -204,7 +204,7 @@ module ActiveModel # extend ActiveModel::Naming # end # - # BookCover.model_name # => "BookCover" + # BookCover.model_name.name # => "BookCover" # BookCover.model_name.human # => "Book cover" # # BookCover.model_name.i18n_key # => :book_cover @@ -214,14 +214,20 @@ module ActiveModel # is required to pass the Active Model Lint test. So either extending the # provided method below, or rolling your own is required. module Naming + def self.extended(base) #:nodoc: + base.remove_possible_method :model_name + base.delegate :model_name, to: :class + end + # Returns an ActiveModel::Name object for module. It can be # used to retrieve all kinds of naming-related information # (See ActiveModel::Name for more information). # - # class Person < ActiveModel::Model + # class Person + # include ActiveModel::Model # end # - # Person.model_name # => Person + # Person.model_name.name # => "Person" # Person.model_name.class # => ActiveModel::Name # Person.model_name.singular # => "person" # Person.model_name.plural # => "people" @@ -262,10 +268,10 @@ module ActiveModel # namespaced models regarding whether it's inside isolated engine. # # # For isolated engine: - # ActiveModel::Naming.singular_route_key(Blog::Post) #=> post + # ActiveModel::Naming.singular_route_key(Blog::Post) # => "post" # # # For shared engine: - # ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post + # ActiveModel::Naming.singular_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 @@ -274,10 +280,10 @@ module ActiveModel # namespaced models regarding whether it's inside isolated engine. # # # For isolated engine: - # ActiveModel::Naming.route_key(Blog::Post) #=> posts + # ActiveModel::Naming.route_key(Blog::Post) # => "posts" # # # For shared engine: - # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts + # 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. @@ -289,21 +295,19 @@ module ActiveModel # namespaced models regarding whether it's inside isolated engine. # # # For isolated engine: - # ActiveModel::Naming.param_key(Blog::Post) #=> post + # ActiveModel::Naming.param_key(Blog::Post) # => "post" # # # For shared engine: - # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post + # ActiveModel::Naming.param_key(Blog::Post) # => "blog_post" def self.param_key(record_or_class) model_name_from_record_or_class(record_or_class).param_key end def self.model_name_from_record_or_class(record_or_class) #:nodoc: - if record_or_class.respond_to?(:model_name) - record_or_class.model_name - elsif record_or_class.respond_to?(:to_model) - record_or_class.to_model.class.model_name + if record_or_class.respond_to?(:to_model) + record_or_class.to_model.model_name else - record_or_class.class.model_name + record_or_class.model_name end end private_class_method :model_name_from_record_or_class diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 6644b60609..96e88f1b6c 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -2,25 +2,36 @@ module ActiveModel module SecurePassword extend ActiveSupport::Concern - class << self; attr_accessor :min_cost; end + # BCrypt hash function can handle maximum 72 characters, and if we pass + # password of length more than 72 characters it ignores extra characters. + # Hence need to put a restriction on password length. + MAX_PASSWORD_LENGTH_ALLOWED = 72 + + class << self + attr_accessor :min_cost # :nodoc: + end self.min_cost = false module ClassMethods # Adds methods to set and authenticate against a BCrypt password. - # This mechanism requires you to have a password_digest attribute. + # This mechanism requires you to have a +password_digest+ attribute. + # + # The following validations are added automatically: + # * Password must be present on creation + # * Password length should be less than or equal to 72 characters + # * Confirmation of password (using a +password_confirmation+ attribute) # - # Validations for presence of password on create, confirmation of password - # (using a +password_confirmation+ attribute) are automatically added. If - # you wish to turn off validations, pass <tt>validations: false</tt> as an - # argument. You can add more validations by hand if need be. + # If password confirmation validation is not needed, simply leave out the + # value for +password_confirmation+ (i.e. don't provide a form field for + # it). When this attribute has a +nil+ value, the validation will not be + # triggered. # - # If you don't need the confirmation validation, just don't set any - # value to the password_confirmation attribute and the the validation - # will not be triggered. + # For further customizability, it is possible to supress the default + # validations by passing <tt>validations: false</tt> as an argument. # - # You need to add bcrypt-ruby (~> 3.0.0) to Gemfile to use #has_secure_password: + # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password: # - # gem 'bcrypt-ruby', '~> 3.0.0' + # gem 'bcrypt', '~> 3.1.7' # # Example using Active Record (which automatically includes ActiveModel::SecurePassword): # @@ -30,33 +41,44 @@ module ActiveModel # end # # user = User.new(name: 'david', password: '', password_confirmation: 'nomatch') - # user.save # => false, password required + # user.save # => false, password required # user.password = 'mUc3m00RsqyRe' - # user.save # => false, confirmation doesn't match + # user.save # => false, confirmation doesn't match # user.password_confirmation = 'mUc3m00RsqyRe' - # user.save # => true - # user.authenticate('notright') # => false - # user.authenticate('mUc3m00RsqyRe') # => user - # User.find_by_name('david').try(:authenticate, 'notright') # => false - # User.find_by_name('david').try(:authenticate, 'mUc3m00RsqyRe') # => user + # user.save # => true + # user.authenticate('notright') # => false + # user.authenticate('mUc3m00RsqyRe') # => user + # User.find_by(name: 'david').try(:authenticate, 'notright') # => false + # User.find_by(name: 'david').try(:authenticate, 'mUc3m00RsqyRe') # => user def has_secure_password(options = {}) - # Load bcrypt-ruby only when has_secure_password is used. + # Load bcrypt gem only when has_secure_password is used. # This is to avoid ActiveModel (and by extension the entire framework) # being dependent on a binary library. - gem 'bcrypt-ruby', '~> 3.0.0' - require 'bcrypt' + begin + require 'bcrypt' + rescue LoadError + $stderr.puts "You don't have bcrypt installed in your application. Please add it to your Gemfile and run bundle install" + raise + end - attr_reader :password + include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password - validates_presence_of :password, :on => :create + include ActiveModel::Validations - before_create { raise "Password digest missing on new record" if password_digest.blank? } - end + # This ensures the model has a password by checking whether the password_digest + # is present, so that this works with both new and existing records. However, + # when there is an error, the message is added to the password attribute instead + # so that the error message will make sense to the end-user. + validate do |record| + record.errors.add(:password, :blank) unless record.password_digest.present? + end - include InstanceMethodsOnActivation + validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED + validates_confirmation_of :password, allow_blank: true + end + # This code is necessary as long as the protected_attributes gem is supported. if respond_to?(:attributes_protected_by_default) def self.attributes_protected_by_default #:nodoc: super + ['password_digest'] @@ -77,11 +99,13 @@ module ActiveModel # user.authenticate('notright') # => false # user.authenticate('mUc3m00RsqyRe') # => user def authenticate(unencrypted_password) - BCrypt::Password.new(password_digest) == unencrypted_password && self + BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self end + attr_reader :password + # Encrypts the password into the +password_digest+ attribute, only if the - # new password is not blank. + # new password is not empty. # # class User < ActiveRecord::Base # has_secure_password validations: false @@ -93,12 +117,18 @@ module ActiveModel # user.password = 'mUc3m00RsqyRe' # user.password_digest # => "$2a$10$4LEA7r4YmNHtvlAvHhsYAeZmk/xeUVtMTYqwIvYY76EW5GUqDiP4." def password=(unencrypted_password) - unless unencrypted_password.blank? + if unencrypted_password.nil? + self.password_digest = nil + elsif !unencrypted_password.empty? @password = unencrypted_password - cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST + cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost) end end + + def password_confirmation=(unencrypted_password) + @password_confirmation = unencrypted_password + end end end end diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index fdb06aebb9..976f50b13e 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/hash/slice' module ActiveModel # == Active \Model \Serialization # - # Provides a basic serialization to a serializable_hash for your object. + # Provides a basic serialization to a serializable_hash for your objects. # # A minimal implementation could be: # @@ -25,14 +25,14 @@ module ActiveModel # person.name = "Bob" # person.serializable_hash # => {"name"=>"Bob"} # - # You need to declare an attributes hash which contains the attributes you - # want to serialize. Attributes must be strings, not symbols. 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+. + # An +attributes+ hash must be defined and should contain any attributes you + # need to be serialized. Attributes must be strings, not symbols. + # 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 + # Most of the time though, either the JSON or XML serializations are needed. + # Both of these modules automatically include the # <tt>ActiveModel::Serialization</tt> module, so there is no need to # explicitly include it. # @@ -128,7 +128,7 @@ module ActiveModel # retrieve the value for a given attribute differently: # # class MyClass - # include ActiveModel::Validations + # include ActiveModel::Serialization # # def initialize(data = {}) # @data = data diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 9d984b7a18..b66dbf1afe 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -2,7 +2,7 @@ require 'active_support/json' module ActiveModel module Serializers - # == Active Model JSON Serializer + # == Active \Model \JSON \Serializer module JSON extend ActiveSupport::Concern include ActiveModel::Serialization @@ -93,7 +93,7 @@ module ActiveModel end if root - root = self.class.model_name.element if root == true + root = model_name.element if root == true { root => serializable_hash(options) } else serializable_hash(options) @@ -109,7 +109,7 @@ module ActiveModel # # def attributes=(hash) # hash.each do |key, value| - # instance_variable_set("@#{key}", value) + # send("#{key}=", value) # end # end # @@ -130,10 +130,10 @@ module ActiveModel # # json = { person: { name: 'bob', age: 22, awesome:true } }.to_json # person = Person.new - # person.from_json(json) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob"> - # person.name # => "bob" - # person.age # => 22 - # person.awesome # => true + # person.from_json(json, true) # => #<Person:0x007fec5e7a0088 @age=22, @awesome=true, @name="bob"> + # person.name # => "bob" + # person.age # => 22 + # person.awesome # => true def from_json(json, include_root=include_root_in_json) hash = ActiveSupport::JSON.decode(json) hash = hash.values.first if include_root diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 648ae7ce3d..3ad3bf30ad 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -1,4 +1,4 @@ -require 'active_support/core_ext/class/attribute_accessors' +require 'active_support/core_ext/module/attribute_accessors' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/hash/conversions' require 'active_support/core_ext/hash/slice' @@ -79,17 +79,17 @@ module ActiveModel require 'builder' unless defined? ::Builder options[:indent] ||= 2 - options[:builder] ||= ::Builder::XmlMarkup.new(:indent => options[:indent]) + options[:builder] ||= ::Builder::XmlMarkup.new(indent: options[:indent]) @builder = options[:builder] @builder.instruct! unless options[:skip_instruct] - root = (options[:root] || @serializable.class.model_name.element).to_s + root = (options[:root] || @serializable.model_name.element).to_s root = ActiveSupport::XmlMini.rename_key(root, options) args = [root] - args << {:xmlns => options[:namespace]} if options[:namespace] - args << {:type => options[:type]} if options[:type] && !options[:skip_types] + args << { xmlns: options[:namespace] } if options[:namespace] + args << { type: options[:type] } if options[:type] && !options[:skip_types] @builder.tag!(*args) do add_attributes_and_methods @@ -132,7 +132,7 @@ module ActiveModel records = records.to_ary tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) - type = options[:skip_types] ? { } : {:type => "array"} + type = options[:skip_types] ? { } : { type: "array" } association_name = association.to_s.singularize merged_options[:root] = association_name @@ -145,7 +145,7 @@ module ActiveModel record_type = {} else record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name - record_type = {:type => record_class} + record_type = { type: record_class } end record.to_xml merged_options.merge(record_type) @@ -205,7 +205,7 @@ module ActiveModel Serializer.new(self, options).serialize(&block) end - # Sets the model +attributes+ from a JSON string. Returns +self+. + # Sets the model +attributes+ from an XML string. Returns +self+. # # class Person # include ActiveModel::Serializers::Xml diff --git a/activemodel/lib/active_model/translation.rb b/activemodel/lib/active_model/translation.rb index 0d098ba93d..8470915abb 100644 --- a/activemodel/lib/active_model/translation.rb +++ b/activemodel/lib/active_model/translation.rb @@ -41,7 +41,7 @@ module ActiveModel # # Specify +options+ with additional translating options. def human_attribute_name(attribute, options = {}) - options = { :count => 1 }.merge!(options) + options = { count: 1 }.merge!(options) parts = attribute.to_s.split(".") attribute = parts.pop namespace = parts.join("/") unless parts.empty? diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 2db4a25f61..6a2668b8f7 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -4,7 +4,7 @@ require 'active_support/core_ext/hash/except' module ActiveModel - # == Active \Model Validations + # == Active \Model \Validations # # Provides a full validation framework to your objects. # @@ -39,6 +39,7 @@ module ActiveModel extend ActiveSupport::Concern included do + extend ActiveModel::Naming extend ActiveModel::Callbacks extend ActiveModel::Translation @@ -46,7 +47,7 @@ module ActiveModel include HelperMethods attr_accessor :validation_context - define_callbacks :validate, :scope => :name + define_callbacks :validate, scope: :name class_attribute :_validators self._validators = Hash.new { |h,k| h[k] = [] } @@ -66,8 +67,11 @@ module ActiveModel # end # # Options: - # * <tt>:on</tt> - Specifies the context where this validation is active - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>) + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. # * <tt>:if</tt> - Specifies a method, proc or string to call to determine @@ -83,6 +87,8 @@ module ActiveModel validates_with BlockValidator, _merge_attributes(attr_names), &block end + VALID_OPTIONS_FOR_VALIDATE = [:on, :if, :unless, :prepend].freeze + # Adds a validation method or block to the class. This is useful when # overriding the +validate+ instance method becomes too unwieldy and # you're looking for more descriptive declaration of your validations. @@ -124,10 +130,11 @@ module ActiveModel # end # # Options: - # * <tt>:on</tt> - Specifies the context where this validation is active - # (e.g. <tt>on: :create</tt> or <tt>on: :custom_validation_context</tt>) - # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+. - # * <tt>:allow_blank</tt> - Skip validation if attribute is blank. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <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, @@ -139,11 +146,23 @@ module ActiveModel # value. def validate(*args, &block) options = args.extract_options! + + if args.all? { |arg| arg.is_a?(Symbol) } + options.each_key do |k| + unless VALID_OPTIONS_FOR_VALIDATE.include?(k) + raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?") + end + end + end + if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) - options[:if].unshift("validation_context == :#{options[:on]}") + options[:if].unshift ->(o) { + Array(options[:on]).include?(o.validation_context) + } end + args << options set_callback(:validate, *args, &block) end @@ -169,6 +188,49 @@ module ActiveModel _validators.values.flatten.uniq end + # Clears all of the validators and validations. + # + # Note that this will clear anything that is being used to validate + # the model for both the +validates_with+ and +validate+ methods. + # It clears the validators that are created with an invocation of + # +validates_with+ and the callbacks that are set by an invocation + # of +validate+. + # + # class Person + # include ActiveModel::Validations + # + # validates_with MyValidator + # validates_with OtherValidator, on: :create + # validates_with StrictValidator, strict: true + # validate :cannot_be_robot + # + # def cannot_be_robot + # errors.add(:base, 'A person cannot be a robot') if person_is_robot + # end + # end + # + # Person.validators + # # => [ + # # #<MyValidator:0x007fbff403e808 @options={}>, + # # #<OtherValidator:0x007fbff403d930 @options={on: :create}>, + # # #<StrictValidator:0x007fbff3204a30 @options={strict:true}> + # # ] + # + # If one runs <tt>Person.clear_validators!</tt> and then checks to see what + # validators this class has, you would obtain: + # + # Person.validators # => [] + # + # Also, the callback set by <tt>validate :cannot_be_robot</tt> will be erased + # so that: + # + # Person._validate_callbacks.empty? # => true + # + def clear_validators! + reset_callbacks(:validate) + _validators.clear + end + # List all validators that are being used to validate a specific attribute. # # class Person @@ -183,7 +245,6 @@ module ActiveModel # Person.validators_on(:name) # # => [ # # #<ActiveModel::Validations::PresenceValidator:0x007fe604914e60 @attributes=[:name], @options={}>, - # # #<ActiveModel::Validations::InclusionValidator:0x007fe603bb8780 @attributes=[:age], @options={in:0..99}> # # ] def validators_on(*attributes) attributes.flat_map do |attribute| @@ -239,6 +300,8 @@ module ActiveModel # Runs all the specified validations and returns +true+ if no errors were # added otherwise +false+. # + # Aliased as validate. + # # class Person # include ActiveModel::Validations # @@ -273,6 +336,8 @@ module ActiveModel self.validation_context = current_context end + alias_method :validate, :valid? + # Performs the opposite of <tt>valid?</tt>. Returns +true+ if errors were # added, +false+ otherwise. # @@ -327,13 +392,10 @@ module ActiveModel protected def run_validations! #:nodoc: - run_callbacks :validate + _run_validate_callbacks errors.empty? end end end -Dir[File.dirname(__FILE__) + "/validations/*.rb"].sort.each do |path| - filename = File.basename(path) - require "active_model/validations/#{filename}" -end +Dir[File.dirname(__FILE__) + "/validations/*.rb"].each { |file| require file } diff --git a/activemodel/lib/active_model/validations/absence.rb b/activemodel/lib/active_model/validations/absence.rb index 1a1863370b..9b5416fb1d 100644 --- a/activemodel/lib/active_model/validations/absence.rb +++ b/activemodel/lib/active_model/validations/absence.rb @@ -21,7 +21,7 @@ module ActiveModel # * <tt>:message</tt> - A custom error message (default is: "must be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_absence_of(*attr_names) validates_with AbsenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 0935ad0d2a..ac5e79859b 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -3,7 +3,8 @@ module ActiveModel module Validations class AcceptanceValidator < EachValidator # :nodoc: def initialize(options) - super({ :allow_nil => true, :accept => "1" }.merge!(options)) + super({ allow_nil: true, accept: "1" }.merge!(options)) + setup!(options[:class]) end def validate_each(record, attribute, value) @@ -12,7 +13,8 @@ module ActiveModel end end - def setup(klass) + private + def setup!(klass) attr_readers = attributes.reject { |name| klass.attribute_method?(name) } attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } klass.send(:attr_reader, *attr_readers) @@ -36,8 +38,6 @@ module ActiveModel # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "must be # accepted"). - # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default - # is +true+). # * <tt>:accept</tt> - Specifies value that is considered accepted. # The default value is a string "1", which makes it easy to relate to # an HTML checkbox. This should be set to +true+ if you are validating @@ -45,8 +45,8 @@ module ActiveModel # before validation. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. - # See <tt>ActiveModel::Validation#validates</tt> for more information + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. + # See <tt>ActiveModel::Validation#validates</tt> for more information. def validates_acceptance_of(*attr_names) validates_with AcceptanceValidator, _merge_attributes(attr_names) end diff --git a/activemodel/lib/active_model/validations/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index e28ad2841b..25ccabd66b 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -1,6 +1,6 @@ module ActiveModel module Validations - # == Active \Model Validation Callbacks + # == Active \Model \Validation \Callbacks # # Provides an interface for any class to have +before_validation+ and # +after_validation+ callbacks. @@ -22,7 +22,10 @@ module ActiveModel included do include ActiveSupport::Callbacks - define_callbacks :validation, :terminator => "result == false", :skip_after_callbacks_if_terminated => true, :scope => [:kind, :name] + define_callbacks :validation, + terminator: ->(_,result) { result == false }, + skip_after_callbacks_if_terminated: true, + scope: [:kind, :name] end module ClassMethods @@ -55,7 +58,9 @@ module ActiveModel if options.is_a?(Hash) && options[:on] options[:if] = Array(options[:if]) options[:on] = Array(options[:on]) - options[:if].unshift("#{options[:on]}.include? self.validation_context") + options[:if].unshift ->(o) { + options[:on].include? o.validation_context + } end set_callback(:validation, :before, *args, &block) end @@ -93,7 +98,9 @@ module ActiveModel options[:if] = Array(options[:if]) if options[:on] options[:on] = Array(options[:on]) - options[:if].unshift("#{options[:on]}.include? self.validation_context") + options[:if].unshift ->(o) { + options[:on].include? o.validation_context + } end set_callback(:validation, :after, *(args << options), &block) end @@ -103,7 +110,7 @@ module ActiveModel # Overwrite run validations to include callbacks. def run_validations! #:nodoc: - run_callbacks(:validation) { super } + _run_validation_callbacks { super } end end end diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index 49df98d6c1..bad9e4f9a9 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -15,26 +15,36 @@ module ActiveModel private def include?(record, value) - exclusions = if delimiter.respond_to?(:call) - delimiter.call(record) - elsif delimiter.respond_to?(:to_sym) - record.send(delimiter) - else - delimiter - end + members = if delimiter.respond_to?(:call) + delimiter.call(record) + elsif delimiter.respond_to?(:to_sym) + record.send(delimiter) + else + delimiter + end - exclusions.send(inclusion_method(exclusions), value) + members.send(inclusion_method(members), value) end def delimiter @delimiter ||= options[:in] || options[:within] end - # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the - # range for equality, so it may be slow for large ranges. The new <tt>Range#cover?</tt> - # uses the previous logic of comparing a value with the range endpoints. + # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all + # possible values in the range for equality, which is slower but more accurate. + # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range + # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges. def inclusion_method(enumerable) - enumerable.is_a?(Range) ? :cover? : :include? + if enumerable.is_a? Range + case enumerable.first + when Numeric, Time, DateTime + :cover? + else + :include? + end + else + :include? + end end end end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 3a3abce364..1b11c28087 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -2,17 +2,27 @@ module ActiveModel module Validations class ConfirmationValidator < EachValidator # :nodoc: + def initialize(options) + super + setup!(options[:class]) + end + def validate_each(record, attribute, value) if (confirmed = record.send("#{attribute}_confirmation")) && (value != confirmed) human_attribute_name = record.class.human_attribute_name(attribute) - record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(:attribute => human_attribute_name)) + record.errors.add(:"#{attribute}_confirmation", :confirmation, options.merge(attribute: human_attribute_name)) end end - def setup(klass) - klass.send(:attr_accessor, *attributes.map do |attribute| + private + def setup!(klass) + klass.send(:attr_reader, *attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") end.compact) + + klass.send(:attr_writer, *attributes.map do |attribute| + :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") + end.compact) end end @@ -44,10 +54,10 @@ module ActiveModel # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "doesn't match - # confirmation"). + # <tt>%{translated_attribute_name}</tt>"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_confirmation_of(*attr_names) validates_with ConfirmationValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index b7f38e48f5..f342d27275 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -8,7 +8,7 @@ module ActiveModel def validate_each(record, attribute, value) if include?(record, value) - record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(:value => value)) + record.errors.add(attribute, :exclusion, options.except(:in, :within).merge!(value: value)) end end end @@ -34,13 +34,9 @@ module ActiveModel # <tt>Range#cover?</tt>, otherwise with <tt>include?</tt>. # * <tt>:message</tt> - Specifies a custom error message (default is: "is # reserved"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to true, skips this validation if the - # attribute is blank(default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_exclusion_of(*attr_names) validates_with ExclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index 9398b7e66e..02478dd5b6 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -17,8 +17,8 @@ module ActiveModel raise ArgumentError, "Either :with or :without must be supplied (but not both)" end - check_options_validity(options, :with) - check_options_validity(options, :without) + check_options_validity :with + check_options_validity :without end private @@ -29,30 +29,32 @@ module ActiveModel end def record_error(record, attribute, name, value) - record.errors.add(attribute, :invalid, options.except(name).merge!(:value => value)) + record.errors.add(attribute, :invalid, options.except(name).merge!(value: value)) end - def regexp_using_multiline_anchors?(regexp) - regexp.source.start_with?("^") || - (regexp.source.end_with?("$") && !regexp.source.end_with?("\\$")) + def check_options_validity(name) + if option = options[name] + if option.is_a?(Regexp) + if options[:multiline] != true && regexp_using_multiline_anchors?(option) + raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ + "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ + ":multiline => true option?" + end + elsif !option.respond_to?(:call) + raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" + end + end end - def check_options_validity(options, name) - option = options[name] - if option && !option.is_a?(Regexp) && !option.respond_to?(:call) - raise ArgumentError, "A regular expression or a proc or lambda must be supplied as :#{name}" - elsif option && option.is_a?(Regexp) && - regexp_using_multiline_anchors?(option) && options[:multiline] != true - raise ArgumentError, "The provided regular expression is using multiline anchors (^ or $), " \ - "which may present a security risk. Did you mean to use \\A and \\z, or forgot to add the " \ - ":multiline => true option?" - end + def regexp_using_multiline_anchors?(regexp) + source = regexp.source + source.start_with?("^") || (source.end_with?("$") && !source.end_with?("\\$")) end end module HelperMethods # Validates whether the value of the specified attribute is of the correct - # form, going by the regular expression provided.You can require that the + # form, going by the regular expression provided. You can require that the # attribute matches the regular expression: # # class Person < ActiveRecord::Base @@ -89,10 +91,6 @@ module ActiveModel # # Configuration options: # * <tt>:message</tt> - A custom error message (default is: "is invalid"). - # * <tt>:allow_nil</tt> - If set to true, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to true, skips this validation if the - # attribute is blank (default is +false+). # * <tt>:with</tt> - Regular expression that if the attribute matches will # result in a successful validation. This can be provided as a proc or # lambda returning regular expression which will be called at runtime. @@ -105,7 +103,7 @@ module ActiveModel # beginning or end of the string. These anchors are <tt>^</tt> and <tt>$</tt>. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_format_of(*attr_names) validates_with FormatValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 5e45a04c2c..c84025f083 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -8,7 +8,7 @@ module ActiveModel def validate_each(record, attribute, value) unless include?(record, value) - record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(:value => value)) + record.errors.add(attribute, :inclusion, options.except(:in, :within).merge!(value: value)) end end end @@ -28,18 +28,15 @@ module ActiveModel # Configuration options: # * <tt>:in</tt> - An enumerable object of available items. This can be # supplied as a proc, lambda or symbol which returns an enumerable. If the - # enumerable is a range the test is performed with <tt>Range#cover?</tt>, - # otherwise with <tt>include?</tt>. + # enumerable is a numerical range the test is performed with <tt>Range#cover?</tt>, + # otherwise with <tt>include?</tt>. When using a proc or lambda the instance + # under validation is passed as an argument. # * <tt>:within</tt> - A synonym(or alias) for <tt>:in</tt> # * <tt>:message</tt> - Specifies a custom error message (default is: "is # not included in the list"). - # * <tt>:allow_nil</tt> - If set to +true+, skips this validation if the - # attribute is +nil+ (default is +false+). - # * <tt>:allow_blank</tt> - If set to +true+, skips this validation if the - # attribute is blank (default is +false+). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_inclusion_of(*attr_names) validates_with InclusionValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 675fb5f1e5..a96b30cadd 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -1,10 +1,10 @@ module ActiveModel - # == Active \Model Length \Validator + # == Active \Model Length Validator module Validations class LengthValidator < EachValidator # :nodoc: - MESSAGES = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }.freeze - CHECKS = { :is => :==, :minimum => :>=, :maximum => :<= }.freeze + MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze + CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze RESERVED_OPTIONS = [:minimum, :maximum, :within, :is, :tokenizer, :too_short, :too_long] diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index 085532c35b..4ba4e3e8f7 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -2,17 +2,18 @@ module ActiveModel module Validations class NumericalityValidator < EachValidator # :nodoc: - CHECKS = { :greater_than => :>, :greater_than_or_equal_to => :>=, - :equal_to => :==, :less_than => :<, :less_than_or_equal_to => :<=, - :odd => :odd?, :even => :even?, :other_than => :!= }.freeze + CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=, + equal_to: :==, less_than: :<, less_than_or_equal_to: :<=, + odd: :odd?, even: :even?, other_than: :!= }.freeze RESERVED_OPTIONS = CHECKS.keys + [:only_integer] def check_validity! keys = CHECKS.keys - [:odd, :even] options.slice(*keys).each do |option, value| - next if value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) - raise ArgumentError, ":#{option} must be a number, a symbol or a proc" + unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol) + raise ArgumentError, ":#{option} must be a number, a symbol or a proc" + end end end @@ -22,6 +23,10 @@ module ActiveModel raw_value = record.send(before_type_cast) if record.respond_to?(before_type_cast) raw_value ||= value + if record_attribute_changed_in_place?(record, attr_name) + raw_value = value + end + return if options[:allow_nil] && raw_value.nil? unless value = parse_raw_value_as_a_number(raw_value) @@ -29,7 +34,7 @@ module ActiveModel return end - if options[:only_integer] + if allow_only_integer?(record) unless value = parse_raw_value_as_an_integer(raw_value) record.errors.add(attr_name, :not_an_integer, filtered_options(raw_value)) return @@ -43,11 +48,15 @@ module ActiveModel record.errors.add(attr_name, option, filtered_options(value)) end else - option_value = option_value.call(record) if option_value.is_a?(Proc) - option_value = record.send(option_value) if option_value.is_a?(Symbol) + case option_value + when Proc + option_value = option_value.call(record) + when Symbol + option_value = record.send(option_value) + end unless value.send(CHECKS[option], option_value) - record.errors.add(attr_name, option, filtered_options(value).merge(:count => option_value)) + record.errors.add(attr_name, option, filtered_options(value).merge!(count: option_value)) end end end @@ -56,24 +65,37 @@ module ActiveModel protected def parse_raw_value_as_a_number(raw_value) - case raw_value - when /\A0[xX]/ - nil - else - begin - Kernel.Float(raw_value) - rescue ArgumentError, TypeError - nil - end - end + Kernel.Float(raw_value) if raw_value !~ /\A0[xX]/ + rescue ArgumentError, TypeError + nil end def parse_raw_value_as_an_integer(raw_value) - raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\Z/ + raw_value.to_i if raw_value.to_s =~ /\A[+-]?\d+\z/ end def filtered_options(value) - options.except(*RESERVED_OPTIONS).merge!(:value => value) + filtered = options.except(*RESERVED_OPTIONS) + filtered[:value] = value + filtered + end + + def allow_only_integer?(record) + case options[:only_integer] + when Symbol + record.send(options[:only_integer]) + when Proc + options[:only_integer].call(record) + else + options[:only_integer] + end + end + + private + + def record_attribute_changed_in_place?(record, attr_name) + record.respond_to?(:attribute_changed_in_place?) && + record.attribute_changed_in_place?(attr_name.to_s) end end @@ -110,7 +132,7 @@ module ActiveModel # * <tt>:even</tt> - Specifies the value must be an even number. # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+ . + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ . # See <tt>ActiveModel::Validation#validates</tt> for more information # # The following checks can also be supplied with a proc or a symbol which @@ -121,6 +143,7 @@ module ActiveModel # * <tt>:equal_to</tt> # * <tt>:less_than</tt> # * <tt>:less_than_or_equal_to</tt> + # * <tt>:only_integer</tt> # # For example: # diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index ab8c8359fc..5d593274eb 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -29,7 +29,7 @@ module ActiveModel # * <tt>:message</tt> - A custom error message (default is: "can't be blank"). # # There is also a list of default options supported by every validator: - # +:if+, +:unless+, +:on+ and +:strict+. + # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+. # See <tt>ActiveModel::Validation#validates</tt> for more information def validates_presence_of(*attr_names) validates_with PresenceValidator, _merge_attributes(attr_names) diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb index 1eb0716891..bda436d8d0 100644 --- a/activemodel/lib/active_model/validations/validates.rb +++ b/activemodel/lib/active_model/validations/validates.rb @@ -13,7 +13,7 @@ module ActiveModel # validates :terms, acceptance: true # validates :password, confirmation: true # validates :username, exclusion: { in: %w(admin superuser) } - # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, on: :create } + # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create } # validates :age, inclusion: { in: 0..9 } # validates :first_name, length: { maximum: 30 } # validates :age, numericality: true @@ -71,9 +71,11 @@ module ActiveModel # # There is also a list of options that could be used along with validators: # - # * <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>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <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, @@ -83,7 +85,9 @@ module ActiveModel # 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> - if the <tt>:strict</tt> option is set to true + # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+. + # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank. + # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true # will raise ActiveModel::StrictValidationFailed instead of adding the error. # <tt>:strict</tt> option can also be set to any other exception. # @@ -159,9 +163,9 @@ module ActiveModel when Hash options when Range, Array - { :in => options } + { in: options } else - { :with => options } + { with: options } end end end diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 2ae335d0f4..a2531327bf 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -52,8 +52,11 @@ module ActiveModel # end # # Configuration options: - # * <tt>:on</tt> - Specifies when this validation is active - # (<tt>:create</tt> or <tt>:update</tt>. + # * <tt>:on</tt> - Specifies the contexts where this validation is active. + # Runs in all validation contexts by default (nil). You can pass a symbol + # or an array of symbols. (e.g. <tt>on: :create</tt> or + # <tt>on: :custom_validation_context</tt> or + # <tt>on: [:create, :custom_validation_context]</tt>) # * <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>). @@ -83,9 +86,10 @@ module ActiveModel # end def validates_with(*args, &block) options = args.extract_options! + options[:class] = self + args.each do |klass| validator = klass.new(options, &block) - validator.setup(self) if validator.respond_to?(:setup) if validator.respond_to?(:attributes) && !validator.attributes.empty? validator.attributes.each do |attribute| @@ -138,6 +142,8 @@ module ActiveModel # class version of this method for more information. def validates_with(*args, &block) options = args.extract_options! + options[:class] = self.class + args.each do |klass| validator = klass.new(options, &block) validator.validate(self) diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index f989b21140..0116de68ab 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -60,6 +60,9 @@ module ActiveModel # end # end # + # Note that the validator is initialized only once for the whole application + # life cycle, and not on each validation run. + # # The easiest way to add custom validators for validating individual attributes # is with the convenient <tt>ActiveModel::EachValidator</tt>. # @@ -76,21 +79,19 @@ module ActiveModel # include ActiveModel::Validations # attr_accessor :title # - # validates :title, presence: true + # validates :title, presence: true, title: true # end # - # Validator may also define a +setup+ instance method which will get called - # with the class that using that validator as its argument. This can be - # useful when there are prerequisites such as an +attr_accessor+ being present. + # It can be useful to access the class that is using that validator when there are prerequisites such + # as an +attr_accessor+ being present. This class is accessible via +options[:class]+ in the constructor. + # To setup your validator override the constructor. # # class MyValidator < ActiveModel::Validator - # def setup(klass) - # klass.send :attr_accessor, :custom_attribute + # def initialize(options={}) + # super + # options[:class].send :attr_accessor, :custom_attribute # end # end - # - # This setup method is only called when used with validation macros or the - # class level <tt>validates_with</tt> method. class Validator attr_reader :options @@ -104,10 +105,10 @@ module ActiveModel # Accepts options that will be made available through the +options+ reader. def initialize(options = {}) - @options = options.freeze + @options = options.except(:class).freeze end - # Return the kind for this validator. + # Returns the kind for this validator. # # PresenceValidator.new.kind # => :presence # UniquenessValidator.new.kind # => :uniqueness diff --git a/activemodel/lib/active_model/version.rb b/activemodel/lib/active_model/version.rb index e195c12a4d..b1f9082ea7 100644 --- a/activemodel/lib/active_model/version.rb +++ b/activemodel/lib/active_model/version.rb @@ -1,10 +1,8 @@ -module ActiveModel - module VERSION #:nodoc: - MAJOR = 4 - MINOR = 0 - TINY = 0 - PRE = "beta" +require_relative 'gem_version' - STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.') +module ActiveModel + # Returns the version of the currently loaded ActiveModel as a <tt>Gem::Version</tt> + def self.version + gem_version end end diff --git a/activemodel/test/cases/attribute_methods_test.rb b/activemodel/test/cases/attribute_methods_test.rb index baaf842222..e81b7ac424 100644 --- a/activemodel/test/cases/attribute_methods_test.rb +++ b/activemodel/test/cases/attribute_methods_test.rb @@ -10,7 +10,7 @@ class ModelWithAttributes end def attributes - { :foo => 'value of foo', :baz => 'value of baz' } + { foo: 'value of foo', baz: 'value of baz' } end private @@ -80,7 +80,7 @@ class ModelWithRubyKeywordNamedAttributes include ActiveModel::AttributeMethods def attributes - { :begin => 'value of begin', :end => 'value of end' } + { begin: 'value of begin', end: 'value of end' } end private @@ -104,10 +104,14 @@ class AttributeMethodsTest < ActiveModel::TestCase end test '#define_attribute_method generates attribute method' do - ModelWithAttributes.define_attribute_method(:foo) + begin + ModelWithAttributes.define_attribute_method(:foo) - assert_respond_to ModelWithAttributes.new, :foo - assert_equal "value of foo", ModelWithAttributes.new.foo + assert_respond_to ModelWithAttributes.new, :foo + assert_equal "value of foo", ModelWithAttributes.new.foo + ensure + ModelWithAttributes.undefine_attribute_methods + end end test '#define_attribute_method does not generate attribute method if already defined in attribute module' do @@ -134,24 +138,36 @@ class AttributeMethodsTest < ActiveModel::TestCase end test '#define_attribute_method generates attribute method with invalid identifier characters' do - ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') + begin + ModelWithWeirdNamesAttributes.define_attribute_method(:'a?b') - assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' - assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') + assert_respond_to ModelWithWeirdNamesAttributes.new, :'a?b' + assert_equal "value of a?b", ModelWithWeirdNamesAttributes.new.send('a?b') + ensure + ModelWithWeirdNamesAttributes.undefine_attribute_methods + end end test '#define_attribute_methods works passing multiple arguments' do - ModelWithAttributes.define_attribute_methods(:foo, :baz) + begin + ModelWithAttributes.define_attribute_methods(:foo, :baz) - assert_equal "value of foo", ModelWithAttributes.new.foo - assert_equal "value of baz", ModelWithAttributes.new.baz + assert_equal "value of foo", ModelWithAttributes.new.foo + assert_equal "value of baz", ModelWithAttributes.new.baz + ensure + ModelWithAttributes.undefine_attribute_methods + end end test '#define_attribute_methods generates attribute methods' do - ModelWithAttributes.define_attribute_methods(:foo) + begin + ModelWithAttributes.define_attribute_methods(:foo) - assert_respond_to ModelWithAttributes.new, :foo - assert_equal "value of foo", ModelWithAttributes.new.foo + assert_respond_to ModelWithAttributes.new, :foo + assert_equal "value of foo", ModelWithAttributes.new.foo + ensure + ModelWithAttributes.undefine_attribute_methods + end end test '#alias_attribute generates attribute_aliases lookup hash' do @@ -164,26 +180,38 @@ class AttributeMethodsTest < ActiveModel::TestCase end test '#define_attribute_methods generates attribute methods with spaces in their names' do - ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') + begin + ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') - assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar' - assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') + assert_respond_to ModelWithAttributesWithSpaces.new, :'foo bar' + assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.send(:'foo bar') + ensure + ModelWithAttributesWithSpaces.undefine_attribute_methods + end 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') + begin + ModelWithAttributesWithSpaces.define_attribute_methods(:'foo bar') + ModelWithAttributesWithSpaces.alias_attribute(:'foo_bar', :'foo bar') - assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar + assert_equal "value of foo bar", ModelWithAttributesWithSpaces.new.foo_bar + ensure + ModelWithAttributesWithSpaces.undefine_attribute_methods + end end test '#alias_attribute works with attributes named as a ruby keyword' do - ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end]) - ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin) - ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end) - - assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from - assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to + begin + ModelWithRubyKeywordNamedAttributes.define_attribute_methods([:begin, :end]) + ModelWithRubyKeywordNamedAttributes.alias_attribute(:from, :begin) + ModelWithRubyKeywordNamedAttributes.alias_attribute(:to, :end) + + assert_equal "value of begin", ModelWithRubyKeywordNamedAttributes.new.from + assert_equal "value of end", ModelWithRubyKeywordNamedAttributes.new.to + ensure + ModelWithRubyKeywordNamedAttributes.undefine_attribute_methods + end end test '#undefine_attribute_methods removes attribute methods' do @@ -194,7 +222,7 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_raises(NoMethodError) { ModelWithAttributes.new.foo } end - test 'acessing a suffixed attribute' do + test 'accessing a suffixed attribute' do m = ModelWithAttributes2.new m.attributes = { 'foo' => 'bar' } @@ -202,17 +230,6 @@ class AttributeMethodsTest < ActiveModel::TestCase assert_equal 'bar', m.foo_test end - test 'explicitly specifying an empty prefix/suffix is deprecated' do - klass = Class.new(ModelWithAttributes) - - assert_deprecated { klass.attribute_method_suffix '' } - assert_deprecated { klass.attribute_method_prefix '' } - - klass.define_attribute_methods(:foo) - - assert_equal 'value of foo', klass.new.foo - end - test 'should not interfere with method_missing if the attr has a private/protected method' do m = ModelWithAttributes2.new m.attributes = { 'private_method' => '<3', 'protected_method' => 'O_o' } diff --git a/activemodel/test/cases/callbacks_test.rb b/activemodel/test/cases/callbacks_test.rb index 086e7266ff..2ac681b8d8 100644 --- a/activemodel/test/cases/callbacks_test.rb +++ b/activemodel/test/cases/callbacks_test.rb @@ -7,6 +7,7 @@ class CallbacksTest < ActiveModel::TestCase model.callbacks << :before_around_create yield model.callbacks << :after_around_create + false end end @@ -15,25 +16,29 @@ class CallbacksTest < ActiveModel::TestCase extend ActiveModel::Callbacks define_model_callbacks :create - define_model_callbacks :initialize, :only => :after - define_model_callbacks :multiple, :only => [:before, :around] - define_model_callbacks :empty, :only => [] + define_model_callbacks :initialize, only: :after + define_model_callbacks :multiple, only: [:before, :around] + define_model_callbacks :empty, only: [] before_create :before_create around_create CallbackValidator.new after_create do |model| model.callbacks << :after_create + false end after_create "@callbacks << :final_callback" - def initialize(valid=true) - @callbacks, @valid = [], valid + def initialize(options = {}) + @callbacks = [] + @valid = options[:valid] + @before_create_returns = options[:before_create_returns] end def before_create @callbacks << :before_create + @before_create_returns end def create @@ -51,14 +56,20 @@ class CallbacksTest < ActiveModel::TestCase :after_around_create, :after_create, :final_callback] end - test "after callbacks are always appended" do + test "the callback chain is not halted when around or after callbacks return false" do model = ModelCallbacks.new model.create assert_equal model.callbacks.last, :final_callback end + test "the callback chain is halted when a before callback returns false" do + model = ModelCallbacks.new(before_create_returns: false) + model.create + assert_equal model.callbacks.last, :before_create + end + test "after callbacks are not executed if the block returns false" do - model = ModelCallbacks.new(false) + model = ModelCallbacks.new(valid: false) model.create assert_equal model.callbacks, [ :before_create, :before_around_create, :create, :after_around_create] @@ -107,7 +118,7 @@ class CallbacksTest < ActiveModel::TestCase test "after_create callbacks with both callbacks declared in one line" do assert_equal ["callback1", "callback2"], Violin1.new.create.history end - test "after_create callbacks with both callbacks declared in differnt lines" do + test "after_create callbacks with both callbacks declared in different lines" do assert_equal ["callback1", "callback2"], Violin2.new.create.history end diff --git a/activemodel/test/cases/conversion_test.rb b/activemodel/test/cases/conversion_test.rb index a037666cbc..800cad6d9a 100644 --- a/activemodel/test/cases/conversion_test.rb +++ b/activemodel/test/cases/conversion_test.rb @@ -13,7 +13,7 @@ class ConversionTest < ActiveModel::TestCase end test "to_key default implementation returns the id in an array for persisted records" do - assert_equal [1], Contact.new(:id => 1).to_key + assert_equal [1], Contact.new(id: 1).to_key end test "to_param default implementation returns nil for new records" do @@ -21,7 +21,21 @@ class ConversionTest < ActiveModel::TestCase end test "to_param default implementation returns a string of ids for persisted records" do - assert_equal "1", Contact.new(:id => 1).to_param + assert_equal "1", Contact.new(id: 1).to_param + end + + test "to_param returns the string joined by '-'" do + assert_equal "abc-xyz", Contact.new(id: ["abc", "xyz"]).to_param + end + + test "to_param returns nil if to_key is nil" do + klass = Class.new(Contact) do + def persisted? + true + end + end + + assert_nil klass.new.to_param end test "to_partial_path default implementation returns a string giving a relative path" do diff --git a/activemodel/test/cases/deprecated_mass_assignment_security_test.rb b/activemodel/test/cases/deprecated_mass_assignment_security_test.rb deleted file mode 100644 index c1fe8822cd..0000000000 --- a/activemodel/test/cases/deprecated_mass_assignment_security_test.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'cases/helper' -require 'models/project' - -class DeprecatedMassAssignmentSecurityTest < ActiveModel::TestCase - def test_attr_accessible_raise_error - assert_raise RuntimeError, /protected_attributes/ do - Project.attr_accessible :username - end - end - - def test_attr_protected_raise_error - assert_raise RuntimeError, /protected_attributes/ do - Project.attr_protected :username - end - end -end diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb index ba45089cca..db2cd885e2 100644 --- a/activemodel/test/cases/dirty_test.rb +++ b/activemodel/test/cases/dirty_test.rb @@ -3,11 +3,12 @@ require "cases/helper" class DirtyTest < ActiveModel::TestCase class DirtyModel include ActiveModel::Dirty - define_attribute_methods :name, :color + define_attribute_methods :name, :color, :size def initialize @name = nil @color = nil + @size = nil end def name @@ -28,9 +29,25 @@ class DirtyTest < ActiveModel::TestCase @color = val end + def size + @size + end + + def size=(val) + attribute_will_change!(:size) unless val == @size + @size = val + end + def save - @previously_changed = changes - @changed_attributes.clear + changes_applied + end + + def reload + clear_changes_information + end + + def deprecated_reload + reset_changes end end @@ -58,12 +75,30 @@ class DirtyTest < ActiveModel::TestCase assert_equal [nil, "John"], @model.changes['name'] end + test "checking if an attribute has changed to a particular value" do + @model.name = "Ringo" + assert @model.name_changed?(from: nil, to: "Ringo") + assert_not @model.name_changed?(from: "Pete", to: "Ringo") + assert @model.name_changed?(to: "Ringo") + assert_not @model.name_changed?(to: "Pete") + assert @model.name_changed?(from: nil) + assert_not @model.name_changed?(from: "Pete") + end + test "changes accessible through both strings and symbols" do @model.name = "David" assert_not_nil @model.changes[:name] assert_not_nil @model.changes['name'] end + test "be consistent with symbols arguments after the changes are applied" do + @model.name = "David" + assert @model.attribute_changed?(:name) + @model.save + @model.name = 'Rafael' + assert @model.attribute_changed?(:name) + end + test "attribute mutation" do @model.instance_variable_set("@name", "Yam") assert !@model.name_changed? @@ -76,7 +111,7 @@ class DirtyTest < ActiveModel::TestCase test "resetting attribute" do @model.name = "Bob" - @model.reset_name! + @model.restore_name! assert_nil @model.name assert !@model.name_changed? end @@ -125,4 +160,69 @@ class DirtyTest < ActiveModel::TestCase assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change assert_equal @model.name_was, "Otto" end + + test "using attribute_will_change! with a symbol" do + @model.size = 1 + assert @model.size_changed? + end + + test "reload should reset all changes" do + @model.name = 'Dmitry' + @model.name_changed? + @model.save + @model.name = 'Bob' + + assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] + assert_equal 'Dmitry', @model.changed_attributes['name'] + + @model.reload + + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes + end + + test "reset_changes is deprecated" do + @model.name = 'Dmitry' + @model.name_changed? + @model.save + @model.name = 'Bob' + + assert_equal [nil, 'Dmitry'], @model.previous_changes['name'] + assert_equal 'Dmitry', @model.changed_attributes['name'] + + assert_deprecated do + @model.deprecated_reload + end + + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.previous_changes + assert_equal ActiveSupport::HashWithIndifferentAccess.new, @model.changed_attributes + end + + test "restore_attributes should restore all previous data" do + @model.name = 'Dmitry' + @model.color = 'Red' + @model.save + @model.name = 'Bob' + @model.color = 'White' + + @model.restore_attributes + + assert_not @model.changed? + assert_equal 'Dmitry', @model.name + assert_equal 'Red', @model.color + end + + test "restore_attributes can restore only some attributes" do + @model.name = 'Dmitry' + @model.color = 'Red' + @model.save + @model.name = 'Bob' + @model.color = 'White' + + @model.restore_attributes(['name']) + + assert @model.changed? + assert_equal 'Dmitry', @model.name + assert_equal 'White', @model.color + end end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index cc0c3f16d2..efedd9055f 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -11,7 +11,7 @@ class ErrorsTest < ActiveModel::TestCase attr_reader :errors def validate! - errors.add(:name, "can not be nil") if name == nil + errors.add(:name, "cannot be nil") if name == nil end def read_attribute_for_validation(attr) @@ -51,10 +51,26 @@ class ErrorsTest < ActiveModel::TestCase def test_has_key? errors = ActiveModel::Errors.new(self) errors[:foo] = 'omg' - assert errors.has_key?(:foo), 'errors should have key :foo' + assert_equal true, errors.has_key?(:foo), 'errors should have key :foo' end - test "should be able to clear the errors" do + def test_has_no_key + errors = ActiveModel::Errors.new(self) + assert_equal false, errors.has_key?(:name), 'errors should not have key :name' + end + + def test_key? + errors = ActiveModel::Errors.new(self) + errors[:foo] = 'omg' + assert_equal true, errors.key?(:foo), 'errors should have key :foo' + end + + def test_no_key + errors = ActiveModel::Errors.new(self) + assert_equal false, errors.key?(:name), 'errors should not have key :name' + end + + test "clear errors" do person = Person.new person.validate! @@ -63,7 +79,7 @@ class ErrorsTest < ActiveModel::TestCase assert person.errors.empty? end - test "get returns the error by the provided key" do + test "get returns the errors for the provided key" do errors = ActiveModel::Errors.new(self) errors[:foo] = "omg" @@ -77,6 +93,13 @@ class ErrorsTest < ActiveModel::TestCase assert_equal({ foo: "omg" }, errors.messages) end + test "error access is indifferent" do + errors = ActiveModel::Errors.new(self) + errors[:foo] = "omg" + + assert_equal ["omg"], errors["foo"] + end + test "values returns an array of messages" do errors = ActiveModel::Errors.new(self) errors.set(:foo, "omg") @@ -93,21 +116,7 @@ class ErrorsTest < ActiveModel::TestCase assert_equal [:foo, :baz], errors.keys end - test "as_json returns a json formatted representation of the errors hash" do - person = Person.new - person.validate! - - assert_equal({ name: ["can not be nil"] }, person.errors.as_json) - end - - test "as_json with :full_messages option" do - person = Person.new - person.validate! - - assert_equal({ name: ["name can not be nil"] }, person.errors.as_json(full_messages: true)) - end - - test "should return true if no errors" do + test "detecting whether there are errors with empty?, blank?, include?" do person = Person.new person.errors[:foo] assert person.errors.empty? @@ -115,139 +124,154 @@ class ErrorsTest < ActiveModel::TestCase assert !person.errors.include?(:foo) end - test "method validate! should work" do + test "adding errors using conditionals with Person#validate!" do person = Person.new person.validate! - assert_equal ["name can not be nil"], person.errors.full_messages - assert_equal ["can not be nil"], person.errors[:name] + assert_equal ["name cannot be nil"], person.errors.full_messages + assert_equal ["cannot be nil"], person.errors[:name] end - test 'should be able to assign error' do + test "assign error" do person = Person.new person.errors[:name] = 'should not be nil' assert_equal ["should not be nil"], person.errors[:name] end - test 'should be able to add an error on an attribute' do + test "add an error message on a specific attribute" do person = Person.new - person.errors.add(:name, "can not be blank") - assert_equal ["can not be blank"], person.errors[:name] + person.errors.add(:name, "cannot be blank") + assert_equal ["cannot be blank"], person.errors[:name] end - test "should be able to add an error with a symbol" do + test "add an error with a symbol" do person = Person.new person.errors.add(:name, :blank) message = person.errors.generate_message(:name, :blank) assert_equal [message], person.errors[:name] end - test "should be able to add an error with a proc" do + test "add an error with a proc" do person = Person.new - message = Proc.new { "can not be blank" } + message = Proc.new { "cannot be blank" } person.errors.add(:name, message) - assert_equal ["can not be blank"], person.errors[:name] + assert_equal ["cannot be blank"], person.errors[:name] end - test "added? should be true if that error was added" do + test "added? detects if a specific error was added to the object" do person = Person.new - person.errors.add(:name, "can not be blank") - assert person.errors.added?(:name, "can not be blank") + person.errors.add(:name, "cannot be blank") + assert person.errors.added?(:name, "cannot be blank") end - test "added? should handle when message is a symbol" do + test "added? handles symbol message" do person = Person.new person.errors.add(:name, :blank) assert person.errors.added?(:name, :blank) end - test "added? should handle when message is a proc" do + test "added? handles proc messages" do person = Person.new - message = Proc.new { "can not be blank" } + message = Proc.new { "cannot be blank" } person.errors.add(:name, message) assert person.errors.added?(:name, message) end - test "added? should default message to :invalid" do + test "added? defaults message to :invalid" do person = Person.new person.errors.add(:name) assert person.errors.added?(:name) end - test "added? should be true when several errors are present, and we ask for one of them" do + test "added? matches the given message when several errors are present for the same attribute" do person = Person.new - person.errors.add(:name, "can not be blank") + person.errors.add(:name, "cannot be blank") person.errors.add(:name, "is invalid") - assert person.errors.added?(:name, "can not be blank") + assert person.errors.added?(:name, "cannot be blank") end - test "added? should be false if no errors are present" do + test "added? returns false when no errors are present" do person = Person.new assert !person.errors.added?(:name) end - test "added? should be false when an error is present, but we check for another error" do + test "added? returns false when checking a nonexisting error and other errors are present for the given attribute" do person = Person.new person.errors.add(:name, "is invalid") - assert !person.errors.added?(:name, "can not be blank") + assert !person.errors.added?(:name, "cannot be blank") end - test 'should respond to size' do + test "size calculates the number of error messages" do person = Person.new - person.errors.add(:name, "can not be blank") + person.errors.add(:name, "cannot be blank") assert_equal 1, person.errors.size end - test 'to_a should return an array' do + test "to_a returns the list of errors with complete messages containing the attribute names" do + person = Person.new + person.errors.add(:name, "cannot be blank") + person.errors.add(:name, "cannot be nil") + assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.to_a + end + + test "to_hash returns the error messages hash" do person = Person.new - person.errors.add(:name, "can not be blank") - person.errors.add(:name, "can not be nil") - assert_equal ["name can not be blank", "name can not be nil"], person.errors.to_a + person.errors.add(:name, "cannot be blank") + assert_equal({ name: ["cannot be blank"] }, person.errors.to_hash) end - test 'to_hash should return a hash' do + test "full_messages creates a list of error messages with the attribute name included" do person = Person.new - person.errors.add(:name, "can not be blank") - assert_instance_of ::Hash, person.errors.to_hash + person.errors.add(:name, "cannot be blank") + person.errors.add(:name, "cannot be nil") + assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages end - test 'full_messages should return an array of error messages, with the attribute name included' do + test "full_messages_for contains all the error messages for the given attribute" do person = Person.new - person.errors.add(:name, "can not be blank") - person.errors.add(:name, "can not be nil") - assert_equal ["name can not be blank", "name can not be nil"], person.errors.full_messages + person.errors.add(:name, "cannot be blank") + person.errors.add(:name, "cannot be nil") + assert_equal ["name cannot be blank", "name cannot be nil"], person.errors.full_messages_for(:name) end - test 'full_message should return the given message if attribute equals :base' do + test "full_messages_for does not contain error messages from other attributes" do + person = Person.new + person.errors.add(:name, "cannot be blank") + person.errors.add(:email, "cannot be blank") + assert_equal ["name cannot be blank"], person.errors.full_messages_for(:name) + end + + test "full_messages_for returns an empty list in case there are no errors for the given attribute" do + person = Person.new + person.errors.add(:name, "cannot be blank") + assert_equal [], person.errors.full_messages_for(:email) + end + + test "full_message returns the given message when attribute is :base" do person = Person.new assert_equal "press the button", person.errors.full_message(:base, "press the button") end - test 'full_message should return the given message with the attribute name included' do + test "full_message returns the given message with the attribute name included" do person = Person.new - assert_equal "name can not be blank", person.errors.full_message(:name, "can not be blank") + assert_equal "name cannot be blank", person.errors.full_message(:name, "cannot be blank") + assert_equal "name_test cannot be blank", person.errors.full_message(:name_test, "cannot be blank") end - test 'should return a JSON hash representation of the errors' do + test "as_json creates a json formatted representation of the errors hash" do person = Person.new - person.errors.add(:name, "can not be blank") - person.errors.add(:name, "can not be nil") - person.errors.add(:email, "is invalid") - hash = person.errors.as_json - assert_equal ["can not be blank", "can not be nil"], hash[:name] - assert_equal ["is invalid"], hash[:email] + person.validate! + + assert_equal({ name: ["cannot be nil"] }, person.errors.as_json) end - test 'should return a JSON hash representation of the errors with full messages' do + test "as_json with :full_messages option creates a json formatted representation of the errors containing complete messages" do person = Person.new - person.errors.add(:name, "can not be blank") - person.errors.add(:name, "can not be nil") - person.errors.add(:email, "is invalid") - hash = person.errors.as_json(:full_messages => true) - assert_equal ["name can not be blank", "name can not be nil"], hash[:name] - assert_equal ["email is invalid"], hash[:email] + person.validate! + + assert_equal({ name: ["name cannot be nil"] }, person.errors.as_json(full_messages: true)) end - test "generate_message should work without i18n_scope" do + test "generate_message works without i18n_scope" do person = Person.new assert !Person.respond_to?(:i18n_scope) assert_nothing_raised { @@ -270,8 +294,15 @@ class ErrorsTest < ActiveModel::TestCase test "add_on_empty generates message with custom default message" do person = Person.new - person.errors.expects(:generate_message).with(:name, :empty, {:message => 'custom'}) - person.errors.add_on_empty :name, :message => 'custom' + person.errors.expects(:generate_message).with(:name, :empty, { message: 'custom' }) + person.errors.add_on_empty :name, message: 'custom' + end + + test "add_on_empty generates message with empty string value" do + person = Person.new + person.name = '' + person.errors.expects(:generate_message).with(:name, :empty, {}) + person.errors.add_on_empty :name end test "add_on_blank generates message" do @@ -289,7 +320,7 @@ class ErrorsTest < ActiveModel::TestCase test "add_on_blank generates message with custom default message" do person = Person.new - person.errors.expects(:generate_message).with(:name, :blank, {:message => 'custom'}) - person.errors.add_on_blank :name, :message => 'custom' + person.errors.expects(:generate_message).with(:name, :blank, { message: 'custom' }) + person.errors.add_on_blank :name, message: 'custom' end end diff --git a/activemodel/test/cases/helper.rb b/activemodel/test/cases/helper.rb index 7a63674757..4ce6103593 100644 --- a/activemodel/test/cases/helper.rb +++ b/activemodel/test/cases/helper.rb @@ -7,4 +7,14 @@ require 'active_support/core_ext/string/access' # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true +# Disable available locale checks to avoid warnings running the test suite. +I18n.enforce_available_locales = false + require 'active_support/testing/autorun' + +require 'mocha/setup' # FIXME: stop using mocha + +# FIXME: we have tests that depend on run order, we should fix that and +# remove this method call. +require 'active_support/test_case' +ActiveSupport::TestCase.test_order = :sorted diff --git a/activemodel/test/cases/model_test.rb b/activemodel/test/cases/model_test.rb index 588d8e661e..ee0fa26546 100644 --- a/activemodel/test/cases/model_test.rb +++ b/activemodel/test/cases/model_test.rb @@ -3,7 +3,30 @@ require 'cases/helper' class ModelTest < ActiveModel::TestCase include ActiveModel::Lint::Tests + module DefaultValue + def self.included(klass) + klass.class_eval { attr_accessor :hello } + end + + def initialize(*args) + @attr ||= 'default value' + super + end + end + class BasicModel + include DefaultValue + include ActiveModel::Model + attr_accessor :attr + end + + class BasicModelWithReversedMixins + include ActiveModel::Model + include DefaultValue + attr_accessor :attr + end + + class SimpleModel include ActiveModel::Model attr_accessor :attr end @@ -13,20 +36,40 @@ class ModelTest < ActiveModel::TestCase end def test_initialize_with_params - object = BasicModel.new(:attr => "value") - assert_equal object.attr, "value" + object = BasicModel.new(attr: "value") + assert_equal "value", object.attr + end + + def test_initialize_with_params_and_mixins_reversed + object = BasicModelWithReversedMixins.new(attr: "value") + assert_equal "value", object.attr end def test_initialize_with_nil_or_empty_hash_params_does_not_explode assert_nothing_raised do BasicModel.new() - BasicModel.new nil + BasicModel.new(nil) BasicModel.new({}) + SimpleModel.new(attr: 'value') end end def test_persisted_is_always_false - object = BasicModel.new(:attr => "value") + object = BasicModel.new(attr: "value") assert object.persisted? == false end + + def test_mixin_inclusion_chain + object = BasicModel.new + assert_equal 'default value', object.attr + end + + def test_mixin_initializer_when_args_exist + object = BasicModel.new(hello: 'world') + assert_equal 'world', object.hello + end + + def test_mixin_initializer_when_args_dont_exist + assert_raises(NoMethodError) { SimpleModel.new(hello: 'world') } + end end diff --git a/activemodel/test/cases/naming_test.rb b/activemodel/test/cases/naming_test.rb index 38ba3cc152..7b8287edbf 100644 --- a/activemodel/test/cases/naming_test.rb +++ b/activemodel/test/cases/naming_test.rb @@ -245,7 +245,7 @@ class NamingHelpersTest < ActiveModel::TestCase end def test_uncountable - assert uncountable?(@uncountable), "Expected 'sheep' to be uncoutable" + assert uncountable?(@uncountable), "Expected 'sheep' to be uncountable" assert !uncountable?(@klass), "Expected 'contact' to be countable" end @@ -272,3 +272,9 @@ class NameWithAnonymousClassTest < ActiveModel::TestCase assert_equal "Anonymous", model_name end end + +class NamingMethodDelegationTest < ActiveModel::TestCase + def test_model_name + assert_equal Blog::Post.model_name, Blog::Post.new.model_name + end +end diff --git a/activemodel/test/cases/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index a0cd1402b1..96b3b07e50 100644 --- a/activemodel/test/cases/railtie_test.rb +++ b/activemodel/test/cases/railtie_test.rb @@ -7,9 +7,12 @@ class RailtieTest < ActiveModel::TestCase def setup require 'active_model/railtie' + # Set a fake logger to avoid creating the log directory automatically + fake_logger = Logger.new(nil) + @app ||= Class.new(::Rails::Application) do config.eager_load = false - config.logger = Logger.new(STDOUT) + config.logger = fake_logger end end diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb index 7783bb25d5..6d56c8344a 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -1,78 +1,192 @@ require 'cases/helper' require 'models/user' -require 'models/oauthed_user' require 'models/visitor' -require 'models/administrator' class SecurePasswordTest < ActiveModel::TestCase setup do + # Used only to speed up tests + @original_min_cost = ActiveModel::SecurePassword.min_cost ActiveModel::SecurePassword.min_cost = true @user = User.new @visitor = Visitor.new - @oauthed_user = OauthedUser.new + + # Simulate loading an existing user from the DB + @existing_user = User.new + @existing_user.password_digest = BCrypt::Password.create('password', cost: BCrypt::Engine::MIN_COST) end teardown do - ActiveModel::SecurePassword.min_cost = false + ActiveModel::SecurePassword.min_cost = @original_min_cost + end + + test "automatically include ActiveModel::Validations when validations are enabled" do + assert_respond_to @user, :valid? end - test "blank password" do - @user.password = @visitor.password = '' + test "don't include ActiveModel::Validations when validations are disabled" do + assert_not_respond_to @visitor, :valid? + end + + test "create a new user with validations and valid password/confirmation" do + @user.password = 'password' + @user.password_confirmation = 'password' + + assert @user.valid?(:create), 'user should be valid' + + @user.password = 'a' * 72 + @user.password_confirmation = 'a' * 72 + + assert @user.valid?(:create), 'user should be valid' + end + + test "create a new user with validation and a spaces only password" do + @user.password = ' ' * 72 + assert @user.valid?(:create), 'user should be valid' + end + + test "create a new user with validation and a blank password" do + @user.password = '' assert !@user.valid?(:create), 'user should be invalid' - assert @visitor.valid?(:create), 'visitor should be valid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] end - test "nil password" do - @user.password = @visitor.password = nil + test "create a new user with validation and a nil password" do + @user.password = nil assert !@user.valid?(:create), 'user should be invalid' - assert @visitor.valid?(:create), 'visitor should be valid' + assert_equal 1, @user.errors.count + assert_equal ["can't be blank"], @user.errors[:password] end - test "blank password doesn't override previous password" do - @user.password = 'test' - @user.password = '' - assert_equal @user.password, 'test' + test 'create a new user with validation and password length greater than 72' do + @user.password = 'a' * 73 + @user.password_confirmation = 'a' * 73 + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["is too long (maximum is 72 characters)"], @user.errors[:password] + end + + test "create a new user with validation and a blank password confirmation" do + @user.password = 'password' + @user.password_confirmation = '' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] end - test "password must be present" do - assert !@user.valid?(:create) - assert_equal 1, @user.errors.size + test "create a new user with validation and a nil password confirmation" do + @user.password = 'password' + @user.password_confirmation = nil + assert @user.valid?(:create), 'user should be valid' end - test "match confirmation" do - @user.password = @visitor.password = "thiswillberight" - @user.password_confirmation = @visitor.password_confirmation = "wrong" + test "create a new user with validation and an incorrect password confirmation" do + @user.password = 'password' + @user.password_confirmation = 'something else' + assert !@user.valid?(:create), 'user should be invalid' + assert_equal 1, @user.errors.count + assert_equal ["doesn't match Password"], @user.errors[:password_confirmation] + end + + test "update an existing user with validation and no change in password" do + assert @existing_user.valid?(:update), 'user should be valid' + end - assert !@user.valid? - assert @visitor.valid? + test "update an existing user with validations and valid password/confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'password' - @user.password_confirmation = "thiswillberight" + assert @existing_user.valid?(:update), 'user should be valid' - assert @user.valid? + @existing_user.password = 'a' * 72 + @existing_user.password_confirmation = 'a' * 72 + + assert @existing_user.valid?(:update), 'user should be valid' end - test "authenticate" do - @user.password = "secret" + test "updating an existing user with validation and a blank password" do + @existing_user.password = '' + assert @existing_user.valid?(:update), 'user should be valid' + end - assert !@user.authenticate("wrong") - assert @user.authenticate("secret") + test "updating an existing user with validation and a spaces only password" do + @user.password = ' ' * 72 + assert @user.valid?(:update), 'user should be valid' end - test "User should not be created with blank digest" do - assert_raise RuntimeError do - @user.run_callbacks :create - end - @user.password = "supersecretpassword" - assert_nothing_raised do - @user.run_callbacks :create - end + test "updating an existing user with validation and a blank password and password_confirmation" do + @existing_user.password = '' + @existing_user.password_confirmation = '' + assert @existing_user.valid?(:update), 'user should be valid' end - test "Oauthed user can be created with blank digest" do - assert_nothing_raised do - @oauthed_user.run_callbacks :create - end + test "updating an existing user with validation and a nil password" do + @existing_user.password = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test 'updating an existing user with validation and password length greater than 72' do + @existing_user.password = 'a' * 73 + @existing_user.password_confirmation = 'a' * 73 + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["is too long (maximum is 72 characters)"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a blank password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a nil password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = nil + assert @existing_user.valid?(:update), 'user should be valid' + end + + test "updating an existing user with validation and an incorrect password confirmation" do + @existing_user.password = 'password' + @existing_user.password_confirmation = 'something else' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["doesn't match Password"], @existing_user.errors[:password_confirmation] + end + + test "updating an existing user with validation and a blank password digest" do + @existing_user.password_digest = '' + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "updating an existing user with validation and a nil password digest" do + @existing_user.password_digest = nil + assert !@existing_user.valid?(:update), 'user should be invalid' + assert_equal 1, @existing_user.errors.count + assert_equal ["can't be blank"], @existing_user.errors[:password] + end + + test "setting a blank password should not change an existing password" do + @existing_user.password = '' + assert @existing_user.password_digest == 'password' + end + + test "setting a nil password should clear an existing password" do + @existing_user.password = nil + assert_equal nil, @existing_user.password_digest + end + + test "authenticate" do + @user.password = "secret" + + assert !@user.authenticate("wrong") + assert @user.authenticate("secret") end test "Password digest cost defaults to bcrypt default cost when min_cost is false" do @@ -82,6 +196,19 @@ class SecurePasswordTest < ActiveModel::TestCase assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost end + test "Password digest cost honors bcrypt cost attribute when min_cost is false" do + begin + original_bcrypt_cost = BCrypt::Engine.cost + ActiveModel::SecurePassword.min_cost = false + BCrypt::Engine.cost = 5 + + @user.password = "secret" + assert_equal BCrypt::Engine.cost, @user.password_digest.cost + ensure + BCrypt::Engine.cost = original_bcrypt_cost + end + end + test "Password digest cost can be set to bcrypt min cost to speed up tests" do ActiveModel::SecurePassword.min_cost = true diff --git a/activemodel/test/cases/serialization_test.rb b/activemodel/test/cases/serialization_test.rb index d2ba9fd95d..4ae41aa19c 100644 --- a/activemodel/test/cases/serialization_test.rb +++ b/activemodel/test/cases/serialization_test.rb @@ -49,32 +49,32 @@ class SerializationTest < ActiveModel::TestCase def test_method_serializable_hash_should_work_with_only_option expected = {"name"=>"David"} - assert_equal expected, @user.serializable_hash(:only => [:name]) + 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]) + 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]) + 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]) + 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]) + 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]) + assert_equal expected, @user.serializable_hash(methods: [:bar]) end def test_should_use_read_attribute_for_serialization @@ -83,26 +83,26 @@ class SerializationTest < ActiveModel::TestCase end expected = { "name" => "Jon" } - assert_equal expected, @user.serializable_hash(:only => :name) + assert_equal expected, @user.serializable_hash(only: :name) end def test_include_option_with_singular_association expected = {"name"=>"David", "gender"=>"male", "email"=>"david@example.com", "address"=>{"street"=>"123 Lane", "city"=>"Springfield", "state"=>"CA", "zip"=>11111}} - assert_equal expected, @user.serializable_hash(:include => :address) + 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) + 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) + assert_equal expected, @user.serializable_hash(include: :friends) end class FriendList @@ -120,7 +120,7 @@ class SerializationTest < ActiveModel::TestCase 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) + assert_equal expected, @user.serializable_hash(include: :friends) end def test_multiple_includes @@ -128,13 +128,13 @@ class SerializationTest < ActiveModel::TestCase "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]) + 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"}}) + assert_equal expected, @user.serializable_hash(include: { address: { only: "street" } }) end def test_nested_include @@ -143,19 +143,19 @@ class SerializationTest < ActiveModel::TestCase "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}}) + assert_equal expected, @user.serializable_hash(except: :gender, include: { friends: { except: :gender } }) end def test_multiple_includes_with_options @@ -163,6 +163,6 @@ class SerializationTest < ActiveModel::TestCase "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]) + 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 9134c4980c..e2eb91eeb0 100644 --- a/activemodel/test/cases/serializers/json_serialization_test.rb +++ b/activemodel/test/cases/serializers/json_serialization_test.rb @@ -1,25 +1,7 @@ require 'cases/helper' require 'models/contact' -require 'models/automobile' require 'active_support/core_ext/object/instance_variables' -class Contact - include ActiveModel::Serializers::JSON - include ActiveModel::Validations - - def attributes=(hash) - hash.each do |k, v| - instance_variable_set("@#{k}", v) - end - end - - remove_method :attributes if method_defined?(:attributes) - - def attributes - instance_values - end -end - class JsonSerializationTest < ActiveModel::TestCase def setup @contact = Contact.new @@ -30,11 +12,6 @@ class JsonSerializationTest < ActiveModel::TestCase @contact.preferences = { 'shows' => 'anime' } end - def teardown - # set to the default value - Contact.include_root_in_json = false - end - test "should not include root in json (class method)" do json = @contact.to_json @@ -47,19 +24,25 @@ class JsonSerializationTest < ActiveModel::TestCase end test "should include root in json if include_root_in_json is true" do - Contact.include_root_in_json = true - json = @contact.to_json - - assert_match %r{^\{"contact":\{}, json - assert_match %r{"name":"Konata Izumi"}, json - assert_match %r{"age":16}, json - assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) - assert_match %r{"awesome":true}, json - assert_match %r{"preferences":\{"shows":"anime"\}}, json + begin + original_include_root_in_json = Contact.include_root_in_json + Contact.include_root_in_json = true + json = @contact.to_json + + assert_match %r{^\{"contact":\{}, json + assert_match %r{"name":"Konata Izumi"}, json + assert_match %r{"age":16}, json + assert json.include?(%("created_at":#{ActiveSupport::JSON.encode(Time.utc(2006, 8, 1))})) + assert_match %r{"awesome":true}, json + assert_match %r{"preferences":\{"shows":"anime"\}}, json + ensure + Contact.include_root_in_json = original_include_root_in_json + end end test "should include root in json (option) even if the default is set to false" do json = @contact.to_json(root: true) + assert_match %r{^\{"contact":\{}, json end @@ -91,7 +74,7 @@ class JsonSerializationTest < ActiveModel::TestCase end test "should allow attribute filtering with only" do - json = @contact.to_json(:only => [:name, :age]) + json = @contact.to_json(only: [:name, :age]) assert_match %r{"name":"Konata Izumi"}, json assert_match %r{"age":16}, json @@ -145,22 +128,21 @@ class JsonSerializationTest < ActiveModel::TestCase end test "as_json should return a hash if include_root_in_json is true" do - Contact.include_root_in_json = true - json = @contact.as_json - - assert_kind_of Hash, json - assert_kind_of Hash, json['contact'] - %w(name age created_at awesome preferences).each do |field| - assert_equal @contact.send(field), json['contact'][field] + begin + original_include_root_in_json = Contact.include_root_in_json + Contact.include_root_in_json = true + json = @contact.as_json + + assert_kind_of Hash, json + assert_kind_of Hash, json['contact'] + %w(name age created_at awesome preferences).each do |field| + assert_equal @contact.send(field), json['contact'][field] + end + ensure + Contact.include_root_in_json = original_include_root_in_json end end - test "as_json should keep the default order in the hash" do - json = @contact.as_json - - assert_equal %w(name age created_at awesome preferences), json.keys - end - test "from_json should work without a root (class attribute)" do json = @contact.to_json result = Contact.new.from_json(json) @@ -204,7 +186,7 @@ class JsonSerializationTest < ActiveModel::TestCase assert_no_match %r{"preferences":}, json end - test "custom as_json options should be extendible" do + test "custom as_json options should be extensible" do def @contact.as_json(options = {}); super(options.merge(only: [:name])); end json = @contact.to_json diff --git a/activemodel/test/cases/serializers/xml_serialization_test.rb b/activemodel/test/cases/serializers/xml_serialization_test.rb index 99a9c1fe33..22fca5bd17 100644 --- a/activemodel/test/cases/serializers/xml_serialization_test.rb +++ b/activemodel/test/cases/serializers/xml_serialization_test.rb @@ -3,18 +3,6 @@ require 'models/contact' require 'active_support/core_ext/object/instance_variables' require 'ostruct' -class Contact - include ActiveModel::Serializers::Xml - - attr_accessor :address, :friends, :contact - - remove_method :attributes if method_defined?(:attributes) - - def attributes - instance_values.except("address", "friends", "contact") - end -end - module Admin class Contact < ::Contact end @@ -35,7 +23,7 @@ end class SerializableContact < Contact def serializable_hash(options={}) - super(options.merge(:only => [:name, :age])) + super(options.merge(only: [:name, :age])) end end @@ -50,60 +38,55 @@ class XmlSerializationTest < ActiveModel::TestCase customer.name = "John" @contact.preferences = customer @contact.address = Address.new - @contact.address.street = "123 Lane" @contact.address.city = "Springfield" - @contact.address.state = "CA" - @contact.address.zip = 11111 @contact.address.apt_number = 35 @contact.friends = [Contact.new, Contact.new] - @related_contact = SerializableContact.new - @related_contact.name = "related" - @contact.contact = @related_contact + @contact.contact = SerializableContact.new end test "should serialize default root" do - @xml = @contact.to_xml - assert_match %r{^<contact>}, @xml - assert_match %r{</contact>$}, @xml + xml = @contact.to_xml + assert_match %r{^<contact>}, xml + assert_match %r{</contact>$}, xml end test "should serialize namespaced root" do - @xml = Admin::Contact.new(@contact.attributes).to_xml - assert_match %r{^<contact>}, @xml - assert_match %r{</contact>$}, @xml + xml = Admin::Contact.new(@contact.attributes).to_xml + assert_match %r{^<contact>}, xml + assert_match %r{</contact>$}, xml end test "should serialize default root with namespace" do - @xml = @contact.to_xml :namespace => "http://xml.rubyonrails.org/contact" - assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, @xml - assert_match %r{</contact>$}, @xml + xml = @contact.to_xml namespace: "http://xml.rubyonrails.org/contact" + assert_match %r{^<contact xmlns="http://xml.rubyonrails.org/contact">}, xml + assert_match %r{</contact>$}, xml end test "should serialize custom root" do - @xml = @contact.to_xml :root => 'xml_contact' - assert_match %r{^<xml-contact>}, @xml - assert_match %r{</xml-contact>$}, @xml + xml = @contact.to_xml root: 'xml_contact' + assert_match %r{^<xml-contact>}, xml + assert_match %r{</xml-contact>$}, xml end test "should allow undasherized tags" do - @xml = @contact.to_xml :root => 'xml_contact', :dasherize => false - assert_match %r{^<xml_contact>}, @xml - assert_match %r{</xml_contact>$}, @xml - assert_match %r{<created_at}, @xml + xml = @contact.to_xml root: 'xml_contact', dasherize: false + assert_match %r{^<xml_contact>}, xml + assert_match %r{</xml_contact>$}, xml + assert_match %r{<created_at}, xml end test "should allow camelized tags" do - @xml = @contact.to_xml :root => 'xml_contact', :camelize => true - assert_match %r{^<XmlContact>}, @xml - assert_match %r{</XmlContact>$}, @xml - assert_match %r{<CreatedAt}, @xml + xml = @contact.to_xml root: 'xml_contact', camelize: true + assert_match %r{^<XmlContact>}, xml + assert_match %r{</XmlContact>$}, xml + assert_match %r{<CreatedAt}, xml end test "should allow lower-camelized tags" do - @xml = @contact.to_xml :root => 'xml_contact', :camelize => :lower - assert_match %r{^<xmlContact>}, @xml - assert_match %r{</xmlContact>$}, @xml - assert_match %r{<createdAt}, @xml + xml = @contact.to_xml root: 'xml_contact', camelize: :lower + assert_match %r{^<xmlContact>}, xml + assert_match %r{</xmlContact>$}, xml + assert_match %r{<createdAt}, xml end test "should use serializable hash" do @@ -111,22 +94,22 @@ class XmlSerializationTest < ActiveModel::TestCase @contact.name = 'aaron stack' @contact.age = 25 - @xml = @contact.to_xml - assert_match %r{<name>aaron stack</name>}, @xml - assert_match %r{<age type="integer">25</age>}, @xml - assert_no_match %r{<awesome>}, @xml + xml = @contact.to_xml + assert_match %r{<name>aaron stack</name>}, xml + assert_match %r{<age type="integer">25</age>}, xml + assert_no_match %r{<awesome>}, xml end test "should allow skipped types" do - @xml = @contact.to_xml :skip_types => true - assert_match %r{<age>25</age>}, @xml + xml = @contact.to_xml skip_types: true + assert_match %r{<age>25</age>}, xml end test "should include yielded additions" do - @xml = @contact.to_xml do |xml| + xml_output = @contact.to_xml do |xml| xml.creator "David" end - assert_match %r{<creator>David</creator>}, @xml + assert_match %r{<creator>David</creator>}, xml_output end test "should serialize string" do @@ -134,7 +117,7 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize nil" do - assert_match %r{<pseudonyms nil=\"true\"/>}, @contact.to_xml(:methods => :pseudonyms) + assert_match %r{<pseudonyms nil="true"/>}, @contact.to_xml(methods: :pseudonyms) end test "should serialize integer" do @@ -142,50 +125,50 @@ class XmlSerializationTest < ActiveModel::TestCase end test "should serialize datetime" do - assert_match %r{<created-at type=\"dateTime\">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml + assert_match %r{<created-at type="dateTime">2006-08-01T00:00:00Z</created-at>}, @contact.to_xml end test "should serialize boolean" do - assert_match %r{<awesome type=\"boolean\">false</awesome>}, @contact.to_xml + assert_match %r{<awesome type="boolean">false</awesome>}, @contact.to_xml end test "should serialize array" do - assert_match %r{<social type=\"array\">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(:methods => :social) + assert_match %r{<social type="array">\s*<social>twitter</social>\s*<social>github</social>\s*</social>}, @contact.to_xml(methods: :social) end test "should serialize hash" do - assert_match %r{<network>\s*<git type=\"symbol\">github</git>\s*</network>}, @contact.to_xml(:methods => :network) + assert_match %r{<network>\s*<git type="symbol">github</git>\s*</network>}, @contact.to_xml(methods: :network) end test "should serialize yaml" do - assert_match %r{<preferences type=\"yaml\">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml + assert_match %r{<preferences type="yaml">--- !ruby/struct:Customer(\s*)\nname: John\n</preferences>}, @contact.to_xml end test "should call proc on object" do proc = Proc.new { |options| options[:builder].tag!('nationality', 'unknown') } - xml = @contact.to_xml(:procs => [ proc ]) + xml = @contact.to_xml(procs: [ proc ]) assert_match %r{<nationality>unknown</nationality>}, xml end - test 'should supply serializable to second proc argument' do + test "should supply serializable to second proc argument" do proc = Proc.new { |options, record| options[:builder].tag!('name-reverse', record.name.reverse) } - xml = @contact.to_xml(:procs => [ proc ]) + xml = @contact.to_xml(procs: [ proc ]) assert_match %r{<name-reverse>kcats noraa</name-reverse>}, xml end test "should serialize string correctly when type passed" do - xml = @contact.to_xml :type => 'Contact' + xml = @contact.to_xml type: 'Contact' assert_match %r{<contact type="Contact">}, xml assert_match %r{<name>aaron stack</name>}, xml end test "include option with singular association" do - xml = @contact.to_xml :include => :address, :indent => 0 - assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + xml = @contact.to_xml include: :address, indent: 0 + assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true)) end test "include option with plural association" do - xml = @contact.to_xml :include => :friends, :indent => 0 + xml = @contact.to_xml include: :friends, indent: 0 assert_match %r{<friends type="array">}, xml assert_match %r{<friend type="Contact">}, xml end @@ -202,60 +185,60 @@ class XmlSerializationTest < ActiveModel::TestCase test "include option with ary" do @contact.friends = FriendList.new(@contact.friends) - xml = @contact.to_xml :include => :friends, :indent => 0 + xml = @contact.to_xml include: :friends, indent: 0 assert_match %r{<friends type="array">}, xml assert_match %r{<friend type="Contact">}, xml end test "multiple includes" do - xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => [ :address, :friends ] - assert xml.include?(@contact.address.to_xml(:indent => 0, :skip_instruct => true)) + xml = @contact.to_xml indent: 0, skip_instruct: true, include: [ :address, :friends ] + assert xml.include?(@contact.address.to_xml(indent: 0, skip_instruct: true)) assert_match %r{<friends type="array">}, xml assert_match %r{<friend type="Contact">}, xml end test "include with options" do - xml = @contact.to_xml :indent => 0, :skip_instruct => true, :include => { :address => { :only => :city } } + xml = @contact.to_xml indent: 0, skip_instruct: true, include: { address: { only: :city } } assert xml.include?(%(><address><city>Springfield</city></address>)) end test "propagates skip_types option to included associations" do - xml = @contact.to_xml :include => :friends, :indent => 0, :skip_types => true + xml = @contact.to_xml include: :friends, indent: 0, skip_types: true assert_match %r{<friends>}, xml assert_match %r{<friend>}, xml end test "propagates skip-types option to included associations and attributes" do - xml = @contact.to_xml :skip_types => true, :include => :address, :indent => 0 + xml = @contact.to_xml skip_types: true, include: :address, indent: 0 assert_match %r{<address>}, xml assert_match %r{<apt-number>}, xml end test "propagates camelize option to included associations and attributes" do - xml = @contact.to_xml :camelize => true, :include => :address, :indent => 0 + xml = @contact.to_xml camelize: true, include: :address, indent: 0 assert_match %r{<Address>}, xml assert_match %r{<AptNumber type="integer">}, xml end test "propagates dasherize option to included associations and attributes" do - xml = @contact.to_xml :dasherize => false, :include => :address, :indent => 0 + xml = @contact.to_xml dasherize: false, include: :address, indent: 0 assert_match %r{<apt_number type="integer">}, xml end test "don't propagate skip_types if skip_types is defined at the included association level" do - xml = @contact.to_xml :skip_types => true, :include => { :address => { :skip_types => false } }, :indent => 0 + xml = @contact.to_xml skip_types: true, include: { address: { skip_types: false } }, indent: 0 assert_match %r{<address>}, xml assert_match %r{<apt-number type="integer">}, xml end test "don't propagate camelize if camelize is defined at the included association level" do - xml = @contact.to_xml :camelize => true, :include => { :address => { :camelize => false } }, :indent => 0 + xml = @contact.to_xml camelize: true, include: { address: { camelize: false } }, indent: 0 assert_match %r{<address>}, xml assert_match %r{<apt-number type="integer">}, xml end test "don't propagate dasherize if dasherize is defined at the included association level" do - xml = @contact.to_xml :dasherize => false, :include => { :address => { :dasherize => true } }, :indent => 0 + xml = @contact.to_xml dasherize: false, include: { address: { dasherize: true } }, indent: 0 assert_match %r{<address>}, xml assert_match %r{<apt-number type="integer">}, xml end diff --git a/activemodel/test/cases/translation_test.rb b/activemodel/test/cases/translation_test.rb index fd833cdd06..cedc812ec7 100644 --- a/activemodel/test/cases/translation_test.rb +++ b/activemodel/test/cases/translation_test.rb @@ -7,23 +7,27 @@ class ActiveModelI18nTests < ActiveModel::TestCase I18n.backend = I18n::Backend::Simple.new end + def teardown + I18n.backend.reload! + end + def test_translated_model_attributes - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } } + I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute' } } } assert_equal 'person name attribute', Person.human_attribute_name('name') end def test_translated_model_attributes_with_default - I18n.backend.store_translations 'en', :attributes => { :name => 'name default attribute' } + I18n.backend.store_translations 'en', attributes: { name: 'name default attribute' } assert_equal 'name default attribute', Person.human_attribute_name('name') end def test_translated_model_attributes_using_default_option - assert_equal 'name default attribute', Person.human_attribute_name('name', :default => "name default attribute") + assert_equal 'name default attribute', Person.human_attribute_name('name', default: "name default attribute") end def test_translated_model_attributes_using_default_option_as_symbol - I18n.backend.store_translations 'en', :default_name => 'name default attribute' - assert_equal 'name default attribute', Person.human_attribute_name('name', :default => :default_name) + I18n.backend.store_translations 'en', default_name: 'name default attribute' + assert_equal 'name default attribute', Person.human_attribute_name('name', default: :default_name) end def test_translated_model_attributes_falling_back_to_default @@ -31,71 +35,74 @@ class ActiveModelI18nTests < ActiveModel::TestCase end def test_translated_model_attributes_using_default_option_as_symbol_and_falling_back_to_default - assert_equal 'Name', Person.human_attribute_name('name', :default => :default_name) + assert_equal 'Name', Person.human_attribute_name('name', default: :default_name) end def test_translated_model_attributes_with_symbols - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } } + I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } assert_equal 'person name attribute', Person.human_attribute_name(:name) end def test_translated_model_attributes_with_ancestor - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:child => {:name => 'child name attribute'} } } + I18n.backend.store_translations 'en', activemodel: { attributes: { child: { name: 'child name attribute'} } } assert_equal 'child name attribute', Child.human_attribute_name('name') end def test_translated_model_attributes_with_ancestors_fallback - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:name => 'person name attribute'} } } + I18n.backend.store_translations 'en', activemodel: { attributes: { person: { name: 'person name attribute'} } } assert_equal 'person name attribute', Child.human_attribute_name('name') end def test_translated_model_attributes_with_attribute_matching_namespaced_model_name - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:person => {:gender => 'person gender'}, :"person/gender" => {:attribute => 'person gender attribute'}}} + I18n.backend.store_translations 'en', activemodel: { attributes: { + person: { gender: 'person gender'}, + :"person/gender" => { attribute: 'person gender attribute' } + } } assert_equal 'person gender', Person.human_attribute_name('gender') assert_equal 'person gender attribute', Person::Gender.human_attribute_name('attribute') end def test_translated_deeply_nested_model_attributes - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/contacts/addresses" => {:street => 'Deeply Nested Address Street'}}} + I18n.backend.store_translations 'en', activemodel: { attributes: { :"person/contacts/addresses" => { street: 'Deeply Nested Address Street' } } } assert_equal 'Deeply Nested Address Street', Person.human_attribute_name('contacts.addresses.street') end def test_translated_nested_model_attributes - I18n.backend.store_translations 'en', :activemodel => {:attributes => {:"person/addresses" => {:street => 'Person Address Street'}}} + 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'}}} + 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'} } + I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } assert_equal 'person model', Person.model_name.human end def test_translated_model_names_with_sti - I18n.backend.store_translations 'en', :activemodel => {:models => {:child => 'child model'} } + I18n.backend.store_translations 'en', activemodel: { models: { child: 'child model' } } assert_equal 'child model', Child.model_name.human end def test_translated_model_names_with_ancestors_fallback - I18n.backend.store_translations 'en', :activemodel => {:models => {:person => 'person model'} } + I18n.backend.store_translations 'en', activemodel: { models: { person: 'person model' } } assert_equal 'person model', Child.model_name.human end def test_human_does_not_modify_options - options = { :default => 'person model' } + options = { default: 'person model' } Person.model_name.human(options) - assert_equal({ :default => 'person model' }, options) + assert_equal({ default: 'person model' }, options) end def test_human_attribute_name_does_not_modify_options - options = { :default => 'Cool gender' } + options = { default: 'Cool gender' } Person.human_attribute_name('gender', options) - assert_equal({ :default => 'Cool gender' }, options) + assert_equal({ default: 'Cool gender' }, options) end end diff --git a/activemodel/test/cases/validations/absence_validation_test.rb b/activemodel/test/cases/validations/absence_validation_test.rb index c05d71de5a..ebfe1cf4e4 100644 --- a/activemodel/test/cases/validations/absence_validation_test.rb +++ b/activemodel/test/cases/validations/absence_validation_test.rb @@ -6,12 +6,12 @@ require 'models/custom_reader' class AbsenceValidationTest < ActiveModel::TestCase teardown do - Topic.reset_callbacks(:validate) - Person.reset_callbacks(:validate) - CustomReader.reset_callbacks(:validate) + Topic.clear_validators! + Person.clear_validators! + CustomReader.clear_validators! end - def test_validate_absences + def test_validates_absence_of Topic.validates_absence_of(:title, :content) t = Topic.new t.title = "foo" @@ -23,11 +23,12 @@ class AbsenceValidationTest < ActiveModel::TestCase t.content = "something" assert t.invalid? assert_equal ["must be blank"], t.errors[:content] + assert_equal [], t.errors[:title] t.content = "" assert t.valid? end - def test_accepts_array_arguments + def test_validates_absence_of_with_array_arguments Topic.validates_absence_of %w(title content) t = Topic.new t.title = "foo" @@ -37,7 +38,7 @@ class AbsenceValidationTest < ActiveModel::TestCase assert_equal ["must be blank"], t.errors[:content] end - def test_validates_acceptance_of_with_custom_error_using_quotes + def test_validates_absence_of_with_custom_error_using_quotes Person.validates_absence_of :karma, message: "This string contains 'single' and \"double\" quotes" p = Person.new p.karma = "good" diff --git a/activemodel/test/cases/validations/acceptance_validation_test.rb b/activemodel/test/cases/validations/acceptance_validation_test.rb index de04e11258..e78aa1adaf 100644 --- a/activemodel/test/cases/validations/acceptance_validation_test.rb +++ b/activemodel/test/cases/validations/acceptance_validation_test.rb @@ -8,7 +8,7 @@ require 'models/person' class AcceptanceValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_terms_of_service_agreement_no_acceptance @@ -30,7 +30,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase end def test_eula - Topic.validates_acceptance_of(:eula, :message => "must be abided") + Topic.validates_acceptance_of(:eula, message: "must be abided") t = Topic.new("title" => "We should be confirmed","eula" => "") assert t.invalid? @@ -41,7 +41,7 @@ class AcceptanceValidationTest < ActiveModel::TestCase end def test_terms_of_service_agreement_with_accept_value - Topic.validates_acceptance_of(:terms_of_service, :accept => "I agree.") + Topic.validates_acceptance_of(:terms_of_service, accept: "I agree.") t = Topic.new("title" => "We should be confirmed", "terms_of_service" => "") assert t.invalid? @@ -63,6 +63,6 @@ class AcceptanceValidationTest < ActiveModel::TestCase p.karma = "1" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/callbacks_test.rb b/activemodel/test/cases/validations/callbacks_test.rb index 0015b3c196..5d6d48b824 100644 --- a/activemodel/test/cases/validations/callbacks_test.rb +++ b/activemodel/test/cases/validations/callbacks_test.rb @@ -30,18 +30,44 @@ class DogWithTwoValidators < Dog before_validation { self.history << 'before_validation_marker2' } end -class DogValidatorReturningFalse < Dog +class DogBeforeValidatorReturningFalse < Dog before_validation { false } before_validation { self.history << 'before_validation_marker2' } end +class DogAfterValidatorReturningFalse < Dog + after_validation { false } + after_validation { self.history << 'after_validation_marker' } +end + class DogWithMissingName < Dog before_validation { self.history << 'before_validation_marker' } validates_presence_of :name end +class DogValidatorWithIfCondition < Dog + before_validation :set_before_validation_marker1, if: -> { true } + before_validation :set_before_validation_marker2, if: -> { false } + + after_validation :set_after_validation_marker1, if: -> { true } + after_validation :set_after_validation_marker2, if: -> { false } + + def set_before_validation_marker1; self.history << 'before_validation_marker1'; end + def set_before_validation_marker2; self.history << 'before_validation_marker2' ; end + + def set_after_validation_marker1; self.history << 'after_validation_marker1'; end + def set_after_validation_marker2; self.history << 'after_validation_marker2' ; end +end + + class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase + def test_if_condition_is_respected_for_before_validation + d = DogValidatorWithIfCondition.new + d.valid? + assert_equal ["before_validation_marker1", "after_validation_marker1"], d.history + end + def test_before_validation_and_after_validation_callbacks_should_be_called d = DogWithMethodCallbacks.new d.valid? @@ -61,12 +87,18 @@ class CallbacksWithMethodNamesShouldBeCalled < ActiveModel::TestCase end def test_further_callbacks_should_not_be_called_if_before_validation_returns_false - d = DogValidatorReturningFalse.new + d = DogBeforeValidatorReturningFalse.new output = d.valid? assert_equal [], d.history assert_equal false, output end + def test_further_callbacks_should_be_called_if_after_validation_returns_false + d = DogAfterValidatorReturningFalse.new + d.valid? + assert_equal ['after_validation_marker'], d.history + end + def test_validation_test_should_be_done d = DogWithMissingName.new output = d.valid? diff --git a/activemodel/test/cases/validations/conditional_validation_test.rb b/activemodel/test/cases/validations/conditional_validation_test.rb index e06b04af19..1261937b56 100644 --- a/activemodel/test/cases/validations/conditional_validation_test.rb +++ b/activemodel/test/cases/validations/conditional_validation_test.rb @@ -6,12 +6,12 @@ require 'models/topic' class ConditionalValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_if_validation_using_method_true # When the method returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -20,23 +20,23 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_method_true # When the method returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_method_false # When the method returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => :condition_is_true_but_its_not ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: :condition_is_true_but_its_not) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_method_false # When the method returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => :condition_is_true_but_its_not ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: :condition_is_true_but_its_not) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -45,7 +45,7 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "a = 1; a == 1" ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "a = 1; a == 1") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -54,23 +54,23 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_string_true # When the evaluated string returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "a = 1; a == 1" ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "a = 1; a == 1") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :if => "false") + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", if: "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_string_false # When the evaluated string returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", :unless => "false") + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", unless: "false") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -79,8 +79,8 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_if_validation_using_block_true # When the block returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", - :if => Proc.new { |r| r.content.size > 4 } ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", + if: Proc.new { |r| r.content.size > 4 }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -89,26 +89,26 @@ class ConditionalValidationTest < ActiveModel::TestCase def test_unless_validation_using_block_true # When the block returns true - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", - :unless => Proc.new { |r| r.content.size > 4 } ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", + unless: Proc.new { |r| r.content.size > 4 }) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_if_validation_using_block_false # When the block returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", - :if => Proc.new { |r| r.title != "uhohuhoh"} ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", + if: Proc.new { |r| r.title != "uhohuhoh"}) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.valid? - assert t.errors[:title].empty? + assert_empty t.errors[:title] end def test_unless_validation_using_block_false # When the block returns false - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}", - :unless => Proc.new { |r| r.title != "uhohuhoh"} ) + Topic.validates_length_of(:title, maximum: 5, too_long: "hoo %{count}", + unless: Proc.new { |r| r.title != "uhohuhoh"} ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -120,11 +120,11 @@ class ConditionalValidationTest < ActiveModel::TestCase # ensure that it works correctly def test_validation_with_if_as_string Topic.validates_presence_of(:title) - Topic.validates_presence_of(:author_name, :if => "title.to_s.match('important')") + Topic.validates_presence_of(:author_name, if: "title.to_s.match('important')") t = Topic.new assert t.invalid?, "A topic without a title should not be valid" - assert t.errors[:author_name].empty?, "A topic without an 'important' title should not require an author" + assert_empty t.errors[:author_name], "A topic without an 'important' title should not require an author" t.title = "Just a title" assert t.valid?, "A topic with a basic title should be valid" diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index f7556a249f..65a2a1eb49 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -7,13 +7,13 @@ require 'models/person' class ConfirmationValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_no_title_confirmation Topic.validates_confirmation_of(:title) - t = Topic.new(:author_name => "Plutarch") + t = Topic.new(author_name: "Plutarch") assert t.valid? t.title_confirmation = "Parallel Lives" @@ -49,26 +49,60 @@ class ConfirmationValidationTest < ActiveModel::TestCase p.karma = "None" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_title_confirmation_with_i18n_attribute - @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend - I18n.load_path.clear - I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', { - :errors => {:messages => {:confirmation => "doesn't match %{attribute}"}}, - :activemodel => {:attributes => {:topic => {:title => 'Test Title'}}} - }) + begin + @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend + I18n.load_path.clear + I18n.backend = I18n::Backend::Simple.new + I18n.backend.store_translations('en', { + errors: { messages: { confirmation: "doesn't match %{attribute}" } }, + activemodel: { attributes: { topic: { title: 'Test Title'} } } + }) + + Topic.validates_confirmation_of(:title) + + t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") + assert t.invalid? + assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation] + ensure + I18n.load_path.replace @old_load_path + I18n.backend = @old_backend + I18n.backend.reload! + end + end - Topic.validates_confirmation_of(:title) + test "does not override confirmation reader if present" do + klass = Class.new do + include ActiveModel::Validations - t = Topic.new("title" => "We should be confirmed","title_confirmation" => "") - assert t.invalid? - assert_equal ["doesn't match Test Title"], t.errors[:title_confirmation] + def title_confirmation + "expected title" + end + + validates_confirmation_of :title + end - I18n.load_path.replace @old_load_path - I18n.backend = @old_backend + assert_equal "expected title", klass.new.title_confirmation, + "confirmation validation should not override the reader" end + test "does not override confirmation writer if present" do + klass = Class.new do + include ActiveModel::Validations + + def title_confirmation=(value) + @title_confirmation = "expected title" + end + + validates_confirmation_of :title + end + + model = klass.new + model.title_confirmation = "new title" + assert_equal "expected title", model.title_confirmation, + "confirmation validation should not override the writer" + end end diff --git a/activemodel/test/cases/validations/exclusion_validation_test.rb b/activemodel/test/cases/validations/exclusion_validation_test.rb index 7d5af27f3d..1ce41f9bc9 100644 --- a/activemodel/test/cases/validations/exclusion_validation_test.rb +++ b/activemodel/test/cases/validations/exclusion_validation_test.rb @@ -7,18 +7,18 @@ require 'models/person' class ExclusionValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_exclusion_of - Topic.validates_exclusion_of( :title, :in => %w( abe monkey ) ) + Topic.validates_exclusion_of(:title, in: %w( abe monkey )) assert Topic.new("title" => "something", "content" => "abc").valid? assert Topic.new("title" => "monkey", "content" => "abc").invalid? end def test_validates_exclusion_of_with_formatted_message - Topic.validates_exclusion_of( :title, :in => %w( abe monkey ), :message => "option %{value} is restricted" ) + Topic.validates_exclusion_of(:title, in: %w( abe monkey ), message: "option %{value} is restricted") assert Topic.new("title" => "something", "content" => "abc") @@ -29,7 +29,7 @@ class ExclusionValidationTest < ActiveModel::TestCase end def test_validates_exclusion_of_with_within_option - Topic.validates_exclusion_of( :title, :within => %w( abe monkey ) ) + Topic.validates_exclusion_of(:title, within: %w( abe monkey )) assert Topic.new("title" => "something", "content" => "abc") @@ -39,7 +39,7 @@ class ExclusionValidationTest < ActiveModel::TestCase end def test_validates_exclusion_of_for_ruby_class - Person.validates_exclusion_of :karma, :in => %w( abe monkey ) + Person.validates_exclusion_of :karma, in: %w( abe monkey ) p = Person.new p.karma = "abe" @@ -50,11 +50,11 @@ class ExclusionValidationTest < ActiveModel::TestCase p.karma = "Lifo" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end 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 ) } + Topic.validates_exclusion_of :title, in: lambda { |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } t = Topic.new t.title = "elephant" @@ -66,7 +66,7 @@ class ExclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_symbol - Person.validates_exclusion_of :karma, :in => :reserved_karmas + Person.validates_exclusion_of :karma, in: :reserved_karmas p = Person.new p.karma = "abe" @@ -87,6 +87,6 @@ class ExclusionValidationTest < ActiveModel::TestCase assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/format_validation_test.rb b/activemodel/test/cases/validations/format_validation_test.rb index 308a3c6cef..0f91b73cd7 100644 --- a/activemodel/test/cases/validations/format_validation_test.rb +++ b/activemodel/test/cases/validations/format_validation_test.rb @@ -7,11 +7,11 @@ require 'models/person' class PresenceValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validate_format - Topic.validates_format_of(:title, :content, :with => /\AValidation\smacros \w+!\z/, :message => "is bad data") + Topic.validates_format_of(:title, :content, with: /\AValidation\smacros \w+!\z/, message: "is bad data") t = Topic.new("title" => "i'm incorrect", "content" => "Validation macros rule!") assert t.invalid?, "Shouldn't be valid" @@ -27,7 +27,7 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validate_format_with_allow_blank - Topic.validates_format_of(:title, :with => /\AValidation\smacros \w+!\z/, :allow_blank => true) + Topic.validates_format_of(:title, with: /\AValidation\smacros \w+!\z/, allow_blank: true) assert Topic.new("title" => "Shouldn't be valid").invalid? assert Topic.new("title" => "").valid? assert Topic.new("title" => nil).valid? @@ -36,7 +36,7 @@ class PresenceValidationTest < ActiveModel::TestCase # testing ticket #3142 def test_validate_format_numeric - Topic.validates_format_of(:title, :content, :with => /\A[1-9][0-9]*\z/, :message => "is bad data") + Topic.validates_format_of(:title, :content, with: /\A[1-9][0-9]*\z/, message: "is bad data") t = Topic.new("title" => "72x", "content" => "6789") assert t.invalid?, "Shouldn't be valid" @@ -63,24 +63,24 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validate_format_with_formatted_message - Topic.validates_format_of(:title, :with => /\AValid Title\z/, :message => "can't be %{value}") - t = Topic.new(:title => 'Invalid title') + Topic.validates_format_of(:title, with: /\AValid Title\z/, message: "can't be %{value}") + t = Topic.new(title: 'Invalid title') assert t.invalid? assert_equal ["can't be Invalid title"], t.errors[:title] end - + def test_validate_format_of_with_multiline_regexp_should_raise_error - assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /^Valid Title$/) } + assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /^Valid Title$/) } end - + def test_validate_format_of_with_multiline_regexp_and_option assert_nothing_raised(ArgumentError) do - Topic.validates_format_of(:title, :with => /^Valid Title$/, :multiline => true) + Topic.validates_format_of(:title, with: /^Valid Title$/, multiline: true) end end def test_validate_format_with_not_option - Topic.validates_format_of(:title, :without => /foo/, :message => "should not contain foo") + Topic.validates_format_of(:title, without: /foo/, message: "should not contain foo") t = Topic.new t.title = "foobar" @@ -97,19 +97,19 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validates_format_of_with_both_regexps_should_raise_error - assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => /this/, :without => /that/) } + assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: /this/, without: /that/) } end def test_validates_format_of_when_with_isnt_a_regexp_should_raise_error - assert_raise(ArgumentError) { Topic.validates_format_of(:title, :with => "clearly not a regexp") } + assert_raise(ArgumentError) { Topic.validates_format_of(:title, with: "clearly not a regexp") } end def test_validates_format_of_when_not_isnt_a_regexp_should_raise_error - assert_raise(ArgumentError) { Topic.validates_format_of(:title, :without => "clearly not a regexp") } + assert_raise(ArgumentError) { Topic.validates_format_of(:title, without: "clearly not a regexp") } end def test_validates_format_of_with_lambda - Topic.validates_format_of :content, :with => lambda{ |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ } + Topic.validates_format_of :content, with: lambda { |topic| topic.title == "digit" ? /\A\d+\Z/ : /\A\S+\Z/ } t = Topic.new t.title = "digit" @@ -121,7 +121,7 @@ class PresenceValidationTest < ActiveModel::TestCase 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/ } + Topic.validates_format_of :content, without: lambda { |topic| topic.title == "characters" ? /\A\d+\Z/ : /\A\S+\Z/ } t = Topic.new t.title = "characters" @@ -133,7 +133,7 @@ class PresenceValidationTest < ActiveModel::TestCase end def test_validates_format_of_for_ruby_class - Person.validates_format_of :karma, :with => /\A\d+\Z/ + Person.validates_format_of :karma, with: /\A\d+\Z/ p = Person.new p.karma = "Pixies" @@ -144,6 +144,6 @@ class PresenceValidationTest < ActiveModel::TestCase p.karma = "1234" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb index 302cbe9761..3eeb80a48b 100644 --- a/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_generate_message_validation_test.rb @@ -4,35 +4,35 @@ require 'models/person' class I18nGenerateMessageValidationTest < ActiveModel::TestCase def setup - Person.reset_callbacks(:validate) + Person.clear_validators! @person = Person.new end # validates_inclusion_of: generate_message(attr_name, :inclusion, message: custom_message, value: value) def test_generate_message_inclusion_with_default_message - assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, :value => 'title') + assert_equal 'is not included in the list', @person.errors.generate_message(:title, :inclusion, value: 'title') end def test_generate_message_inclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, :message => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :inclusion, message: 'custom message %{value}', value: 'title') end # validates_exclusion_of: generate_message(attr_name, :exclusion, message: custom_message, value: value) def test_generate_message_exclusion_with_default_message - assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, :value => 'title') + assert_equal 'is reserved', @person.errors.generate_message(:title, :exclusion, value: 'title') end def test_generate_message_exclusion_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, :message => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :exclusion, message: 'custom message %{value}', value: 'title') end # validates_format_of: generate_message(attr_name, :invalid, message: custom_message, value: value) def test_generate_message_invalid_with_default_message - assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, :value => 'title') + assert_equal 'is invalid', @person.errors.generate_message(:title, :invalid, value: 'title') end def test_generate_message_invalid_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, :message => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :invalid, message: 'custom message %{value}', value: 'title') end # validates_confirmation_of: generate_message(attr_name, :confirmation, message: custom_message) @@ -41,7 +41,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_confirmation_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, :message => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :confirmation, message: 'custom message') end # validates_acceptance_of: generate_message(attr_name, :accepted, message: custom_message) @@ -50,7 +50,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_accepted_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, :message => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :accepted, message: 'custom message') end # add_on_empty: generate_message(attr, :empty, message: custom_message) @@ -59,7 +59,7 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_empty_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :empty, :message => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :empty, message: 'custom message') end # add_on_blank: generate_message(attr, :blank, message: custom_message) @@ -68,71 +68,83 @@ class I18nGenerateMessageValidationTest < ActiveModel::TestCase end def test_generate_message_blank_with_custom_message - assert_equal 'custom message', @person.errors.generate_message(:title, :blank, :message => 'custom message') + assert_equal 'custom message', @person.errors.generate_message(:title, :blank, message: 'custom message') end # validates_length_of: generate_message(attr, :too_long, message: custom_message, count: option_value.end) - def test_generate_message_too_long_with_default_message - assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, :count => 10) + def test_generate_message_too_long_with_default_message_plural + assert_equal "is too long (maximum is 10 characters)", @person.errors.generate_message(:title, :too_long, count: 10) + end + + def test_generate_message_too_long_with_default_message_singular + assert_equal "is too long (maximum is 1 character)", @person.errors.generate_message(:title, :too_long, count: 1) end def test_generate_message_too_long_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, :message => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_long, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :too_short, default: custom_message, count: option_value.begin) - def test_generate_message_too_short_with_default_message - assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, :count => 10) + def test_generate_message_too_short_with_default_message_plural + assert_equal "is too short (minimum is 10 characters)", @person.errors.generate_message(:title, :too_short, count: 10) + end + + def test_generate_message_too_short_with_default_message_singular + assert_equal "is too short (minimum is 1 character)", @person.errors.generate_message(:title, :too_short, count: 1) end def test_generate_message_too_short_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, :message => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :too_short, message: 'custom message %{count}', count: 10) end # validates_length_of: generate_message(attr, :wrong_length, message: custom_message, count: option_value) - def test_generate_message_wrong_length_with_default_message - assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, :count => 10) + def test_generate_message_wrong_length_with_default_message_plural + assert_equal "is the wrong length (should be 10 characters)", @person.errors.generate_message(:title, :wrong_length, count: 10) + end + + def test_generate_message_wrong_length_with_default_message_singular + assert_equal "is the wrong length (should be 1 character)", @person.errors.generate_message(:title, :wrong_length, count: 1) end def test_generate_message_wrong_length_with_custom_message - assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, :message => 'custom message %{count}', :count => 10) + assert_equal 'custom message 10', @person.errors.generate_message(:title, :wrong_length, message: 'custom message %{count}', count: 10) end # validates_numericality_of: generate_message(attr_name, :not_a_number, value: raw_value, message: custom_message) def test_generate_message_not_a_number_with_default_message - assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, :value => 'title') + assert_equal "is not a number", @person.errors.generate_message(:title, :not_a_number, value: 'title') end def test_generate_message_not_a_number_with_custom_message - assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, :message => 'custom message %{value}', :value => 'title') + assert_equal 'custom message title', @person.errors.generate_message(:title, :not_a_number, message: 'custom message %{value}', value: 'title') end # validates_numericality_of: generate_message(attr_name, option, value: raw_value, default: custom_message) def test_generate_message_greater_than_with_default_message - assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, :value => 'title', :count => 10) + assert_equal "must be greater than 10", @person.errors.generate_message(:title, :greater_than, value: 'title', count: 10) end def test_generate_message_greater_than_or_equal_to_with_default_message - assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, :value => 'title', :count => 10) + assert_equal "must be greater than or equal to 10", @person.errors.generate_message(:title, :greater_than_or_equal_to, value: 'title', count: 10) end def test_generate_message_equal_to_with_default_message - assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, :value => 'title', :count => 10) + assert_equal "must be equal to 10", @person.errors.generate_message(:title, :equal_to, value: 'title', count: 10) end def test_generate_message_less_than_with_default_message - assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, :value => 'title', :count => 10) + assert_equal "must be less than 10", @person.errors.generate_message(:title, :less_than, value: 'title', count: 10) end def test_generate_message_less_than_or_equal_to_with_default_message - assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, :value => 'title', :count => 10) + assert_equal "must be less than or equal to 10", @person.errors.generate_message(:title, :less_than_or_equal_to, value: 'title', count: 10) end def test_generate_message_odd_with_default_message - assert_equal "must be odd", @person.errors.generate_message(:title, :odd, :value => 'title', :count => 10) + assert_equal "must be odd", @person.errors.generate_message(:title, :odd, value: 'title', count: 10) end def test_generate_message_even_with_default_message - assert_equal "must be even", @person.errors.generate_message(:title, :even, :value => 'title', :count => 10) + assert_equal "must be even", @person.errors.generate_message(:title, :even, value: 'title', count: 10) end end diff --git a/activemodel/test/cases/validations/i18n_validation_test.rb b/activemodel/test/cases/validations/i18n_validation_test.rb index 4c01b47608..96084a32ba 100644 --- a/activemodel/test/cases/validations/i18n_validation_test.rb +++ b/activemodel/test/cases/validations/i18n_validation_test.rb @@ -6,37 +6,38 @@ require 'models/person' class I18nValidationTest < ActiveModel::TestCase def setup - Person.reset_callbacks(:validate) + Person.clear_validators! @person = Person.new @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations('en', :errors => {:messages => {:custom => nil}}) + I18n.backend.store_translations('en', errors: { messages: { custom: nil } }) end def teardown - Person.reset_callbacks(:validate) + Person.clear_validators! I18n.load_path.replace @old_load_path I18n.backend = @old_backend + I18n.backend.reload! end def test_full_message_encoding - I18n.backend.store_translations('en', :errors => { - :messages => { :too_short => '猫舌' }}) - Person.validates_length_of :title, :within => 3..5 + I18n.backend.store_translations('en', errors: { + messages: { too_short: '猫舌' } }) + Person.validates_length_of :title, within: 3..5 @person.valid? assert_equal ['Title 猫舌'], @person.errors.full_messages end def test_errors_full_messages_translates_human_attribute_name_for_model_attributes @person.errors.add(:name, 'not found') - Person.expects(:human_attribute_name).with(:name, :default => 'Name').returns("Person's name") + Person.expects(:human_attribute_name).with(:name, default: 'Name').returns("Person's name") assert_equal ["Person's name not found"], @person.errors.full_messages end def test_errors_full_messages_uses_format - I18n.backend.store_translations('en', :errors => {:format => "Field %{attribute} %{message}"}) + I18n.backend.store_translations('en', errors: { format: "Field %{attribute} %{message}" }) @person.errors.add('name', 'empty') assert_equal ["Field Name empty"], @person.errors.full_messages end @@ -49,10 +50,10 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES = [ # [ case, validation_options, generate_message_options] [ "given no options", {}, {}], - [ "given custom message", {:message => "custom"}, {:message => "custom"}], - [ "given if condition", {:if => lambda { true }}, {}], - [ "given unless condition", {:unless => lambda { false }}, {}], - [ "given option that is not reserved", {:format => "jpg"}, {:format => "jpg" }] + [ "given custom message", { message: "custom" }, { message: "custom" }], + [ "given if condition", { if: lambda { true }}, {}], + [ "given unless condition", { unless: lambda { false }}, {}], + [ "given option that is not reserved", { format: "jpg" }, { format: "jpg" }] ] # validates_confirmation_of w/ mocha @@ -61,7 +62,7 @@ class I18nValidationTest < ActiveModel::TestCase test "validates_confirmation_of on generated message #{name}" do Person.validates_confirmation_of :title, validation_options @person.title_confirmation = 'foo' - @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(:attribute => 'Title')) + @person.errors.expects(:generate_message).with(:title_confirmation, :confirmation, generate_message_options.merge(attribute: 'Title')) @person.valid? end end @@ -70,7 +71,7 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_acceptance_of on generated message #{name}" do - Person.validates_acceptance_of :title, validation_options.merge(:allow_nil => false) + Person.validates_acceptance_of :title, validation_options.merge(allow_nil: false) @person.errors.expects(:generate_message).with(:title, :accepted, generate_message_options) @person.valid? end @@ -90,8 +91,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :withing on generated message when too short #{name}" do - Person.validates_length_of :title, validation_options.merge(:within => 3..5) - @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(:count => 3)) + Person.validates_length_of :title, validation_options.merge(within: 3..5) + @person.errors.expects(:generate_message).with(:title, :too_short, generate_message_options.merge(count: 3)) @person.valid? end end @@ -100,9 +101,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :too_long generated message #{name}" do - Person.validates_length_of :title, validation_options.merge(:within => 3..5) + Person.validates_length_of :title, validation_options.merge(within: 3..5) @person.title = 'this title is too long' - @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(:count => 5)) + @person.errors.expects(:generate_message).with(:title, :too_long, generate_message_options.merge(count: 5)) @person.valid? end end @@ -111,8 +112,8 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_length_of for :is on generated message #{name}" do - Person.validates_length_of :title, validation_options.merge(:is => 5) - @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(:count => 5)) + Person.validates_length_of :title, validation_options.merge(is: 5) + @person.errors.expects(:generate_message).with(:title, :wrong_length, generate_message_options.merge(count: 5)) @person.valid? end end @@ -121,9 +122,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_format_of on generated message #{name}" do - Person.validates_format_of :title, validation_options.merge(:with => /\A[1-9][0-9]*\z/) + Person.validates_format_of :title, validation_options.merge(with: /\A[1-9][0-9]*\z/) @person.title = '72x' - @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(:value => '72x')) + @person.errors.expects(:generate_message).with(:title, :invalid, generate_message_options.merge(value: '72x')) @person.valid? end end @@ -132,9 +133,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of on generated message #{name}" do - Person.validates_inclusion_of :title, validation_options.merge(:in => %w(a b c)) + Person.validates_inclusion_of :title, validation_options.merge(in: %w(a b c)) @person.title = 'z' - @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z')) + @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z')) @person.valid? end end @@ -143,9 +144,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_inclusion_of using :within on generated message #{name}" do - Person.validates_inclusion_of :title, validation_options.merge(:within => %w(a b c)) + Person.validates_inclusion_of :title, validation_options.merge(within: %w(a b c)) @person.title = 'z' - @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(:value => 'z')) + @person.errors.expects(:generate_message).with(:title, :inclusion, generate_message_options.merge(value: 'z')) @person.valid? end end @@ -154,9 +155,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of generated message #{name}" do - Person.validates_exclusion_of :title, validation_options.merge(:in => %w(a b c)) + Person.validates_exclusion_of :title, validation_options.merge(in: %w(a b c)) @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a')) + @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a')) @person.valid? end end @@ -165,9 +166,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_exclusion_of using :within generated message #{name}" do - Person.validates_exclusion_of :title, validation_options.merge(:within => %w(a b c)) + Person.validates_exclusion_of :title, validation_options.merge(within: %w(a b c)) @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(:value => 'a')) + @person.errors.expects(:generate_message).with(:title, :exclusion, generate_message_options.merge(value: 'a')) @person.valid? end end @@ -178,7 +179,7 @@ class I18nValidationTest < ActiveModel::TestCase test "validates_numericality_of generated message #{name}" do Person.validates_numericality_of :title, validation_options @person.title = 'a' - @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(:value => 'a')) + @person.errors.expects(:generate_message).with(:title, :not_a_number, generate_message_options.merge(value: 'a')) @person.valid? end end @@ -187,9 +188,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :only_integer on generated message #{name}" do - Person.validates_numericality_of :title, validation_options.merge(:only_integer => true) + Person.validates_numericality_of :title, validation_options.merge(only_integer: true) @person.title = '0.0' - @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(:value => '0.0')) + @person.errors.expects(:generate_message).with(:title, :not_an_integer, generate_message_options.merge(value: '0.0')) @person.valid? end end @@ -198,9 +199,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :odd on generated message #{name}" do - Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :odd => true) + Person.validates_numericality_of :title, validation_options.merge(only_integer: true, odd: true) @person.title = 0 - @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(:value => 0)) + @person.errors.expects(:generate_message).with(:title, :odd, generate_message_options.merge(value: 0)) @person.valid? end end @@ -209,9 +210,9 @@ class I18nValidationTest < ActiveModel::TestCase COMMON_CASES.each do |name, validation_options, generate_message_options| test "validates_numericality_of for :less_than on generated message #{name}" do - Person.validates_numericality_of :title, validation_options.merge(:only_integer => true, :less_than => 0) + Person.validates_numericality_of :title, validation_options.merge(only_integer: true, less_than: 0) @person.title = 1 - @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(:value => 1, :count => 0)) + @person.errors.expects(:generate_message).with(:title, :less_than, generate_message_options.merge(value: 1, count: 0)) @person.valid? end end @@ -226,8 +227,8 @@ class I18nValidationTest < ActiveModel::TestCase end # test "validates_confirmation_of finds custom model key translation when blank" test "#{validation} finds custom model key translation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} + I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message' } } } } } } + I18n.backend.store_translations 'en', errors: { messages: { error_type => 'global message'}} yield(@person, {}) @person.valid? @@ -236,17 +237,17 @@ class I18nValidationTest < ActiveModel::TestCase # test "validates_confirmation_of finds custom model key translation with interpolation when blank" test "#{validation} finds custom model key translation with interpolation when #{error_type}" do - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {attribute => {error_type => 'custom message with %{extra}'}}}}}} - I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} + I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { attribute => { error_type => 'custom message with %{extra}' } } } } } } + I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } - yield(@person, {:extra => "extra information"}) + yield(@person, { extra: "extra information" }) @person.valid? assert_equal ['custom message with extra information'], @person.errors[attribute] end # test "validates_confirmation_of finds global default key translation when blank" test "#{validation} finds global default key translation when #{error_type}" do - I18n.backend.store_translations 'en', :errors => {:messages => {error_type => 'global message'}} + I18n.backend.store_translations 'en', errors: { messages: {error_type => 'global message'} } yield(@person, {}) @person.valid? @@ -264,7 +265,7 @@ class I18nValidationTest < ActiveModel::TestCase # validates_acceptance_of w/o mocha set_expectations_for_validation "validates_acceptance_of", :accepted do |person, options_to_merge| - Person.validates_acceptance_of :title, options_to_merge.merge(:allow_nil => false) + Person.validates_acceptance_of :title, options_to_merge.merge(allow_nil: false) end # validates_presence_of w/o mocha @@ -276,36 +277,36 @@ class I18nValidationTest < ActiveModel::TestCase # validates_length_of :within w/o mocha set_expectations_for_validation "validates_length_of", :too_short do |person, options_to_merge| - Person.validates_length_of :title, options_to_merge.merge(:within => 3..5) + Person.validates_length_of :title, options_to_merge.merge(within: 3..5) end set_expectations_for_validation "validates_length_of", :too_long do |person, options_to_merge| - Person.validates_length_of :title, options_to_merge.merge(:within => 3..5) + Person.validates_length_of :title, options_to_merge.merge(within: 3..5) person.title = "too long" end # validates_length_of :is w/o mocha set_expectations_for_validation "validates_length_of", :wrong_length do |person, options_to_merge| - Person.validates_length_of :title, options_to_merge.merge(:is => 5) + Person.validates_length_of :title, options_to_merge.merge(is: 5) end # validates_format_of w/o mocha set_expectations_for_validation "validates_format_of", :invalid do |person, options_to_merge| - Person.validates_format_of :title, options_to_merge.merge(:with => /\A[1-9][0-9]*\z/) + Person.validates_format_of :title, options_to_merge.merge(with: /\A[1-9][0-9]*\z/) end # validates_inclusion_of w/o mocha set_expectations_for_validation "validates_inclusion_of", :inclusion do |person, options_to_merge| - Person.validates_inclusion_of :title, options_to_merge.merge(:in => %w(a b c)) + Person.validates_inclusion_of :title, options_to_merge.merge(in: %w(a b c)) end # validates_exclusion_of w/o mocha set_expectations_for_validation "validates_exclusion_of", :exclusion do |person, options_to_merge| - Person.validates_exclusion_of :title, options_to_merge.merge(:in => %w(a b c)) + Person.validates_exclusion_of :title, options_to_merge.merge(in: %w(a b c)) person.title = 'a' end @@ -319,55 +320,54 @@ class I18nValidationTest < ActiveModel::TestCase # validates_numericality_of with :only_integer w/o mocha set_expectations_for_validation "validates_numericality_of", :not_an_integer do |person, options_to_merge| - Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true) + Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true) person.title = '1.0' end # validates_numericality_of :odd w/o mocha set_expectations_for_validation "validates_numericality_of", :odd do |person, options_to_merge| - Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :odd => true) + Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, odd: true) person.title = 0 end # validates_numericality_of :less_than w/o mocha set_expectations_for_validation "validates_numericality_of", :less_than do |person, options_to_merge| - Person.validates_numericality_of :title, options_to_merge.merge(:only_integer => true, :less_than => 0) + Person.validates_numericality_of :title, options_to_merge.merge(only_integer: true, less_than: 0) person.title = 1 end # test with validates_with def test_validations_with_message_symbol_must_translate - I18n.backend.store_translations 'en', :errors => {:messages => {:custom_error => "I am a custom error"}} - Person.validates_presence_of :title, :message => :custom_error + I18n.backend.store_translations 'en', errors: { messages: { custom_error: "I am a custom error" } } + Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_symbol_must_translate_per_attribute - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:attributes => {:title => {:custom_error => "I am a custom error"}}}}}} - Person.validates_presence_of :title, :message => :custom_error + I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { attributes: { title: { custom_error: "I am a custom error" } } } } } } + Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_symbol_must_translate_per_model - I18n.backend.store_translations 'en', :activemodel => {:errors => {:models => {:person => {:custom_error => "I am a custom error"}}}} - Person.validates_presence_of :title, :message => :custom_error + I18n.backend.store_translations 'en', activemodel: { errors: { models: { person: { custom_error: "I am a custom error" } } } } + Person.validates_presence_of :title, message: :custom_error @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end def test_validates_with_message_string - Person.validates_presence_of :title, :message => "I am a custom error" + Person.validates_presence_of :title, message: "I am a custom error" @person.title = nil @person.valid? assert_equal ["I am a custom error"], @person.errors[:title] end - end diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb index 117e9109fc..3a8f3080e1 100644 --- a/activemodel/test/cases/validations/inclusion_validation_test.rb +++ b/activemodel/test/cases/validations/inclusion_validation_test.rb @@ -1,5 +1,6 @@ # encoding: utf-8 require 'cases/helper' +require 'active_support/all' require 'models/topic' require 'models/person' @@ -7,20 +8,42 @@ require 'models/person' class InclusionValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_inclusion_of_range - Topic.validates_inclusion_of( :title, :in => 'aaa'..'bbb' ) + Topic.validates_inclusion_of(:title, in: 'aaa'..'bbb') assert Topic.new("title" => "bbc", "content" => "abc").invalid? assert Topic.new("title" => "aa", "content" => "abc").invalid? + assert Topic.new("title" => "aaab", "content" => "abc").invalid? assert Topic.new("title" => "aaa", "content" => "abc").valid? assert Topic.new("title" => "abc", "content" => "abc").valid? assert Topic.new("title" => "bbb", "content" => "abc").valid? end + def test_validates_inclusion_of_time_range + Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now) + assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid? + assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid? + assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid? + end + + def test_validates_inclusion_of_date_range + Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today) + assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid? + assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid? + assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid? + end + + def test_validates_inclusion_of_date_time_range + Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current) + assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid? + assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid? + assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid? + end + def test_validates_inclusion_of - Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ) ) + Topic.validates_inclusion_of(:title, in: %w( a b c d e f g )) assert Topic.new("title" => "a!", "content" => "abc").invalid? assert Topic.new("title" => "a b", "content" => "abc").invalid? @@ -33,16 +56,16 @@ class InclusionValidationTest < ActiveModel::TestCase assert t.errors[:title].any? assert_equal ["is not included in the list"], t.errors[:title] - assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => nil ) } - assert_raise(ArgumentError) { Topic.validates_inclusion_of( :title, :in => 0) } + assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: nil) } + assert_raise(ArgumentError) { Topic.validates_inclusion_of(:title, in: 0) } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => "hi!" ) } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => {} ) } - assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of( :title, :in => [] ) } + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: "hi!") } + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: {}) } + assert_nothing_raised(ArgumentError) { Topic.validates_inclusion_of(:title, in: []) } end def test_validates_inclusion_of_with_allow_nil - Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :allow_nil => true ) + Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), allow_nil: true) assert Topic.new("title" => "a!", "content" => "abc").invalid? assert Topic.new("title" => "", "content" => "abc").invalid? @@ -50,7 +73,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_formatted_message - Topic.validates_inclusion_of( :title, :in => %w( a b c d e f g ), :message => "option %{value} is not in the list" ) + Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ), message: "option %{value} is not in the list") assert Topic.new("title" => "a", "content" => "abc").valid? @@ -61,7 +84,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_within_option - Topic.validates_inclusion_of( :title, :within => %w( a b c d e f g ) ) + Topic.validates_inclusion_of(:title, within: %w( a b c d e f g )) assert Topic.new("title" => "a", "content" => "abc").valid? @@ -71,7 +94,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_for_ruby_class - Person.validates_inclusion_of :karma, :in => %w( abe monkey ) + Person.validates_inclusion_of :karma, in: %w( abe monkey ) p = Person.new p.karma = "Lifo" @@ -82,11 +105,11 @@ class InclusionValidationTest < ActiveModel::TestCase p.karma = "monkey" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end 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 ) } + Topic.validates_inclusion_of :title, in: lambda{ |topic| topic.author_name == "sikachu" ? %w( monkey elephant ) : %w( abe wasabi ) } t = Topic.new t.title = "wasabi" @@ -98,7 +121,7 @@ class InclusionValidationTest < ActiveModel::TestCase end def test_validates_inclusion_of_with_symbol - Person.validates_inclusion_of :karma, :in => :available_karmas + Person.validates_inclusion_of :karma, in: :available_karmas p = Person.new p.karma = "Lifo" @@ -119,6 +142,6 @@ class InclusionValidationTest < ActiveModel::TestCase assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end end diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 1a40ca8efc..046ffcb16f 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -5,13 +5,12 @@ require 'models/topic' require 'models/person' class LengthValidationTest < ActiveModel::TestCase - def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_validates_length_of_with_allow_nil - Topic.validates_length_of( :title, :is => 5, :allow_nil => true ) + Topic.validates_length_of( :title, is: 5, allow_nil: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").invalid? @@ -20,7 +19,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_allow_blank - Topic.validates_length_of( :title, :is => 5, :allow_blank => true ) + Topic.validates_length_of( :title, is: 5, allow_blank: true ) assert Topic.new("title" => "ab").invalid? assert Topic.new("title" => "").valid? @@ -29,7 +28,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -51,13 +50,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_allow_nil - Topic.validates_length_of :title, :maximum => 10 + Topic.validates_length_of :title, maximum: 10 t = Topic.new assert t.valid? end def test_optionally_validates_length_of_using_minimum - Topic.validates_length_of :title, :minimum => 5, :allow_nil => true + Topic.validates_length_of :title, minimum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -67,7 +66,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -82,7 +81,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_maximum - Topic.validates_length_of :title, :maximum => 5, :allow_nil => true + Topic.validates_length_of :title, maximum: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -92,7 +91,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "a!", "content" => "I'm ooooooooh so very long") assert t.invalid? @@ -111,7 +110,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_with_exclusive_range - Topic.validates_length_of(:title, :within => 4...10) + Topic.validates_length_of(:title, within: 4...10) t = Topic.new("title" => "9 chars!!") assert t.valid? @@ -125,7 +124,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within - Topic.validates_length_of :title, :content, :within => 3..5, :allow_nil => true + Topic.validates_length_of :title, :content, within: 3..5, allow_nil: true t = Topic.new('title' => 'abc', 'content' => 'abcd') assert t.valid? @@ -135,7 +134,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -153,7 +152,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_is - Topic.validates_length_of :title, :is => 5, :allow_nil => true + Topic.validates_length_of :title, is: 5, allow_nil: true t = Topic.new("title" => "valid", "content" => "whatever") assert t.valid? @@ -167,25 +166,25 @@ class LengthValidationTest < ActiveModel::TestCase bigmax = 2 ** 32 bigrange = bigmin...bigmax assert_nothing_raised do - Topic.validates_length_of :title, :is => bigmin + 5 - Topic.validates_length_of :title, :within => bigrange - Topic.validates_length_of :title, :in => bigrange - Topic.validates_length_of :title, :minimum => bigmin - Topic.validates_length_of :title, :maximum => bigmax + Topic.validates_length_of :title, is: bigmin + 5 + Topic.validates_length_of :title, within: bigrange + Topic.validates_length_of :title, in: bigrange + Topic.validates_length_of :title, minimum: bigmin + Topic.validates_length_of :title, maximum: bigmax end end def test_validates_length_of_nasty_params - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => -6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => 6) } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :minimum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :maximum => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :within => "a") } - assert_raise(ArgumentError) { Topic.validates_length_of(:title, :is => "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: -6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: 6) } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, minimum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, maximum: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, within: "a") } + assert_raise(ArgumentError) { Topic.validates_length_of(:title, is: "a") } end def test_validates_length_of_custom_errors_for_minimum_with_message - Topic.validates_length_of( :title, :minimum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -193,7 +192,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_minimum_with_too_short - Topic.validates_length_of( :title, :minimum => 5, :too_short => "hoo %{count}" ) + Topic.validates_length_of( :title, minimum: 5, too_short: "hoo %{count}" ) t = Topic.new("title" => "uhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -201,7 +200,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_message - Topic.validates_length_of( :title, :maximum => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -209,7 +208,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_in - Topic.validates_length_of(:title, :in => 10..20, :message => "hoo %{count}") + Topic.validates_length_of(:title, in: 10..20, message: "hoo %{count}") t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -222,7 +221,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_maximum_with_too_long - Topic.validates_length_of( :title, :maximum => 5, :too_long => "hoo %{count}" ) + Topic.validates_length_of( :title, maximum: 5, too_long: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -230,21 +229,21 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_both_too_short_and_too_long - Topic.validates_length_of :title, :minimum => 3, :maximum => 5, :too_short => 'too short', :too_long => 'too long' + Topic.validates_length_of :title, minimum: 3, maximum: 5, too_short: 'too short', too_long: 'too long' - t = Topic.new(:title => 'a') + t = Topic.new(title: 'a') assert t.invalid? assert t.errors[:title].any? assert_equal ['too short'], t.errors['title'] - t = Topic.new(:title => 'aaaaaa') + t = Topic.new(title: 'aaaaaa') assert t.invalid? assert t.errors[:title].any? assert_equal ['too long'], t.errors['title'] end def test_validates_length_of_custom_errors_for_is_with_message - Topic.validates_length_of( :title, :is => 5, :message => "boo %{count}" ) + Topic.validates_length_of( :title, is: 5, message: "boo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -252,7 +251,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_custom_errors_for_is_with_wrong_length - Topic.validates_length_of( :title, :is => 5, :wrong_length => "hoo %{count}" ) + Topic.validates_length_of( :title, is: 5, wrong_length: "hoo %{count}" ) t = Topic.new("title" => "uhohuhoh", "content" => "whatever") assert t.invalid? assert t.errors[:title].any? @@ -260,7 +259,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_minimum_utf8 - Topic.validates_length_of :title, :minimum => 5 + Topic.validates_length_of :title, minimum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -272,7 +271,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_utf8 - Topic.validates_length_of :title, :maximum => 5 + Topic.validates_length_of :title, maximum: 5 t = Topic.new("title" => "一二三四五", "content" => "whatever") assert t.valid? @@ -284,7 +283,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_within_utf8 - Topic.validates_length_of(:title, :content, :within => 3..5) + Topic.validates_length_of(:title, :content, within: 3..5) t = Topic.new("title" => "一二", "content" => "12三四五六七") assert t.invalid? @@ -296,12 +295,12 @@ class LengthValidationTest < ActiveModel::TestCase end def test_optionally_validates_length_of_using_within_utf8 - 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 => "一二三四五") + t = Topic.new(title: "一二三四五") assert t.valid?, t.errors.inspect - t = Topic.new(:title => "一二三") + t = Topic.new(title: "一二三") assert t.valid?, t.errors.inspect t.title = nil @@ -309,7 +308,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_utf8 - Topic.validates_length_of :title, :is => 5 + Topic.validates_length_of :title, is: 5 t = Topic.new("title" => "一二345", "content" => "whatever") assert t.valid? @@ -321,9 +320,9 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_with_block - Topic.validates_length_of :content, :minimum => 5, :too_short => "Your essay must be at least %{count} words.", - :tokenizer => lambda {|str| str.scan(/\w+/) } - t = Topic.new(:content => "this content should be long enough") + Topic.validates_length_of :content, minimum: 5, too_short: "Your essay must be at least %{count} words.", + tokenizer: lambda {|str| str.scan(/\w+/) } + t = Topic.new(content: "this content should be long enough") assert t.valid? t.content = "not long enough" @@ -333,18 +332,18 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_for_fixnum - Topic.validates_length_of(:approved, :is => 4) + Topic.validates_length_of(:approved, is: 4) - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1) assert t.invalid? assert t.errors[:approved].any? - t = Topic.new("title" => "uhohuhoh", "content" => "whatever", :approved => 1234) + t = Topic.new("title" => "uhohuhoh", "content" => "whatever", approved: 1234) assert t.valid? end def test_validates_length_of_for_ruby_class - Person.validates_length_of :karma, :minimum => 5 + Person.validates_length_of :karma, minimum: 5 p = Person.new p.karma = "Pix" @@ -355,11 +354,11 @@ class LengthValidationTest < ActiveModel::TestCase p.karma = "The Smiths" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_length_of_for_infinite_maxima - Topic.validates_length_of(:title, :within => 5..Float::INFINITY) + Topic.validates_length_of(:title, within: 5..Float::INFINITY) t = Topic.new("title" => "1234") assert t.invalid? @@ -368,7 +367,7 @@ class LengthValidationTest < ActiveModel::TestCase t.title = "12345" assert t.valid? - Topic.validates_length_of(:author_name, :maximum => Float::INFINITY) + Topic.validates_length_of(:author_name, maximum: Float::INFINITY) assert t.valid? @@ -377,13 +376,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_maximum_should_not_allow_nil_when_nil_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_nil => false + Topic.validates_length_of :title, maximum: 10, allow_nil: false t = Topic.new assert t.invalid? end def test_validates_length_of_using_maximum_should_not_allow_nil_and_empty_string_when_blank_not_allowed - Topic.validates_length_of :title, :maximum => 10, :allow_blank => false + Topic.validates_length_of :title, maximum: 10, allow_blank: false t = Topic.new assert t.invalid? @@ -392,13 +391,13 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_both_minimum_and_maximum_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 5, :maximum => 10 + Topic.validates_length_of :title, minimum: 5, maximum: 10 t = Topic.new assert t.invalid? end def test_validates_length_of_using_minimum_0_should_not_allow_nil - Topic.validates_length_of :title, :minimum => 0 + Topic.validates_length_of :title, minimum: 0 t = Topic.new assert t.invalid? @@ -407,11 +406,19 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_is_0_should_not_allow_nil - Topic.validates_length_of :title, :is => 0 + Topic.validates_length_of :title, is: 0 t = Topic.new assert t.invalid? t.title = "" assert t.valid? end + + def test_validates_with_diff_in_option + Topic.validates_length_of(:title, is: 5) + Topic.validates_length_of(:title, is: 5, if: Proc.new { false } ) + + assert Topic.new("title" => "david").valid? + assert Topic.new("title" => "david2").invalid? + end end diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index 6742a4bab0..12a22f9c40 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -9,7 +9,7 @@ require 'bigdecimal' class NumericalityValidationTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end NIL = [nil] @@ -30,84 +30,99 @@ class NumericalityValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_with_nil_allowed - Topic.validates_numericality_of :approved, :allow_nil => true + Topic.validates_numericality_of :approved, allow_nil: true invalid!(JUNK + BLANK) valid!(NIL + FLOATS + INTEGERS + BIGDECIMAL + INFINITY) end def test_validates_numericality_of_with_integer_only - Topic.validates_numericality_of :approved, :only_integer => true + Topic.validates_numericality_of :approved, only_integer: true invalid!(NIL + BLANK + JUNK + FLOATS + BIGDECIMAL + INFINITY) valid!(INTEGERS) end def test_validates_numericality_of_with_integer_only_and_nil_allowed - Topic.validates_numericality_of :approved, :only_integer => true, :allow_nil => true + Topic.validates_numericality_of :approved, only_integer: true, allow_nil: true invalid!(JUNK + BLANK + FLOATS + BIGDECIMAL + INFINITY) valid!(NIL + INTEGERS) end + def test_validates_numericality_of_with_integer_only_and_symbol_as_value + Topic.validates_numericality_of :approved, only_integer: :condition_is_true_but_its_not + + invalid!(NIL + BLANK + JUNK) + valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY) + end + + def test_validates_numericality_of_with_integer_only_and_proc_as_value + Topic.send(:define_method, :allow_only_integers?, lambda { false }) + Topic.validates_numericality_of :approved, only_integer: Proc.new(&:allow_only_integers?) + + invalid!(NIL + BLANK + JUNK) + valid!(FLOATS + INTEGERS + BIGDECIMAL + INFINITY) + end + def test_validates_numericality_with_greater_than - Topic.validates_numericality_of :approved, :greater_than => 10 + Topic.validates_numericality_of :approved, greater_than: 10 invalid!([-10, 10], 'must be greater than 10') valid!([11]) end def test_validates_numericality_with_greater_than_or_equal - Topic.validates_numericality_of :approved, :greater_than_or_equal_to => 10 + Topic.validates_numericality_of :approved, greater_than_or_equal_to: 10 invalid!([-9, 9], 'must be greater than or equal to 10') valid!([10]) end def test_validates_numericality_with_equal_to - Topic.validates_numericality_of :approved, :equal_to => 10 + Topic.validates_numericality_of :approved, equal_to: 10 invalid!([-10, 11] + INFINITY, 'must be equal to 10') valid!([10]) end def test_validates_numericality_with_less_than - Topic.validates_numericality_of :approved, :less_than => 10 + Topic.validates_numericality_of :approved, less_than: 10 invalid!([10], 'must be less than 10') valid!([-9, 9]) end def test_validates_numericality_with_less_than_or_equal_to - Topic.validates_numericality_of :approved, :less_than_or_equal_to => 10 + Topic.validates_numericality_of :approved, less_than_or_equal_to: 10 invalid!([11], 'must be less than or equal to 10') valid!([-10, 10]) end def test_validates_numericality_with_odd - Topic.validates_numericality_of :approved, :odd => true + Topic.validates_numericality_of :approved, odd: true invalid!([-2, 2], 'must be odd') valid!([-1, 1]) end def test_validates_numericality_with_even - Topic.validates_numericality_of :approved, :even => true + Topic.validates_numericality_of :approved, even: true invalid!([-1, 1], 'must be even') valid!([-2, 2]) end def test_validates_numericality_with_greater_than_less_than_and_even - Topic.validates_numericality_of :approved, :greater_than => 1, :less_than => 4, :even => true + Topic.validates_numericality_of :approved, greater_than: 1, less_than: 4, even: true invalid!([1, 3, 4]) valid!([2]) end def test_validates_numericality_with_other_than - Topic.validates_numericality_of :approved, :other_than => 0 + Topic.validates_numericality_of :approved, other_than: 0 invalid!([0, 0.0]) valid!([-1, 42]) @@ -115,30 +130,32 @@ class NumericalityValidationTest < ActiveModel::TestCase 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 } + Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved) invalid!([3, 4]) valid!([5, 6]) + ensure Topic.send(:remove_method, :min_approved) end def test_validates_numericality_with_symbol Topic.send(:define_method, :max_approved, lambda { 5 }) - Topic.validates_numericality_of :approved, :less_than_or_equal_to => :max_approved + Topic.validates_numericality_of :approved, less_than_or_equal_to: :max_approved invalid!([6]) valid!([4, 5]) + ensure Topic.send(:remove_method, :max_approved) end def test_validates_numericality_with_numeric_message - Topic.validates_numericality_of :approved, :less_than => 4, :message => "smaller than %{count}" + Topic.validates_numericality_of :approved, less_than: 4, message: "smaller than %{count}" topic = Topic.new("title" => "numeric test", "approved" => 10) assert !topic.valid? assert_equal ["smaller than 4"], topic.errors[:approved] - Topic.validates_numericality_of :approved, :greater_than => 4, :message => "greater than %{count}" + Topic.validates_numericality_of :approved, greater_than: 4, message: "greater than %{count}" topic = Topic.new("title" => "numeric test", "approved" => 1) assert !topic.valid? @@ -146,7 +163,7 @@ class NumericalityValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_for_ruby_class - Person.validates_numericality_of :karma, :allow_nil => false + Person.validates_numericality_of :karma, allow_nil: false p = Person.new p.karma = "Pix" @@ -157,15 +174,15 @@ class NumericalityValidationTest < ActiveModel::TestCase p.karma = "1234" assert p.valid? ensure - Person.reset_callbacks(:validate) + Person.clear_validators! end def test_validates_numericality_with_invalid_args - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than_or_equal_to => "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than_or_equal_to => "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :greater_than => "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :less_than => "foo" } - assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, :equal_to => "foo" } + assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than_or_equal_to: "foo" } + assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than_or_equal_to: "foo" } + assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, greater_than: "foo" } + assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, less_than: "foo" } + assert_raise(ArgumentError){ Topic.validates_numericality_of :approved, equal_to: "foo" } end private @@ -185,7 +202,7 @@ class NumericalityValidationTest < ActiveModel::TestCase end def with_each_topic_approved_value(values) - topic = Topic.new(:title => "numeric test", :content => "whatever") + topic = Topic.new(title: "numeric test", content: "whatever") values.each do |value| topic.approved = value yield topic, value diff --git a/activemodel/test/cases/validations/presence_validation_test.rb b/activemodel/test/cases/validations/presence_validation_test.rb index 2f228cfa83..ecf16d1e16 100644 --- a/activemodel/test/cases/validations/presence_validation_test.rb +++ b/activemodel/test/cases/validations/presence_validation_test.rb @@ -8,9 +8,9 @@ require 'models/custom_reader' class PresenceValidationTest < ActiveModel::TestCase teardown do - Topic.reset_callbacks(:validate) - Person.reset_callbacks(:validate) - CustomReader.reset_callbacks(:validate) + Topic.clear_validators! + Person.clear_validators! + CustomReader.clear_validators! end def test_validate_presences diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb index 144532d6f4..8d4b74ee49 100644 --- a/activemodel/test/cases/validations/validates_test.rb +++ b/activemodel/test/cases/validations/validates_test.rb @@ -3,7 +3,6 @@ require 'cases/helper' require 'models/person' require 'models/topic' require 'models/person_with_validator' -require 'validators/email_validator' require 'validators/namespace/email_validator' class ValidatesTest < ActiveModel::TestCase @@ -11,26 +10,26 @@ class ValidatesTest < ActiveModel::TestCase teardown :reset_callbacks def reset_callbacks - Person.reset_callbacks(:validate) - Topic.reset_callbacks(:validate) - PersonWithValidator.reset_callbacks(:validate) + Person.clear_validators! + Topic.clear_validators! + PersonWithValidator.clear_validators! end def test_validates_with_messages_empty - Person.validates :title, :presence => {:message => "" } + 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.validates :title, numericality: true person = Person.new person.valid? assert_equal ['is not a number'], person.errors[:title] end def test_validates_with_attribute_specified_as_string - Person.validates "title", :numericality => true + Person.validates "title", numericality: true person = Person.new person.valid? assert_equal ['is not a number'], person.errors[:title] @@ -41,14 +40,14 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_built_in_validation_and_options - Person.validates :salary, :numericality => { :message => 'my custom message' } + Person.validates :salary, numericality: { message: 'my custom message' } person = Person.new person.valid? assert_equal ['my custom message'], person.errors[:salary] end def test_validates_with_validator_class - Person.validates :karma, :email => true + Person.validates :karma, email: true person = Person.new person.valid? assert_equal ['is not an email'], person.errors[:karma] @@ -62,33 +61,33 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_if_as_local_conditions - Person.validates :karma, :presence => true, :email => { :unless => :condition_is_true } + Person.validates :karma, presence: true, email: { unless: :condition_is_true } person = Person.new person.valid? assert_equal ["can't be blank"], person.errors[:karma] end def test_validates_with_if_as_shared_conditions - Person.validates :karma, :presence => true, :email => true, :if => :condition_is_true + Person.validates :karma, presence: true, email: true, if: :condition_is_true person = Person.new person.valid? assert_equal ["can't be blank", "is not an email"], person.errors[:karma].sort end def test_validates_with_unless_shared_conditions - Person.validates :karma, :presence => true, :email => true, :unless => :condition_is_true + Person.validates :karma, presence: true, email: true, unless: :condition_is_true person = Person.new assert person.valid? end def test_validates_with_allow_nil_shared_conditions - Person.validates :karma, :length => { :minimum => 20 }, :email => true, :allow_nil => true + Person.validates :karma, length: { minimum: 20 }, email: true, allow_nil: true person = Person.new assert person.valid? end def test_validates_with_regexp - Person.validates :karma, :format => /positive|negative/ + Person.validates :karma, format: /positive|negative/ person = Person.new assert person.invalid? assert_equal ['is invalid'], person.errors[:karma] @@ -97,7 +96,7 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_array - Person.validates :gender, :inclusion => %w(m f) + Person.validates :gender, inclusion: %w(m f) person = Person.new assert person.invalid? assert_equal ['is not included in the list'], person.errors[:gender] @@ -106,7 +105,7 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_range - Person.validates :karma, :length => 6..20 + Person.validates :karma, length: 6..20 person = Person.new assert person.invalid? assert_equal ['is too short (minimum is 6 characters)'], person.errors[:karma] @@ -115,25 +114,25 @@ class ValidatesTest < ActiveModel::TestCase end def test_validates_with_validator_class_and_options - Person.validates :karma, :email => { :message => 'my custom message' } + Person.validates :karma, email: { message: 'my custom message' } person = Person.new person.valid? assert_equal ['my custom message'], person.errors[:karma] end def test_validates_with_unknown_validator - assert_raise(ArgumentError) { Person.validates :karma, :unknown => true } + assert_raise(ArgumentError) { Person.validates :karma, unknown: true } end def test_validates_with_included_validator - PersonWithValidator.validates :title, :presence => true + PersonWithValidator.validates :title, presence: true person = PersonWithValidator.new person.valid? assert_equal ['Local validator'], person.errors[:title] end def test_validates_with_included_validator_and_options - PersonWithValidator.validates :title, :presence => { :custom => ' please' } + PersonWithValidator.validates :title, presence: { custom: ' please' } person = PersonWithValidator.new person.valid? assert_equal ['Local validator please'], person.errors[:title] @@ -141,7 +140,7 @@ class ValidatesTest < ActiveModel::TestCase def test_validates_with_included_validator_and_wildcard_shortcut # Shortcut for PersonWithValidator.validates :title, like: { with: "Mr." } - PersonWithValidator.validates :title, :like => "Mr." + PersonWithValidator.validates :title, like: "Mr." person = PersonWithValidator.new person.title = "Ms. Pacman" person.valid? @@ -149,7 +148,7 @@ class ValidatesTest < ActiveModel::TestCase end def test_defining_extra_default_keys_for_validates - Topic.validates :title, :confirmation => true, :message => 'Y U NO CONFIRM' + Topic.validates :title, confirmation: true, message: 'Y U NO CONFIRM' topic = Topic.new topic.title = "What's happening" topic.title_confirmation = "Not this" diff --git a/activemodel/test/cases/validations/validations_context_test.rb b/activemodel/test/cases/validations/validations_context_test.rb index 15a49e38dd..005bf118c6 100644 --- a/activemodel/test/cases/validations/validations_context_test.rb +++ b/activemodel/test/cases/validations/validations_context_test.rb @@ -4,10 +4,8 @@ require 'cases/helper' require 'models/topic' class ValidationsContextTest < ActiveModel::TestCase - def teardown - Topic.reset_callbacks(:validate) - Topic._validators.clear + Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" @@ -18,22 +16,35 @@ class ValidationsContextTest < ActiveModel::TestCase end end - test "with a class that adds errors on update and validating a new model with no arguments" do - Topic.validates_with(ValidatorThatAddsErrors, :on => :create) + test "with a class that adds errors on create and validating a new model with no arguments" do + Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new - assert topic.valid?, "Validation doesn't run on create if 'on' is set to update" + assert topic.valid?, "Validation doesn't run on valid? if 'on' is set to create" end test "with a class that adds errors on update and validating a new model" do - Topic.validates_with(ValidatorThatAddsErrors, :on => :update) + Topic.validates_with(ValidatorThatAddsErrors, on: :update) topic = Topic.new assert topic.valid?(:create), "Validation doesn't run on create if 'on' is set to update" end test "with a class that adds errors on create and validating a new model" do - Topic.validates_with(ValidatorThatAddsErrors, :on => :create) + Topic.validates_with(ValidatorThatAddsErrors, on: :create) topic = Topic.new assert topic.invalid?(:create), "Validation does run on create if 'on' is set to create" assert topic.errors[:base].include?(ERROR_MESSAGE) end -end
\ No newline at end of file + + test "with a class that adds errors on multiple contexts and validating a new model" do + Topic.validates_with(ValidatorThatAddsErrors, on: [:context1, :context2]) + + topic = Topic.new + assert topic.valid?, "Validation ran with no context given when 'on' is set to context1 and context2" + + assert topic.invalid?(:context1), "Validation did not run on context1 when 'on' is set to context1 and context2" + assert topic.errors[:base].include?(ERROR_MESSAGE) + + assert topic.invalid?(:context2), "Validation did not run on context2 when 'on' is set to context1 and context2" + assert topic.errors[:base].include?(ERROR_MESSAGE) + end +end diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 457f553661..736c2deea8 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -6,8 +6,7 @@ require 'models/topic' class ValidatesWithTest < ActiveModel::TestCase def teardown - Topic.reset_callbacks(:validate) - Topic._validators.clear + Topic.clear_validators! end ERROR_MESSAGE = "Validation error from validator" @@ -50,7 +49,7 @@ class ValidatesWithTest < ActiveModel::TestCase end end - test "vaidation with class that adds errors" do + test "validation with class that adds errors" do Topic.validates_with(ValidatorThatAddsErrors) topic = Topic.new assert topic.invalid?, "A class that adds errors causes the record to be invalid" @@ -72,26 +71,26 @@ class ValidatesWithTest < ActiveModel::TestCase end test "with if statements that return false" do - Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 2") + Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 2") topic = Topic.new assert topic.valid? end test "with if statements that return true" do - Topic.validates_with(ValidatorThatAddsErrors, :if => "1 == 1") + Topic.validates_with(ValidatorThatAddsErrors, if: "1 == 1") topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) end test "with unless statements that return true" do - Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 1") + Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 1") topic = Topic.new assert topic.valid? end test "with unless statements that returns false" do - Topic.validates_with(ValidatorThatAddsErrors, :unless => "1 == 2") + Topic.validates_with(ValidatorThatAddsErrors, unless: "1 == 2") topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) @@ -100,45 +99,23 @@ class ValidatesWithTest < ActiveModel::TestCase test "passes all configuration options to the validator class" do topic = Topic.new validator = mock() - validator.expects(:new).with(:foo => :bar, :if => "1 == 1").returns(validator) + validator.expects(:new).with(foo: :bar, if: "1 == 1", class: Topic).returns(validator) validator.expects(:validate).with(topic) - Topic.validates_with(validator, :if => "1 == 1", :foo => :bar) - assert topic.valid? - end - - test "calls setup method of validator passing in self when validator has setup method" do - topic = Topic.new - validator = stub_everything - validator.stubs(:new).returns(validator) - validator.stubs(:validate) - validator.stubs(:respond_to?).with(:setup).returns(true) - validator.expects(:setup).with(Topic).once - Topic.validates_with(validator) - assert topic.valid? - end - - test "doesn't call setup method of validator when validator has no setup method" do - topic = Topic.new - validator = stub_everything - validator.stubs(:new).returns(validator) - validator.stubs(:validate) - validator.stubs(:respond_to?).with(:setup).returns(false) - validator.expects(:setup).with(Topic).never - Topic.validates_with(validator) + Topic.validates_with(validator, if: "1 == 1", foo: :bar) assert topic.valid? end test "validates_with with options" do - Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name) + Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name) topic = Topic.new assert topic.invalid? assert topic.errors[:base].include?(ERROR_MESSAGE) end test "validates_with each validator" do - Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content]) - topic = Topic.new :title => "Title", :content => "Content" + Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content]) + topic = Topic.new title: "Title", content: "Content" assert topic.invalid? assert_equal ["Value is Title"], topic.errors[:title] assert_equal ["Value is Content"], topic.errors[:content] @@ -146,7 +123,7 @@ class ValidatesWithTest < ActiveModel::TestCase test "each validator checks validity" do assert_raise RuntimeError do - Topic.validates_with(ValidatorCheckValidity, :attributes => [:title]) + Topic.validates_with(ValidatorCheckValidity, attributes: [:title]) end end @@ -157,25 +134,25 @@ class ValidatesWithTest < ActiveModel::TestCase end test "each validator skip nil values if :allow_nil is set to true" do - Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content], :allow_nil => true) - topic = Topic.new :content => "" + Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_nil: true) + topic = Topic.new content: "" assert topic.invalid? assert topic.errors[:title].empty? assert_equal ["Value is "], topic.errors[:content] end test "each validator skip blank values if :allow_blank is set to true" do - Topic.validates_with(ValidatorPerEachAttribute, :attributes => [:title, :content], :allow_blank => true) - topic = Topic.new :content => "" + Topic.validates_with(ValidatorPerEachAttribute, attributes: [:title, :content], allow_blank: true) + topic = Topic.new content: "" assert topic.valid? assert topic.errors[:title].empty? assert topic.errors[:content].empty? end test "validates_with can validate with an instance method" do - Topic.validates :title, :with => :my_validation + Topic.validates :title, with: :my_validation - topic = Topic.new :title => "foo" + topic = Topic.new title: "foo" assert topic.valid? assert topic.errors[:title].empty? @@ -185,9 +162,9 @@ class ValidatesWithTest < ActiveModel::TestCase end test "optionally pass in the attribute being validated when validating with an instance method" do - Topic.validates :title, :content, :with => :my_validation_with_arg + Topic.validates :title, :content, with: :my_validation_with_arg - topic = Topic.new :title => "foo" + topic = Topic.new title: "foo" assert !topic.valid? assert topic.errors[:title].empty? assert_equal ['is missing'], topic.errors[:content] diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index a9d32808da..2b932683ea 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -4,33 +4,25 @@ require 'cases/helper' require 'models/topic' require 'models/reply' require 'models/custom_reader' -require 'models/automobile' require 'active_support/json' require 'active_support/xml_mini' class ValidationsTest < ActiveModel::TestCase - class CustomStrictValidationException < StandardError; end - def setup - Topic._validators.clear - end - - # Most of the tests mess with the validations of Topic, so lets repair it all the time. - # Other classes we mess with will be dealt with in the specific tests def teardown - Topic.reset_callbacks(:validate) + Topic.clear_validators! end def test_single_field_validation r = Reply.new r.title = "There's no content!" - assert r.invalid?, "A reply without content shouldn't be saveable" + assert r.invalid?, "A reply without content should be invalid" assert r.after_validation_performed, "after_validation callback should be called" r.content = "Messa content!" - assert r.valid?, "A reply with content should be saveable" + assert r.valid?, "A reply with content should be valid" assert r.after_validation_performed, "after_validation callback should be called" end @@ -146,6 +138,8 @@ class ValidationsTest < ActiveModel::TestCase assert_equal 4, hits assert_equal %w(gotcha gotcha), t.errors[:title] assert_equal %w(gotcha gotcha), t.errors[:content] + ensure + CustomReader.clear_validators! end def test_validate_block @@ -166,12 +160,56 @@ class ValidationsTest < ActiveModel::TestCase def test_invalid_validator Topic.validate :i_dont_exist - assert_raise(NameError) do + assert_raises(NoMethodError) do t = Topic.new t.valid? end end + def test_invalid_options_to_validate + error = assert_raises(ArgumentError) do + # A common mistake -- we meant to call 'validates' + Topic.validate :title, presence: true + end + message = 'Unknown key: :presence. Valid keys are: :on, :if, :unless, :prepend. Perhaps you meant to call `validates` instead of `validate`?' + assert_equal message, error.message + end + + def test_callback_options_to_validate + klass = Class.new(Topic) do + attr_reader :call_sequence + + def initialize(*) + super + @call_sequence = [] + end + + private + def validator_a + @call_sequence << :a + end + + def validator_b + @call_sequence << :b + end + + def validator_c + @call_sequence << :c + end + end + + assert_nothing_raised do + klass.validate :validator_a, if: ->{ true } + klass.validate :validator_b, prepend: true + klass.validate :validator_c, unless: ->{ true } + end + + t = klass.new + + assert_predicate t, :valid? + assert_equal [:b, :a], t.call_sequence + end + def test_errors_conversions Topic.validates_presence_of %w(title content) t = Topic.new @@ -190,16 +228,16 @@ class ValidationsTest < ActiveModel::TestCase def test_validation_order Topic.validates_presence_of :title - Topic.validates_length_of :title, :minimum => 2 + Topic.validates_length_of :title, minimum: 2 t = Topic.new("title" => "") assert t.invalid? assert_equal "can't be blank", t.errors["title"].first Topic.validates_presence_of :title, :author_name Topic.validate {errors.add('author_email_address', 'will never be valid')} - Topic.validates_length_of :title, :content, :minimum => 2 + Topic.validates_length_of :title, :content, minimum: 2 - t = Topic.new :title => '' + t = Topic.new title: '' assert t.invalid? assert_equal :title, key = t.errors.keys[0] @@ -213,10 +251,10 @@ class ValidationsTest < ActiveModel::TestCase assert_equal 'is too short (minimum is 2 characters)', t.errors[key][0] end - def test_validaton_with_if_and_on - Topic.validates_presence_of :title, :if => Proc.new{|x| x.author_name = "bad"; true }, :on => :update + def test_validation_with_if_and_on + Topic.validates_presence_of :title, if: Proc.new{|x| x.author_name = "bad"; true }, on: :update - t = Topic.new(:title => "") + t = Topic.new(title: "") # If block should not fire assert t.valid? @@ -239,7 +277,7 @@ class ValidationsTest < ActiveModel::TestCase end def test_validation_with_message_as_proc - Topic.validates_presence_of(:title, :message => proc { "no blanks here".upcase }) + Topic.validates_presence_of(:title, message: proc { "no blanks here".upcase }) t = Topic.new assert t.invalid? @@ -248,7 +286,7 @@ class ValidationsTest < ActiveModel::TestCase def test_list_of_validators_for_model Topic.validates_presence_of :title - Topic.validates_length_of :title, :minimum => 2 + Topic.validates_length_of :title, minimum: 2 assert_equal 2, Topic.validators.count assert_equal [:presence, :length], Topic.validators.map(&:kind) @@ -256,7 +294,7 @@ class ValidationsTest < ActiveModel::TestCase def test_list_of_validators_on_an_attribute Topic.validates_presence_of :title, :content - Topic.validates_length_of :title, :minimum => 2 + Topic.validates_length_of :title, minimum: 2 assert_equal 2, Topic.validators_on(:title).count assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind) @@ -265,13 +303,13 @@ class ValidationsTest < ActiveModel::TestCase end def test_accessing_instance_of_validator_on_an_attribute - Topic.validates_length_of :title, :minimum => 10 + Topic.validates_length_of :title, minimum: 10 assert_equal 10, Topic.validators_on(:title).first.options[:minimum] end def test_list_of_validators_on_multiple_attributes - Topic.validates :title, :length => { :minimum => 10 } - Topic.validates :author_name, :presence => true, :format => /a/ + Topic.validates :title, length: { minimum: 10 } + Topic.validates :author_name, presence: true, format: /a/ validators = Topic.validators_on(:title, :author_name) @@ -279,73 +317,88 @@ class ValidationsTest < ActiveModel::TestCase ActiveModel::Validations::FormatValidator, ActiveModel::Validations::LengthValidator, ActiveModel::Validations::PresenceValidator - ], validators.map { |v| v.class }.sort_by { |c| c.to_s } + ], validators.map(&:class).sort_by(&:to_s) end def test_list_of_validators_will_be_empty_when_empty - Topic.validates :title, :length => { :minimum => 10 } + Topic.validates :title, length: { minimum: 10 } assert_equal [], Topic.validators_on(:author_name) end def test_validations_on_the_instance_level - auto = Automobile.new + Topic.validates :title, :author_name, presence: true + Topic.validates :content, length: { minimum: 10 } - assert auto.invalid? - assert_equal 2, auto.errors.size + topic = Topic.new + assert topic.invalid? + assert_equal 3, topic.errors.size + + topic.title = 'Some Title' + topic.author_name = 'Some Author' + topic.content = 'Some Content Whose Length is more than 10.' + assert topic.valid? + end + + def test_validate + Topic.validate do + validates_presence_of :title, :author_name + validates_length_of :content, minimum: 10 + end - auto.make = 'Toyota' - auto.model = 'Corolla' + topic = Topic.new + assert_empty topic.errors - assert auto.valid? + topic.validate + assert_not_empty topic.errors end def test_strict_validation_in_validates - Topic.validates :title, :strict => true, :presence => true + Topic.validates :title, strict: true, presence: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_not_fails - Topic.validates :title, :strict => true, :presence => true - assert Topic.new(:title => "hello").valid? + Topic.validates :title, strict: true, presence: true + assert Topic.new(title: "hello").valid? end def test_strict_validation_particular_validator - Topic.validates :title, :presence => { :strict => true } + Topic.validates :title, presence: { strict: true } assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_in_custom_validator_helper - Topic.validates_presence_of :title, :strict => true + Topic.validates_presence_of :title, strict: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_strict_validation_custom_exception - Topic.validates_presence_of :title, :strict => CustomStrictValidationException + Topic.validates_presence_of :title, strict: CustomStrictValidationException assert_raises CustomStrictValidationException do Topic.new.valid? end end def test_validates_with_bang - Topic.validates! :title, :presence => true + Topic.validates! :title, presence: true assert_raises ActiveModel::StrictValidationFailed do Topic.new.valid? end end def test_validates_with_false_hash_value - Topic.validates :title, :presence => false + Topic.validates :title, presence: false assert Topic.new.valid? end def test_strict_validation_error_message - Topic.validates :title, :strict => true, :presence => true + Topic.validates :title, strict: true, presence: true exception = assert_raises(ActiveModel::StrictValidationFailed) do Topic.new.valid? @@ -354,14 +407,14 @@ class ValidationsTest < ActiveModel::TestCase end def test_does_not_modify_options_argument - options = { :presence => true } + options = { presence: true } Topic.validates :title, options - assert_equal({ :presence => true }, options) + assert_equal({ presence: true }, options) end def test_dup_validity_is_independent Topic.validates_presence_of :title - topic = Topic.new("title" => "Litterature") + topic = Topic.new("title" => "Literature") topic.valid? duped = topic.dup diff --git a/activemodel/test/models/administrator.rb b/activemodel/test/models/administrator.rb deleted file mode 100644 index 2f3aff290c..0000000000 --- a/activemodel/test/models/administrator.rb +++ /dev/null @@ -1,11 +0,0 @@ -class Administrator - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::SecurePassword - - define_model_callbacks :create - - attr_accessor :name, :password_digest - - has_secure_password -end diff --git a/activemodel/test/models/automobile.rb b/activemodel/test/models/automobile.rb deleted file mode 100644 index 021ea61c80..0000000000 --- a/activemodel/test/models/automobile.rb +++ /dev/null @@ -1,12 +0,0 @@ -class Automobile - include ActiveModel::Validations - - validate :validations - - attr_accessor :make, :model - - def validations - validates_presence_of :make - validates_length_of :model, :within => 2..10 - end -end
\ No newline at end of file diff --git a/activemodel/test/models/contact.rb b/activemodel/test/models/contact.rb index 7bfc542afb..bcfd267a34 100644 --- a/activemodel/test/models/contact.rb +++ b/activemodel/test/models/contact.rb @@ -1,15 +1,20 @@ class Contact extend ActiveModel::Naming include ActiveModel::Conversion + include ActiveModel::Validations + + include ActiveModel::Serializers::JSON + include ActiveModel::Serializers::Xml attr_accessor :id, :name, :age, :created_at, :awesome, :preferences + attr_accessor :address, :friends, :contact def social %w(twitter github) end def network - {:git => :github} + { git: :github } end def initialize(options = {}) @@ -23,4 +28,14 @@ class Contact def persisted? id end + + def attributes=(hash) + hash.each do |k, v| + instance_variable_set("@#{k}", v) + end + end + + def attributes + instance_values.except("address", "friends", "contact") + end end diff --git a/activemodel/test/models/oauthed_user.rb b/activemodel/test/models/oauthed_user.rb deleted file mode 100644 index 9750bc19d4..0000000000 --- a/activemodel/test/models/oauthed_user.rb +++ /dev/null @@ -1,11 +0,0 @@ -class OauthedUser - extend ActiveModel::Callbacks - include ActiveModel::Validations - include ActiveModel::SecurePassword - - define_model_callbacks :create - - has_secure_password(validations: false) - - attr_accessor :password_digest, :password_salt -end diff --git a/activemodel/test/models/reply.rb b/activemodel/test/models/reply.rb index ec1efeac19..b77910e671 100644 --- a/activemodel/test/models/reply.rb +++ b/activemodel/test/models/reply.rb @@ -2,11 +2,11 @@ require 'models/topic' class Reply < Topic validate :errors_on_empty_content - validate :title_is_wrong_create, :on => :create + validate :title_is_wrong_create, on: :create validate :check_empty_title - validate :check_content_mismatch, :on => :create - validate :check_wrong_update, :on => :update + validate :check_content_mismatch, on: :create + validate :check_wrong_update, on: :update def check_empty_title errors[:title] << "is Empty" unless title && title.size > 0 diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb index c9af78f595..1411a093e9 100644 --- a/activemodel/test/models/topic.rb +++ b/activemodel/test/models/topic.rb @@ -6,7 +6,7 @@ class Topic super | [ :message ] end - attr_accessor :title, :author_name, :content, :approved + attr_accessor :title, :author_name, :content, :approved, :created_at attr_accessor :after_validation_performed after_validation :perform_after_validation diff --git a/activemodel/test/models/user.rb b/activemodel/test/models/user.rb index 4b11df12bf..1ec6001c48 100644 --- a/activemodel/test/models/user.rb +++ b/activemodel/test/models/user.rb @@ -1,11 +1,10 @@ class User extend ActiveModel::Callbacks - include ActiveModel::Validations include ActiveModel::SecurePassword define_model_callbacks :create has_secure_password - attr_accessor :password_digest, :password_salt + attr_accessor :password_digest end diff --git a/activemodel/test/models/visitor.rb b/activemodel/test/models/visitor.rb index 4d7f4be097..22ad1a3c3d 100644 --- a/activemodel/test/models/visitor.rb +++ b/activemodel/test/models/visitor.rb @@ -1,6 +1,5 @@ class Visitor extend ActiveModel::Callbacks - include ActiveModel::Validations include ActiveModel::SecurePassword define_model_callbacks :create |