From 7d84c3a2f7ede0e8d04540e9c0640de7378e9b3a Mon Sep 17 00:00:00 2001 From: Nick Sutterer Date: Mon, 13 May 2013 13:59:28 +1000 Subject: deprecate Validator#setup (to get rid of a respond_to call). validators do their setup in their constructor now. --- activemodel/CHANGELOG.md | 4 ++- .../lib/active_model/validations/acceptance.rb | 4 ++- .../lib/active_model/validations/confirmation.rb | 8 +++++- activemodel/lib/active_model/validations/with.rb | 3 +- activemodel/lib/active_model/validator.rb | 32 ++++++++++++++++------ .../test/cases/validations/with_validation_test.rb | 24 +--------------- activemodel/test/cases/validations_test.rb | 21 ++++++++++++++ .../lib/active_record/validations/uniqueness.rb | 7 +---- 8 files changed, 61 insertions(+), 42 deletions(-) diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index eb54b58888..8c7af2d078 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,5 @@ -* No changes. +* Deprecate `Validator#setup`. This should be done manually now in the validator's constructor. + + *Nick Sutterer* Please check [4-0-stable](https://github.com/rails/rails/blob/4-0-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index 78e6f67a47..139de16326 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -4,6 +4,7 @@ module ActiveModel class AcceptanceValidator < EachValidator # :nodoc: def initialize(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) diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 1d85378892..b0542661af 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -2,6 +2,11 @@ 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) @@ -9,7 +14,8 @@ module ActiveModel end end - def setup(klass) + private + def setup!(klass) klass.send(:attr_reader, *attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") end.compact) diff --git a/activemodel/lib/active_model/validations/with.rb b/activemodel/lib/active_model/validations/with.rb index 2ae335d0f4..16bd6670d1 100644 --- a/activemodel/lib/active_model/validations/with.rb +++ b/activemodel/lib/active_model/validations/with.rb @@ -83,9 +83,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| diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index 037650e5ac..690856aee1 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -82,18 +82,16 @@ module ActiveModel # validates :title, presence: 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 accessable 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 validates_with method. class Validator attr_reader :options @@ -107,7 +105,8 @@ module ActiveModel # Accepts options that will be made available through the +options+ reader. def initialize(options = {}) - @options = options.freeze + @options = options.except(:class).freeze + deprecated_setup(options) end # Return the kind for this validator. @@ -123,6 +122,21 @@ module ActiveModel def validate(record) raise NotImplementedError, "Subclasses must implement a validate(record) method." end + + private + def deprecated_setup(options) # TODO: remove me in 4.2. + return unless respond_to?(:setup) + ActiveSupport::Deprecation.warn "The `Validator#setup` instance method is deprecated and will be removed on Rails 4.2. Do your setup in the constructor instead: + +class MyValidator < ActiveModel::Validator + def initialize(options={}) + super + options[:class].send :attr_accessor, :custom_attribute + end +end +" + setup(options[:class]) + end end # +EachValidator+ is a validator which iterates through the attributes given diff --git a/activemodel/test/cases/validations/with_validation_test.rb b/activemodel/test/cases/validations/with_validation_test.rb index efe16d9aa9..93716f1433 100644 --- a/activemodel/test/cases/validations/with_validation_test.rb +++ b/activemodel/test/cases/validations/with_validation_test.rb @@ -100,35 +100,13 @@ 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) - assert topic.valid? - end - test "validates_with with options" do Topic.validates_with(ValidatorThatValidatesOptions, field: :first_name) topic = Topic.new diff --git a/activemodel/test/cases/validations_test.rb b/activemodel/test/cases/validations_test.rb index 3e84297cc2..039b6b8872 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -373,4 +373,25 @@ class ValidationsTest < ActiveModel::TestCase assert topic.invalid? assert duped.valid? end + + # validator test: + def test_setup_is_deprecated_but_still_receives_klass # TODO: remove me in 4.2. + validator_class = Class.new(ActiveModel::Validator) do + def setup(klass) + @old_klass = klass + end + + def validate(*) + @old_klass == Topic or raise "#setup didn't work" + end + end + + assert_deprecated do + Topic.validates_with validator_class + end + + t = Topic.new + t.valid? + end + end diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb index a705d8c2c4..52e46e1ffe 100644 --- a/activerecord/lib/active_record/validations/uniqueness.rb +++ b/activerecord/lib/active_record/validations/uniqueness.rb @@ -7,11 +7,7 @@ module ActiveRecord "Pass a callable instead: `conditions: -> { where(approved: true) }`" end super({ case_sensitive: true }.merge!(options)) - end - - # Unfortunately, we have to tie Uniqueness validators to a class. - def setup(klass) - @klass = klass + @klass = options[:class] end def validate_each(record, attribute, value) @@ -34,7 +30,6 @@ module ActiveRecord end protected - # The check for an existing value should be run from a class that # isn't abstract. This means working down from the current class # (self), to the first non-abstract class. Since classes don't know -- cgit v1.2.3