diff options
Diffstat (limited to 'activemodel')
-rw-r--r-- | activemodel/CHANGELOG.md | 9 | ||||
-rw-r--r-- | activemodel/Rakefile | 1 | ||||
-rw-r--r-- | activemodel/lib/active_model/callbacks.rb | 7 | ||||
-rw-r--r-- | activemodel/lib/active_model/dirty.rb | 20 | ||||
-rw-r--r-- | activemodel/lib/active_model/lint.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/secure_password.rb | 7 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/json.rb | 2 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations.rb | 5 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/acceptance.rb | 4 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/callbacks.rb | 5 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/confirmation.rb | 8 | ||||
-rw-r--r-- | activemodel/lib/active_model/validations/with.rb | 3 | ||||
-rw-r--r-- | activemodel/lib/active_model/validator.rb | 32 | ||||
-rw-r--r-- | activemodel/test/cases/railtie_test.rb | 5 | ||||
-rw-r--r-- | activemodel/test/cases/secure_password_test.rb | 9 | ||||
-rw-r--r-- | activemodel/test/cases/validations/with_validation_test.rb | 24 | ||||
-rw-r--r-- | activemodel/test/cases/validations_test.rb | 23 |
17 files changed, 106 insertions, 60 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index eb54b58888..6fc34ecd60 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,10 @@ -* No changes. +* Fix regression in has_secure_password. When a password is set, but a + confirmation is an empty string, it would incorrectly save. + + *Steve Klabnik* and *Phillip Calvin* + +* 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/Rakefile b/activemodel/Rakefile index fc5aaf9f8f..45d1587ed6 100644 --- a/activemodel/Rakefile +++ b/activemodel/Rakefile @@ -20,7 +20,6 @@ namespace :test do end end -require 'rake/packagetask' require 'rubygems/package_task' spec = eval(File.read("#{dir}/activemodel.gemspec")) diff --git a/activemodel/lib/active_model/callbacks.rb b/activemodel/lib/active_model/callbacks.rb index 94d2181e4d..377aa6ee27 100644 --- a/activemodel/lib/active_model/callbacks.rb +++ b/activemodel/lib/active_model/callbacks.rb @@ -100,7 +100,7 @@ module ActiveModel def define_model_callbacks(*callbacks) options = callbacks.extract_options! options = { - terminator: "result == false", + terminator: ->(_,result) { result == false }, skip_after_callbacks_if_terminated: true, scope: [:kind, :name], only: [:before, :around, :after] @@ -135,7 +135,10 @@ module ActiveModel klass.define_singleton_method("after_#{callback}") do |*args, &block| options = args.extract_options! options[:prepend] = true - options[:if] = Array(options[:if]) << "value != false" + conditional = ActiveSupport::Callbacks::Conditionals::Value.new { |v| + v != false + } + options[:if] = Array(options[:if]) << conditional set_callback(:"#{callback}", :after, *(args << options), &block) end end diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb index cafdb946c0..ea5ddf71de 100644 --- a/activemodel/lib/active_model/dirty.rb +++ b/activemodel/lib/active_model/dirty.rb @@ -142,23 +142,23 @@ module ActiveModel @changed_attributes ||= {} end - private + # Handle <tt>*_changed?</tt> for +method_missing+. + def attribute_changed?(attr) + changed_attributes.include?(attr) + end - # Handle <tt>*_changed?</tt> for +method_missing+. - def attribute_changed?(attr) - changed_attributes.include?(attr) - end + # Handle <tt>*_was</tt> for +method_missing+. + def attribute_was(attr) + attribute_changed?(attr) ? changed_attributes[attr] : __send__(attr) + end + + private # Handle <tt>*_change</tt> for +method_missing+. def attribute_change(attr) [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) diff --git a/activemodel/lib/active_model/lint.rb b/activemodel/lib/active_model/lint.rb index 1be2913f0b..46b446dc08 100644 --- a/activemodel/lib/active_model/lint.rb +++ b/activemodel/lib/active_model/lint.rb @@ -98,7 +98,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/secure_password.rb b/activemodel/lib/active_model/secure_password.rb index 750fd723a0..e553590671 100644 --- a/activemodel/lib/active_model/secure_password.rb +++ b/activemodel/lib/active_model/secure_password.rb @@ -56,8 +56,9 @@ module ActiveModel include InstanceMethodsOnActivation if options.fetch(:validations, true) - validates_confirmation_of :password + validates_confirmation_of :password, if: lambda { |m| m.password.present? } validates_presence_of :password, on: :create + validates_presence_of :password_confirmation, if: lambda { |m| m.password.present? } before_create { raise "Password digest missing on new record" if password_digest.blank? } end @@ -106,9 +107,7 @@ module ActiveModel end def password_confirmation=(unencrypted_password) - unless unencrypted_password.blank? - @password_confirmation = unencrypted_password - end + @password_confirmation = unencrypted_password end end end diff --git a/activemodel/lib/active_model/serializers/json.rb b/activemodel/lib/active_model/serializers/json.rb index 9d984b7a18..05e2e089e5 100644 --- a/activemodel/lib/active_model/serializers/json.rb +++ b/activemodel/lib/active_model/serializers/json.rb @@ -109,7 +109,7 @@ module ActiveModel # # def attributes=(hash) # hash.each do |key, value| - # instance_variable_set("@#{key}", value) + # send("#{key}=", value) # end # end # diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 92206450d2..31c2245265 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -142,7 +142,9 @@ module ActiveModel if options.key?(:on) options = options.dup options[:if] = Array(options[:if]) - options[:if].unshift("validation_context == :#{options[:on]}") + options[:if].unshift lambda { |o| + o.validation_context == options[:on] + } end args << options set_callback(:validate, *args, &block) @@ -226,7 +228,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| 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/callbacks.rb b/activemodel/lib/active_model/validations/callbacks.rb index 8de36b4f8b..cabb9482f2 100644 --- a/activemodel/lib/active_model/validations/callbacks.rb +++ b/activemodel/lib/active_model/validations/callbacks.rb @@ -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 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 <tt>validates_with</tt> 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/railtie_test.rb b/activemodel/test/cases/railtie_test.rb index a0cd1402b1..0643fa775d 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 = mock() + @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 02cd3b8a93..0b900d934d 100644 --- a/activemodel/test/cases/secure_password_test.rb +++ b/activemodel/test/cases/secure_password_test.rb @@ -94,4 +94,13 @@ class SecurePasswordTest < ActiveModel::TestCase @user.password_confirmation = "" assert @user.valid?(:update), "user should be valid" end + + test "will not save if confirmation is blank but password is not" do + @user.password = "password" + @user.password_confirmation = "" + assert_not @user.valid?(:create) + + @user.password_confirmation = "password" + assert @user.valid?(:create) + end end 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 3241a03b53..039b6b8872 100644 --- a/activemodel/test/cases/validations_test.rb +++ b/activemodel/test/cases/validations_test.rb @@ -166,7 +166,7 @@ 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 @@ -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 |