aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authoreileencodes <eileencodes@gmail.com>2015-01-10 16:46:57 -0500
committereileencodes <eileencodes@gmail.com>2015-02-01 16:03:49 -0800
commit27aa4dda7d89ce7332e6d1f3266c3a0cf1c3fb9e (patch)
treef91555ce55e52110d6fc7df6401aa13e227137ee /activerecord
parent226cd8a094c81edb72143173e4d0c88a2eb01d5f (diff)
downloadrails-27aa4dda7d89ce7332e6d1f3266c3a0cf1c3fb9e.tar.gz
rails-27aa4dda7d89ce7332e6d1f3266c3a0cf1c3fb9e.tar.bz2
rails-27aa4dda7d89ce7332e6d1f3266c3a0cf1c3fb9e.zip
Fix validations on child record when record parent has validate: false
Fixes #17621. This 5 year old (or older) issue causes validations to fire when a parent record has `validate: false` option and a child record is saved. It's not the responsibility of the model to validate an associated object unless the object was created or modified by the parent. Clean up tests related to validations `assert_nothing_raised` is not benefiting us in these tests Corrected spelling of "respects" It's better to use `assert_not_operator` over `assert !r.valid`
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG.md12
-rw-r--r--activerecord/lib/active_record/validations/length.rb12
-rw-r--r--activerecord/lib/active_record/validations/presence.rb1
-rw-r--r--activerecord/lib/active_record/validations/uniqueness.rb1
-rw-r--r--activerecord/test/cases/validations/association_validation_test.rb3
-rw-r--r--activerecord/test/cases/validations/length_validation_test.rb19
-rw-r--r--activerecord/test/cases/validations/presence_validation_test.rb16
-rw-r--r--activerecord/test/cases/validations/uniqueness_validation_test.rb17
8 files changed, 76 insertions, 5 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index a17148f1f7..3a0c32e66d 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,15 @@
+* Validation errors would be raised for parent records when an association
+ was saved when the parent had `validate: false`. It should not be the
+ responsibility of the model to validate an associated object unless the
+ object was created or modified by the parent.
+
+ This fixes the issue by skipping validations if the parent record is
+ persisted, not changed, and not marked for destruction.
+
+ Fixes #17621.
+
+ *Eileen M. Uchitelle, Aaron Patterson*
+
* Fix n+1 query problem when eager loading nil associations (fixes #18312)
*Sammy Larbi*
diff --git a/activerecord/lib/active_record/validations/length.rb b/activerecord/lib/active_record/validations/length.rb
index ef5a6cbbe7..5991fbad8e 100644
--- a/activerecord/lib/active_record/validations/length.rb
+++ b/activerecord/lib/active_record/validations/length.rb
@@ -2,11 +2,23 @@ module ActiveRecord
module Validations
class LengthValidator < ActiveModel::Validations::LengthValidator # :nodoc:
def validate_each(record, attribute, association_or_value)
+ return unless should_validate?(record) || associations_are_dirty?(record)
if association_or_value.respond_to?(:loaded?) && association_or_value.loaded?
association_or_value = association_or_value.target.reject(&:marked_for_destruction?)
end
super
end
+
+ def associations_are_dirty?(record)
+ attributes.any? do |attribute|
+ value = record.read_attribute_for_validation(attribute)
+ if value.respond_to?(:loaded?) && value.loaded?
+ value.target.any?(&:marked_for_destruction?)
+ else
+ false
+ end
+ end
+ end
end
module ClassMethods
diff --git a/activerecord/lib/active_record/validations/presence.rb b/activerecord/lib/active_record/validations/presence.rb
index 61b30749d9..75d5bd5a35 100644
--- a/activerecord/lib/active_record/validations/presence.rb
+++ b/activerecord/lib/active_record/validations/presence.rb
@@ -2,6 +2,7 @@ module ActiveRecord
module Validations
class PresenceValidator < ActiveModel::Validations::PresenceValidator # :nodoc:
def validate(record)
+ return unless should_validate?(record)
super
attributes.each do |attribute|
next unless record.class._reflect_on_association(attribute)
diff --git a/activerecord/lib/active_record/validations/uniqueness.rb b/activerecord/lib/active_record/validations/uniqueness.rb
index 8f7a46f2c7..ad56f637e3 100644
--- a/activerecord/lib/active_record/validations/uniqueness.rb
+++ b/activerecord/lib/active_record/validations/uniqueness.rb
@@ -11,6 +11,7 @@ module ActiveRecord
end
def validate_each(record, attribute, value)
+ return unless should_validate?(record)
finder_class = find_finder_class_for(record)
table = finder_class.arel_table
value = map_enum_attribute(finder_class, attribute, value)
diff --git a/activerecord/test/cases/validations/association_validation_test.rb b/activerecord/test/cases/validations/association_validation_test.rb
index e4edc437e6..bff5ffa65e 100644
--- a/activerecord/test/cases/validations/association_validation_test.rb
+++ b/activerecord/test/cases/validations/association_validation_test.rb
@@ -50,7 +50,7 @@ class AssociationValidationTest < ActiveRecord::TestCase
Topic.validates_presence_of :content
r = Reply.create("title" => "A reply", "content" => "with content!")
r.topic = Topic.create("title" => "uhohuhoh")
- assert !r.valid?
+ assert_not_operator r, :valid?
assert_equal ["This string contains 'single' and \"double\" quotes"], r.errors[:topic]
end
@@ -82,5 +82,4 @@ class AssociationValidationTest < ActiveRecord::TestCase
assert interest.valid?, "Expected interest to be valid, but was not. Interest should have a man object associated"
end
end
-
end
diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb
index 2c0e282761..8b69a6815e 100644
--- a/activerecord/test/cases/validations/length_validation_test.rb
+++ b/activerecord/test/cases/validations/length_validation_test.rb
@@ -37,7 +37,7 @@ class LengthValidationTest < ActiveRecord::TestCase
def test_validates_size_of_association_utf8
repair_validations Owner do
- assert_nothing_raised { Owner.validates_size_of :pets, :minimum => 1 }
+ Owner.validates_size_of :pets, :minimum => 1
o = Owner.new('name' => 'あいうえおかきくけこ')
assert !o.save
assert o.errors[:pets].any?
@@ -46,8 +46,8 @@ class LengthValidationTest < ActiveRecord::TestCase
end
end
- def test_validates_size_of_reprects_records_marked_for_destruction
- assert_nothing_raised { Owner.validates_size_of :pets, minimum: 1 }
+ def test_validates_size_of_respects_records_marked_for_destruction
+ Owner.validates_size_of :pets, minimum: 1
owner = Owner.new
assert_not owner.save
assert owner.errors[:pets].any?
@@ -62,4 +62,17 @@ class LengthValidationTest < ActiveRecord::TestCase
assert_equal pet_count, Pet.count
end
+ def test_does_not_validate_length_of_if_parent_record_is_validate_false
+ Owner.validates_length_of :name, minimum: 1
+ owner = Owner.new
+ owner.save!(validate: false)
+ assert owner.persisted?
+
+ pet = Pet.new(owner_id: owner.id)
+ pet.save!
+
+ assert_equal owner.pets.size, 1
+ assert owner.valid?
+ assert pet.valid?
+ end
end
diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb
index 4f38849131..b5b16d7a9b 100644
--- a/activerecord/test/cases/validations/presence_validation_test.rb
+++ b/activerecord/test/cases/validations/presence_validation_test.rb
@@ -65,4 +65,20 @@ class PresenceValidationTest < ActiveRecord::TestCase
assert_nothing_raised { s.valid? }
end
+
+ def test_does_not_validate_presence_of_if_parent_record_is_validate_false
+ repair_validations(Interest) do
+ Interest.validates_presence_of(:topic)
+ interest = Interest.new
+ interest.save!(validate: false)
+ assert interest.persisted?
+
+ man = Man.new(interest_ids: [interest.id])
+ man.save!
+
+ assert_equal man.interests.size, 1
+ assert interest.valid?
+ assert man.valid?
+ end
+ end
end
diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb
index 524f59876e..d41b26a2e5 100644
--- a/activerecord/test/cases/validations/uniqueness_validation_test.rb
+++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb
@@ -386,4 +386,21 @@ class UniquenessValidationTest < ActiveRecord::TestCase
topic = TopicWithUniqEvent.new
assert topic.valid?
end
+
+ def test_does_not_validate_uniqueness_of_if_parent_record_is_validate_false
+ Reply.validates_uniqueness_of(:content)
+
+ Reply.create!(content: "Topic Title")
+
+ reply = Reply.new(content: "Topic Title")
+ reply.save!(validate: false)
+ assert reply.persisted?
+
+ topic = Topic.new(reply_ids: [reply.id])
+ topic.save!
+
+ assert_equal topic.replies.size, 1
+ assert reply.valid?
+ assert topic.valid?
+ end
end