diff options
author | jamie <jamie@soniciq.com> | 2010-01-07 18:44:35 +0100 |
---|---|---|
committer | José Valim <jose.valim@gmail.com> | 2010-01-07 19:23:59 +0100 |
commit | 0a79eb7889e7ac711ff171a453d65f3df57b9237 (patch) | |
tree | 24b32f38be5aee38a950d75178cc59054c4c288c | |
parent | 2dcc53bdbc1103693626625b29df8bfea7c3bcd4 (diff) | |
download | rails-0a79eb7889e7ac711ff171a453d65f3df57b9237.tar.gz rails-0a79eb7889e7ac711ff171a453d65f3df57b9237.tar.bz2 rails-0a79eb7889e7ac711ff171a453d65f3df57b9237.zip |
Add validates method as shortcut to setup validators for a given set of attributes:
class Person < ActiveRecord::Base
include MyValidators
validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 }
validates :email, :presence => true, :email => true
end
[#3058 status:resolved]
Signed-off-by: José Valim <jose.valim@gmail.com>
17 files changed, 328 insertions, 98 deletions
diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index d5460a58bd..573c914e63 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -15,21 +15,26 @@ module ActiveModel module ClassMethods # Validates each attribute against a block. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations + # # validates_each :first_name, :last_name do |record, attr, value| # record.errors.add attr, 'starts with z.' if value[0] == ?z # end # end # # Options: - # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>). + # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, + # other options <tt>:create</tt>, <tt>:update</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 if the validation should - # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The + # occur (e.g. <tt>:if => :allow_validation</tt>, or + # <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should - # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The + # not occur (e.g. <tt>:unless => :skip_validation</tt>, or + # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_each(*attr_names, &block) options = attr_names.extract_options!.symbolize_keys @@ -42,7 +47,9 @@ module ActiveModel # # This can be done with a symbol pointing to a method: # - # class Comment < ActiveRecord::Base + # class Comment + # include ActiveModel::Validations + # # validate :must_be_friends # # def must_be_friends @@ -52,7 +59,9 @@ module ActiveModel # # Or with a block which is passed the current record to be validated: # - # class Comment < ActiveRecord::Base + # class Comment + # include ActiveModel::Validations + # # validate do |comment| # comment.must_be_friends # end @@ -71,6 +80,13 @@ module ActiveModel end set_callback(:validate, *args, &block) end + + private + + def _merge_attributes(attr_names) + options = attr_names.extract_options! + options.merge(:attributes => attr_names) + end end # Returns the Errors object that holds all information about attribute error messages. @@ -90,27 +106,24 @@ module ActiveModel !valid? end - protected - # Hook method defining how an attribute value should be retieved. By default this is assumed - # to be an instance named after the attribute. Override this method in subclasses should you - # need to retrieve the value for a given attribute differently e.g. - # class MyClass - # include ActiveModel::Validations - # - # def initialize(data = {}) - # @data = data - # end - # - # private - # - # def read_attribute_for_validation(key) - # @data[key] - # end - # end - # - def read_attribute_for_validation(key) - send(key) - end + # Hook method defining how an attribute value should be retieved. By default this is assumed + # to be an instance named after the attribute. Override this method in subclasses should you + # need to retrieve the value for a given attribute differently e.g. + # class MyClass + # include ActiveModel::Validations + # + # def initialize(data = {}) + # @data = data + # end + # + # def read_attribute_for_validation(key) + # @data[key] + # end + # end + # + def read_attribute_for_validation(key) + send(key) + end end end diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index bd9463ed27..0423fcd17f 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -10,6 +10,13 @@ module ActiveModel record.errors.add(attribute, :accepted, :default => options[:message]) end end + + def setup(klass) + # Note: instance_methods.map(&:to_s) is important for 1.9 compatibility + # as instance_methods returns symbols unlike 1.8 which returns strings. + new_attributes = attributes.reject { |name| klass.instance_methods.map(&:to_s).include?("#{name}=") } + klass.send(:attr_accessor, *new_attributes) + end end module ClassMethods @@ -37,18 +44,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_acceptance_of(*attr_names) - options = attr_names.extract_options! - - db_cols = begin - column_names - rescue Exception # To ignore both statement and connection errors - [] - end - - names = attr_names.reject { |name| db_cols.include?(name.to_s) } - attr_accessor(*names) - - validates_with AcceptanceValidator, options.merge(:attributes => attr_names) + validates_with AcceptanceValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index b06effdceb..8041d4b61f 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -6,6 +6,10 @@ module ActiveModel return if confirmed.nil? || value == confirmed record.errors.add(attribute, :confirmation, :default => options[:message]) end + + def setup(klass) + klass.send(:attr_accessor, *attributes.map { |attribute| :"#{attribute}_confirmation" }) + end end module ClassMethods @@ -38,9 +42,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_confirmation_of(*attr_names) - options = attr_names.extract_options! - attr_accessor(*(attr_names.map { |n| :"#{n}_confirmation" })) - validates_with ConfirmationValidator, options.merge(:attributes => attr_names) + validates_with ConfirmationValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/exclusion.rb b/activemodel/lib/active_model/validations/exclusion.rb index f8759f253b..0a44c6d54f 100644 --- a/activemodel/lib/active_model/validations/exclusion.rb +++ b/activemodel/lib/active_model/validations/exclusion.rb @@ -2,6 +2,7 @@ module ActiveModel module Validations class ExclusionValidator < EachValidator def check_validity! + options[:in] ||= options.delete(:within) raise ArgumentError, "An object with the method include? is required must be supplied as the " << ":in option of the configuration hash" unless options[:in].respond_to?(:include?) end @@ -33,9 +34,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_exclusion_of(*attr_names) - options = attr_names.extract_options! - options[:in] ||= options.delete(:within) - validates_with ExclusionValidator, options.merge(:attributes => attr_names) + validates_with ExclusionValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/format.rb b/activemodel/lib/active_model/validations/format.rb index d5427c2b03..9a9e7eca4d 100644 --- a/activemodel/lib/active_model/validations/format.rb +++ b/activemodel/lib/active_model/validations/format.rb @@ -8,6 +8,20 @@ module ActiveModel record.errors.add(attribute, :invalid, :default => options[:message], :value => value) end end + + def check_validity! + unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" + raise ArgumentError, "Either :with or :without must be supplied (but not both)" + end + + if options[:with] && !options[:with].is_a?(Regexp) + raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash" + end + + if options[:without] && !options[:without].is_a?(Regexp) + raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash" + end + end end module ClassMethods @@ -43,21 +57,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_format_of(*attr_names) - options = attr_names.extract_options! - - unless options.include?(:with) ^ options.include?(:without) # ^ == xor, or "exclusive or" - raise ArgumentError, "Either :with or :without must be supplied (but not both)" - end - - if options[:with] && !options[:with].is_a?(Regexp) - raise ArgumentError, "A regular expression must be supplied as the :with option of the configuration hash" - end - - if options[:without] && !options[:without].is_a?(Regexp) - raise ArgumentError, "A regular expression must be supplied as the :without option of the configuration hash" - end - - validates_with FormatValidator, options.merge(:attributes => attr_names) + validates_with FormatValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/inclusion.rb b/activemodel/lib/active_model/validations/inclusion.rb index a122e9e737..25b8c7866d 100644 --- a/activemodel/lib/active_model/validations/inclusion.rb +++ b/activemodel/lib/active_model/validations/inclusion.rb @@ -2,6 +2,7 @@ module ActiveModel module Validations class InclusionValidator < EachValidator def check_validity! + options[:in] ||= options.delete(:within) raise ArgumentError, "An object with the method include? is required must be supplied as the " << ":in option of the configuration hash" unless options[:in].respond_to?(:include?) end @@ -33,9 +34,7 @@ module ActiveModel # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The # method, proc or string should return or evaluate to a true or false value. def validates_inclusion_of(*attr_names) - options = attr_names.extract_options! - options[:in] ||= options.delete(:within) - validates_with InclusionValidator, options.merge(:attributes => attr_names) + validates_with InclusionValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/length.rb b/activemodel/lib/active_model/validations/length.rb index 6e90a75c17..f41ce34328 100644 --- a/activemodel/lib/active_model/validations/length.rb +++ b/activemodel/lib/active_model/validations/length.rb @@ -107,8 +107,7 @@ module ActiveModel # count words as in above example.) # Defaults to <tt>lambda{ |value| value.split(//) }</tt> which counts individual characters. def validates_length_of(*attr_names) - options = attr_names.extract_options! - validates_with LengthValidator, options.merge(:attributes => attr_names) + validates_with LengthValidator, _merge_attributes(attr_names) end alias_method :validates_size_of, :validates_length_of diff --git a/activemodel/lib/active_model/validations/numericality.rb b/activemodel/lib/active_model/validations/numericality.rb index f2aab8c5b8..9dfc5125cd 100644 --- a/activemodel/lib/active_model/validations/numericality.rb +++ b/activemodel/lib/active_model/validations/numericality.rb @@ -103,8 +103,7 @@ module ActiveModel # end # def validates_numericality_of(*attr_names) - options = attr_names.extract_options! - validates_with NumericalityValidator, options.merge(:attributes => attr_names) + validates_with NumericalityValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/presence.rb b/activemodel/lib/active_model/validations/presence.rb index a4c6f866a7..4a71cf79b5 100644 --- a/activemodel/lib/active_model/validations/presence.rb +++ b/activemodel/lib/active_model/validations/presence.rb @@ -34,8 +34,7 @@ module ActiveModel # The method, proc or string should return or evaluate to a true or false value. # def validates_presence_of(*attr_names) - options = attr_names.extract_options! - validates_with PresenceValidator, options.merge(:attributes => attr_names) + validates_with PresenceValidator, _merge_attributes(attr_names) end end end diff --git a/activemodel/lib/active_model/validations/validates.rb b/activemodel/lib/active_model/validations/validates.rb new file mode 100644 index 0000000000..e8935d3794 --- /dev/null +++ b/activemodel/lib/active_model/validations/validates.rb @@ -0,0 +1,74 @@ +module ActiveModel + module Validations + module ClassMethods + # This method is a shortcut to all default validators and any custom + # validator classes ending in 'Validator'. Note that Rails default + # validators can be overridden inside specific classes by creating + # custom validator classes in their place such as PresenceValidator. + # + # Examples of using the default rails validators: + # 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 :age, :inclusion => { :in => 0..9 } + # validates :first_name, :length => { :maximum => 30 } + # validates :age, :numericality => true + # validates :username, :presence => true + # validates :username, :uniqueness => true + # + # The power of the +validates+ method comes when using cusom validators + # and default validators in one call for a given attribute e.g. + # class EmailValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << (options[:message] || "is not an email") unless + # value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + # end + # end + # + # class Person + # include ActiveModel::Validations + # attr_accessor :name, :email + # + # validates :name, :presence => true, :uniqueness => true, :length => { :maximum => 100 } + # validates :email, :presence => true, :email => true + # end + # + # Validator classes my also exist within the class being validated + # allowing custom modules of validators to be included as needed e.g. + # + # module MyValidators + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << "must start with 'the'" unless =~ /^the/i + # end + # end + # end + # + # class Film + # include ActiveModel::Validations + # include MyValidators + # + # validates :name, :title => true + # end + # + def validates(*attributes) + validations = attributes.extract_options! + + raise ArgumentError, "You need to supply at least one attribute" if attributes.empty? + raise ArgumentError, "Attribute names must be symbols" if attributes.any?{ |attribute| !attribute.is_a?(Symbol) } + raise ArgumentError, "You need to supply at least one validation" if validations.empty? + + validations.each do |key, options| + begin + validator = const_get("#{key.to_s.camelize}Validator") + rescue NameError + raise ArgumentError, "Unknown validator: '#{key}'" + end + + validates_with(validator, (options == true ? {} : options).merge(:attributes => attributes)) + end + end + end + end +end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 8d521173c6..db563876af 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -2,14 +2,16 @@ module ActiveModel module Validations module ClassMethods - # Passes the record off to the class or classes specified and allows them to add errors based on more complex conditions. + # Passes the record off to the class or classes specified and allows them + # to add errors based on more complex conditions. # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # - # class MyValidator < ActiveRecord::Validator - # def validate + # class MyValidator < ActiveModel::Validator + # def validate(record) # if some_complex_logic # record.errors[:base] << "This record is invalid" # end @@ -23,37 +25,46 @@ module ActiveModel # # You may also pass it multiple classes, like so: # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator, MyOtherValidator, :on => :create # end # # Configuration options: - # * <tt>on</tt> - Specifies when this validation is active (<tt>:create</tt> or <tt>:update</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>). + # * <tt>on</tt> - Specifies when this validation is active + # (<tt>:create</tt> or <tt>:update</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, proc or string should return or evaluate to a true or false value. - # * <tt>unless</tt> - Specifies a method, proc or string to call to determine if the validation should - # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). + # * <tt>unless</tt> - Specifies a method, proc or string to call to + # determine if the validation should not occur + # (e.g. <tt>:unless => :skip_validation</tt>, or + # <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). # The method, proc or string should return or evaluate to a true or false value. # - # If you pass any additional configuration options, they will be passed to the class and available as <tt>options</tt>: + # If you pass any additional configuration options, they will be passed + # to the class and available as <tt>options</tt>: # - # class Person < ActiveRecord::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator, :my_custom_key => "my custom value" # end # - # class MyValidator < ActiveRecord::Validator - # def validate + # class MyValidator < ActiveModel::Validator + # def validate(record) # options[:my_custom_key] # => "my custom value" # end # end # def validates_with(*args, &block) options = args.extract_options! - args.each { |klass| validate(klass.new(options, &block), options) } + args.each do |klass| + validator = klass.new(options, &block) + validator.setup(self) if validator.respond_to?(:setup) + validate(validator, options) + end end end end -end - - +end
\ No newline at end of file diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 01695cb73a..382a4cc98d 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -1,12 +1,13 @@ module ActiveModel #:nodoc: - # A simple base class that can be used along with ActiveModel::Base.validates_with + # A simple base class that can be used along with ActiveModel::Validations::ClassMethods.validates_with # - # class Person < ActiveModel::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # if some_complex_logic # record.errors[:base] = "This record is invalid" # end @@ -18,10 +19,11 @@ module ActiveModel #:nodoc: # end # end # - # Any class that inherits from ActiveModel::Validator will have access to <tt>record</tt>, - # which is an instance of the record being validated, and must implement a method called <tt>validate</tt>. + # Any class that inherits from ActiveModel::Validator must implement a method + # called <tt>validate</tt> which accepts a <tt>record</tt>. # - # class Person < ActiveModel::Base + # class Person + # include ActiveModel::Validations # validates_with MyValidator # end # @@ -36,7 +38,7 @@ module ActiveModel #:nodoc: # from within the validators message # # class MyValidator < ActiveModel::Validator - # def validate + # def validate(record) # record.errors[:base] << "This is some custom error message" # record.errors[:first_name] << "This is some complex validation" # # etc... @@ -51,13 +53,47 @@ module ActiveModel #:nodoc: # @my_custom_field = options[:field_name] || :first_name # end # end + # + # The easiest way to add custom validators for validating individual attributes + # is with the convenient ActiveModel::EachValidator for example: + # + # class TitleValidator < ActiveModel::EachValidator + # def validate_each(record, attribute, value) + # record.errors[attribute] << 'must be Mr. Mrs. or Dr.' unless ['Mr.', 'Mrs.', 'Dr.'].include?(value) + # end + # end + # + # This can now be used in combination with the +validates+ method + # (see ActiveModel::Validations::ClassMethods.validates for more on this) + # + # class Person + # include ActiveModel::Validations + # attr_accessor :title + # + # 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 it's argument. This can be + # useful when there are prerequisites such as an attr_accessor being present + # for example: + # + # class MyValidator < ActiveModel::Validator + # def setup(klass) + # klass.send :attr_accessor, :custom_attribute + # end + # end + # class Validator attr_reader :options + # Accepts options that will be made availible through the +options+ reader. def initialize(options) @options = options end + # Override this method in subclasses with validation logic, adding errors + # to the records +errors+ array where necessary. def validate(record) raise NotImplementedError end @@ -70,7 +106,10 @@ module ActiveModel #:nodoc: # All ActiveModel validations are built on top of this Validator. class EachValidator < Validator attr_reader :attributes - + + # Returns a new validator instance. All options will be available via the + # +options+ reader, however the <tt>:attributes</tt> option will be removed + # and instead be made available through the +attributes+ reader. def initialize(options) @attributes = Array(options.delete(:attributes)) raise ":attributes cannot be blank" if @attributes.empty? @@ -78,18 +117,26 @@ module ActiveModel #:nodoc: check_validity! end + # Performs validation on the supplied record. By default this will call + # +validates_each+ to determine validity therefore subclasses should + # override +validates_each+ with validation logic. def validate(record) attributes.each do |attribute| - value = record.send(:read_attribute_for_validation, attribute) + value = record.read_attribute_for_validation(attribute) next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) validate_each(record, attribute, value) end end + # Override this method in subclasses with the validation logic, adding + # errors to the records +errors+ array where necessary. def validate_each(record, attribute, value) raise NotImplementedError end + # Hook method that gets called by the initializer allowing verification + # that the arguments supplied are valid. You could for example raise an + # ArgumentError when invalid options are supplied. def check_validity! end end @@ -103,6 +150,8 @@ module ActiveModel #:nodoc: super end + private + def validate_each(record, attribute, value) @block.call(record, attribute, value) end diff --git a/activemodel/test/cases/validations/validates_test.rb b/activemodel/test/cases/validations/validates_test.rb new file mode 100644 index 0000000000..c0bf03efc7 --- /dev/null +++ b/activemodel/test/cases/validations/validates_test.rb @@ -0,0 +1,53 @@ +# encoding: utf-8 +require 'cases/helper' +require 'models/person' +require 'models/person_with_validator' +require 'validators/email_validator' + +class ValidatesTest < ActiveRecord::TestCase + def test_validates_with_built_in_validation + Person.validates :title, :numericality => true + person = Person.new + person.valid? + assert person.errors[:title].include?('is not a number') + end + + def test_validates_with_built_in_validation_and_options + Person.validates :title, :numericality => { :message => 'my custom message' } + person = Person.new + person.valid? + assert person.errors[:title].include?('my custom message') + end + + def test_validates_with_validator_class + Person.validates :karma, :email => true + person = Person.new + person.valid? + assert person.errors[:karma].include?('is not an email') + end + + def test_validates_with_validator_class_and_options + Person.validates :karma, :email => { :message => 'my custom message' } + person = Person.new + person.valid? + assert person.errors[:karma].include?('my custom message') + end + + def test_validates_with_unknown_validator + assert_raise(ArgumentError) { Person.validates :karma, :unknown => true } + end + + def test_validates_with_included_validator + PersonWithValidator.validates :title, :presence => true + person = PersonWithValidator.new + person.valid? + assert person.errors[:title].include?('Local validator') + end + + def test_validates_with_included_validator_and_options + PersonWithValidator.validates :title, :presence => { :custom => ' please' } + person = PersonWithValidator.new + person.valid? + assert person.errors[:title].include?('Local validator please') + end +end
\ No newline at end of file diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index 7540ccb580..66b072ea38 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -120,6 +120,28 @@ class ValidatesWithTest < ActiveRecord::TestCase 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) + assert topic.valid? + end test "validates_with with options" do Topic.validates_with(ValidatorThatValidatesOptions, :field => :first_name) diff --git a/activemodel/test/models/custom_reader.rb b/activemodel/test/models/custom_reader.rb index 7ac70e6167..14a8be9ebc 100644 --- a/activemodel/test/models/custom_reader.rb +++ b/activemodel/test/models/custom_reader.rb @@ -8,8 +8,6 @@ class CustomReader def []=(key, value) @data[key] = value end - - private def read_attribute_for_validation(key) @data[key] diff --git a/activemodel/test/models/person_with_validator.rb b/activemodel/test/models/person_with_validator.rb new file mode 100644 index 0000000000..f9763ea853 --- /dev/null +++ b/activemodel/test/models/person_with_validator.rb @@ -0,0 +1,11 @@ +class PersonWithValidator + include ActiveModel::Validations + + class PresenceValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + record.errors[attribute] << "Local validator#{options[:custom]}" if value.blank? + end + end + + attr_accessor :title, :karma +end diff --git a/activemodel/test/validators/email_validator.rb b/activemodel/test/validators/email_validator.rb new file mode 100644 index 0000000000..cff47ac230 --- /dev/null +++ b/activemodel/test/validators/email_validator.rb @@ -0,0 +1,6 @@ +class EmailValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + record.errors[attribute] << (options[:message] || "is not an email") unless + value =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i + end +end
\ No newline at end of file |