From a30e32aab00f9f1497e25361217399490bc01ec8 Mon Sep 17 00:00:00 2001 From: Sean Huber Date: Wed, 20 Aug 2008 11:29:55 -0700 Subject: [PATCH] validates_exclusion_of can now accept a symbol representing an instance method or a proc object as the :in/:within option --- .../lib/active_model/validations/exclusion.rb | 25 +++++++++++++-- activerecord/lib/active_record/validations.rb | 31 +++++++++++++++---- activerecord/test/cases/validations_test.rb | 31 ++++++++++++++++++++ 3 files changed, 76 insertions(+), 11 deletions(-) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index f3367ab..1a4dd87 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -5,12 +5,20 @@ module ActiveModel # # class Person < ActiveRecord::Base # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" + # validates_exclusion_of :username, :in => :some_method_that_returns_an_enumerable_object + # validates_exclusion_of :username, :in => Proc.new { |person| person.some_method_that_returns_an_enumerable_object } # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed" + # + # def some_method_that_returns_an_enumerable_object + # %w( admin superuser ) + # end # end # # Configuration options: - # * :in - An enumerable object of items that the value shouldn't be part of + # * :in - An enumerable object, a symbol representing an instance method of the current class that returns + # an enumerable object, or a Proc that returns an enumerable object of items that the value shouldn't be part of + # * :within - A synonym (or alias) for :in # * :message - Specifies a custom error message (default is: "is reserved") # * :allow_nil - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+) # * :allow_blank - If set to +true+, skips this validation if the attribute is blank (default is: +false+) @@ -26,10 +34,19 @@ module ActiveModel enum = configuration[:in] || configuration[:within] - raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?") - validates_each(attr_names, configuration) do |record, attr_name, value| - record.errors.add(attr_name, configuration[:message] % value) if enum.include?(value) + enum_object = case enum.class.to_s + when 'Symbol' + then record.send(enum) + when 'Proc' + then enum.call(record) + else + enum + end + + raise(ArgumentError, "An object with the method include? is required must be supplied as the :in or :within option of the configuration hash") unless enum_object.respond_to?("include?") + + record.errors.add(attr_name, configuration[:message] % value) if enum_object.include?(value) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index b7e6394..7b0d188 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -745,15 +745,23 @@ module ActiveRecord # # class Person < ActiveRecord::Base # validates_exclusion_of :username, :in => %w( admin superuser ), :message => "You don't belong here" + # validates_exclusion_of :username, :in => :some_method_that_returns_an_enumerable_object + # validates_exclusion_of :username, :in => Proc.new { |person| person.some_method_that_returns_an_enumerable_object } # validates_exclusion_of :age, :in => 30..60, :message => "This site is only for under 30 and over 60" # validates_exclusion_of :format, :in => %w( mov avi ), :message => "extension %s is not allowed" + # + # def some_method_that_returns_an_enumerable_object + # %w( admin superuser ) + # end # end # # Configuration options: - # * :in - An enumerable object of items that the value shouldn't be part of. - # * :message - Specifies a custom error message (default is: "is reserved"). - # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+). - # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+). + # * :in - An enumerable object, a symbol representing an instance method of the current class that returns + # an enumerable object, or a Proc that returns an enumerable object of items that the value shouldn't be part of + # * :within - A synonym (or alias) for :in + # * :message - Specifies a custom error message (default is: "is reserved") + # * :allow_nil - If set to +true+, skips this validation if the attribute is +nil+ (default is: +false+) + # * :allow_blank - If set to +true+, skips this validation if the attribute is blank (default is: +false+) # * :if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The # method, proc or string should return or evaluate to a true or false value. @@ -766,10 +774,19 @@ module ActiveRecord enum = configuration[:in] || configuration[:within] - raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?") - validates_each(attr_names, configuration) do |record, attr_name, value| - if enum.include?(value) + enum_object = case enum.class.to_s + when 'Symbol' + then record.send(enum) + when 'Proc' + then enum.call(record) + else + enum + end + + raise(ArgumentError, "An object with the method include? is required must be supplied as the :in or :within option of the configuration hash") unless enum_object.respond_to?("include?") + + if enum_object.include?(value) message = record.errors.generate_message(attr_name, :exclusion, :default => configuration[:message], :value => value) record.errors.add(attr_name, message) end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 4b2d28c..5899a84 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -688,6 +688,37 @@ class ValidationsTest < ActiveRecord::TestCase assert t.errors.on(:title) assert_equal "option monkey is restricted", t.errors["title"] end + + def test_validates_exclusion_of_with_proc_enumerable + Topic.validates_exclusion_of( :title, :in => Proc.new { |topic| [topic.class.to_s] } ) + + assert Topic.create("title" => "something", "content" => "abc").valid? + assert !Topic.create("title" => "Topic", "content" => "abc").valid? + end + + def test_validates_exclusion_of_with_symbol_enumerable + Topic.class_eval "def title_exclusions; ['test']; end" + + Topic.validates_exclusion_of( :title, :in => :title_exclusions ) + + assert Topic.create("title" => "something", "content" => "abc").valid? + assert !Topic.create("title" => "test", "content" => "abc").valid? + end + + def test_validates_exclusion_of_should_raise_argument_error_if_in_option_is_not_enumerable + Topic.validates_exclusion_of( :title, :in => nil ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end + + def test_validates_exclusion_of_should_raise_argument_error_if_in_option_is_a_symbol_and_is_not_enumerable + Topic.validates_exclusion_of( :title, :in => :new_record? ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end + + def test_validates_exclusion_of_should_raise_argument_error_if_in_option_is_a_proc_and_is_not_enumerable + Topic.validates_exclusion_of( :title, :in => Proc.new { |topic| nil } ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end def test_validates_length_of_using_minimum Topic.validates_length_of :title, :minimum => 5 -- 1.5.4.3 From e9b9a481e48f172630a796ba2ac2e6b5e54e0e0b Mon Sep 17 00:00:00 2001 From: Sean Huber Date: Wed, 20 Aug 2008 11:46:24 -0700 Subject: [PATCH] validates_inclusion_of can now accept a symbol representing an instance method or a proc object as the :in/:within option --- .../lib/active_model/validations/inclusion.rb | 25 +++++++++++-- activerecord/lib/active_record/validations.rb | 31 ++++++++++++---- activerecord/test/cases/validations_test.rb | 38 ++++++++++++++++++++ 3 files changed, 83 insertions(+), 11 deletions(-) diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 9fc1caa..6c1eabe 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -5,12 +5,20 @@ module ActiveModel # # class Person < ActiveRecord::Base # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => :some_method_that_returns_an_enumerable_object + # validates_inclusion_of :gender, :in => Proc.new { |person| person.some_method_that_returns_an_enumerable_object } # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" + # + # def some_method_that_returns_an_enumerable_object + # %w( m f ) + # end # end # # Configuration options: - # * :in - An enumerable object of available items + # * :in - An enumerable object, a symbol representing an instance method of the current class that returns + # an enumerable object, or a Proc that returns an enumerable object of available items + # * :within - A synonym (or alias) for :in # * :message - Specifies a custom error message (default is: "is not included in the list") # * :allow_nil - If set to +true+, skips this validation if the attribute is null (default is: +false+) # * :allow_blank - If set to +true+, skips this validation if the attribute is blank (default is: +false+) @@ -26,10 +34,19 @@ module ActiveModel enum = configuration[:in] || configuration[:within] - raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?") - validates_each(attr_names, configuration) do |record, attr_name, value| - record.errors.add(attr_name, configuration[:message] % value) unless enum.include?(value) + enum_object = case enum.class.to_s + when 'Symbol' + then record.send(enum) + when 'Proc' + then enum.call(record) + else + enum + end + + raise(ArgumentError, "An object with the method include? is required must be supplied as the :in or :within option of the configuration hash") unless enum_object.respond_to?("include?") + + record.errors.add(attr_name, configuration[:message] % value) unless enum_object.include?(value) end end end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 7b0d188..0a3a00e 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -710,15 +710,23 @@ module ActiveRecord # # class Person < ActiveRecord::Base # validates_inclusion_of :gender, :in => %w( m f ), :message => "woah! what are you then!??!!" + # validates_inclusion_of :gender, :in => :some_method_that_returns_an_enumerable_object + # validates_inclusion_of :gender, :in => Proc.new { |person| person.some_method_that_returns_an_enumerable_object } # validates_inclusion_of :age, :in => 0..99 # validates_inclusion_of :format, :in => %w( jpg gif png ), :message => "extension %s is not included in the list" + # + # def some_method_that_returns_an_enumerable_object + # %w( m f ) + # end # end # # Configuration options: - # * :in - An enumerable object of available items. - # * :message - Specifies a custom error message (default is: "is not included in the list"). - # * :allow_nil - If set to true, skips this validation if the attribute is +nil+ (default is +false+). - # * :allow_blank - If set to true, skips this validation if the attribute is blank (default is +false+). + # * :in - An enumerable object, a symbol representing an instance method of the current class that returns + # an enumerable object, or a Proc that returns an enumerable object of available items + # * :within - A synonym (or alias) for :in + # * :message - Specifies a custom error message (default is: "is not included in the list") + # * :allow_nil - If set to +true+, skips this validation if the attribute is null (default is: +false+) + # * :allow_blank - If set to +true+, skips this validation if the attribute is blank (default is: +false+) # * :if - Specifies a method, proc or string to call to determine if the validation should # occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The # method, proc or string should return or evaluate to a true or false value. @@ -731,10 +739,19 @@ module ActiveRecord enum = configuration[:in] || configuration[:within] - raise(ArgumentError, "An object with the method include? is required must be supplied as the :in option of the configuration hash") unless enum.respond_to?("include?") - validates_each(attr_names, configuration) do |record, attr_name, value| - unless enum.include?(value) + enum_object = case enum.class.to_s + when 'Symbol' + then record.send(enum) + when 'Proc' + then enum.call(record) + else + enum + end + + raise(ArgumentError, "An object with the method include? is required must be supplied as the :in or :within option of the configuration hash") unless enum_object.respond_to?("include?") + + unless enum_object.include?(value) message = record.errors.generate_message(attr_name, :inclusion, :default => configuration[:message], :value => value) record.errors.add(attr_name, message) end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 5899a84..ab9cfbe 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -652,6 +652,13 @@ class ValidationsTest < ActiveRecord::TestCase assert Topic.create("title" => nil).valid? assert Topic.create("title" => "abcde").valid? end + + def test_validates_inclusion_of + Topic.validates_inclusion_of( :title, :in => %w( abe monkey ) ) + + assert Topic.create("title" => "monkey", "content" => "abc").valid? + assert !Topic.create("title" => "something", "content" => "abc").valid? + 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 %s is not in the list" ) @@ -663,6 +670,37 @@ class ValidationsTest < ActiveRecord::TestCase assert t.errors.on(:title) assert_equal "option uhoh is not in the list", t.errors["title"] end + + def test_validates_inclusion_of_with_proc_enumerable + Topic.validates_inclusion_of( :title, :in => Proc.new { |topic| [topic.class.to_s] } ) + + assert Topic.create("title" => "Topic", "content" => "abc").valid? + assert !Topic.create("title" => "something", "content" => "abc").valid? + end + + def test_validates_inclusion_of_with_symbol_enumerable + Topic.class_eval "def title_inclusions; ['test']; end" + + Topic.validates_inclusion_of( :title, :in => :title_inclusions ) + + assert Topic.create("title" => "test", "content" => "abc").valid? + assert !Topic.create("title" => "something", "content" => "abc").valid? + end + + def test_validates_inclusion_of_should_raise_argument_error_if_in_option_is_not_enumerable + Topic.validates_inclusion_of( :title, :in => nil ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end + + def test_validates_inclusion_of_should_raise_argument_error_if_in_option_is_a_symbol_and_is_not_enumerable + Topic.validates_inclusion_of( :title, :in => :new_record? ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end + + def test_validates_inclusion_of_should_raise_argument_error_if_in_option_is_a_proc_and_is_not_enumerable + Topic.validates_inclusion_of( :title, :in => Proc.new { |topic| nil } ) + assert_raises(ArgumentError) { Topic.create("title" => "something", "content" => "abc") } + end def test_numericality_with_allow_nil_and_getter_method Developer.validates_numericality_of( :salary, :allow_nil => true) -- 1.5.4.3 From 6e637df1fc7244d862fadd46e538fde46f8bbd24 Mon Sep 17 00:00:00 2001 From: Sean Huber Date: Wed, 20 Aug 2008 11:54:15 -0700 Subject: [PATCH] Updated ActiveRecord CHANGELOG --- activerecord/CHANGELOG | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-) diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG index 90be9b7..6245d7e 100644 --- a/activerecord/CHANGELOG +++ b/activerecord/CHANGELOG @@ -1,5 +1,7 @@ *Edge* +* validates_exclusion_of and validates_inclusion_of can now accept a symbol representing an instance method or a proc object as the :in or :within option + * Set config.active_record.timestamped_migrations = false to have migrations with numeric prefix instead of UTC timestamp. #446. [Andrew Stone, Nik Wakelin] * change_column_default preserves the not-null constraint. #617 [Tarmo Tänav] -- 1.5.4.3 From 9f77d913a49a13afc0f16778acd06af962272c47 Mon Sep 17 00:00:00 2001 From: Sean Huber Date: Thu, 21 Aug 2008 10:05:48 -0700 Subject: [PATCH] validates_exclusion/inclusion_of checks 'case enum' instead of 'case enum.class.to_s' --- .../lib/active_model/validations/exclusion.rb | 10 +++++----- .../lib/active_model/validations/inclusion.rb | 10 +++++----- activerecord/lib/active_record/validations.rb | 20 ++++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index 1a4dd87..380dbc3 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -35,11 +35,11 @@ module ActiveModel enum = configuration[:in] || configuration[:within] validates_each(attr_names, configuration) do |record, attr_name, value| - enum_object = case enum.class.to_s - when 'Symbol' - then record.send(enum) - when 'Proc' - then enum.call(record) + enum_object = case enum + when Symbol + record.send(enum) + when Proc + enum.call(record) else enum end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index 6c1eabe..abdbd13 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -35,11 +35,11 @@ module ActiveModel enum = configuration[:in] || configuration[:within] validates_each(attr_names, configuration) do |record, attr_name, value| - enum_object = case enum.class.to_s - when 'Symbol' - then record.send(enum) - when 'Proc' - then enum.call(record) + enum_object = case enum + when Symbol + record.send(enum) + when Proc + enum.call(record) else enum end diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index 0a3a00e..aad1c2f 100644 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -740,11 +740,11 @@ module ActiveRecord enum = configuration[:in] || configuration[:within] validates_each(attr_names, configuration) do |record, attr_name, value| - enum_object = case enum.class.to_s - when 'Symbol' - then record.send(enum) - when 'Proc' - then enum.call(record) + enum_object = case enum + when Symbol + record.send(enum) + when Proc + enum.call(record) else enum end @@ -792,11 +792,11 @@ module ActiveRecord enum = configuration[:in] || configuration[:within] validates_each(attr_names, configuration) do |record, attr_name, value| - enum_object = case enum.class.to_s - when 'Symbol' - then record.send(enum) - when 'Proc' - then enum.call(record) + enum_object = case enum + when Symbol + record.send(enum) + when Proc + enum.call(record) else enum end -- 1.5.4.3