diff --git a/activerecord/lib/active_record/associations/association.rb b/activerecord/lib/active_record/associations/association.rb index cf22b850b94..705a5571eed 100644 --- a/activerecord/lib/active_record/associations/association.rb +++ b/activerecord/lib/active_record/associations/association.rb @@ -56,6 +56,10 @@ module ActiveRecord @inversed = false end + def reset_negative_cache # :nodoc: + reset if loaded? && target.nil? + end + # Reloads the \target and returns +self+ on success. # The QueryCache is cleared if +force+ is true. def reload(force = false) diff --git a/activerecord/lib/active_record/associations/builder/has_one.rb b/activerecord/lib/active_record/associations/builder/has_one.rb index 27ebe8cb71a..db8393930ed 100644 --- a/activerecord/lib/active_record/associations/builder/has_one.rb +++ b/activerecord/lib/active_record/associations/builder/has_one.rb @@ -32,15 +32,12 @@ module ActiveRecord::Associations::Builder # :nodoc: end end - def self.touch_record(o, name, touch) - record = o.send name + def self.touch_record(record, name, touch) + instance = record.send(name) - return unless record && record.persisted? - - if touch != true - record.touch(touch) - else - record.touch + if instance&.persisted? + touch != true ? + instance.touch(touch) : instance.touch end end @@ -48,11 +45,9 @@ module ActiveRecord::Associations::Builder # :nodoc: name = reflection.name touch = reflection.options[:touch] - callback = lambda { |record| - HasOne.touch_record(record, name, touch) - } - + callback = -> (record) { HasOne.touch_record(record, name, touch) } model.after_create callback, if: :saved_changes? + model.after_create_commit { association(name).reset_negative_cache } model.after_update callback, if: :saved_changes? model.after_destroy callback model.after_touch callback diff --git a/activerecord/test/cases/associations/has_one_associations_test.rb b/activerecord/test/cases/associations/has_one_associations_test.rb index 3ef25c70274..b1fcf475282 100644 --- a/activerecord/test/cases/associations/has_one_associations_test.rb +++ b/activerecord/test/cases/associations/has_one_associations_test.rb @@ -712,6 +712,24 @@ class HasOneAssociationsTest < ActiveRecord::TestCase } end + def test_polymorphic_has_one_with_touch_option_on_create_wont_cache_association_so_fetching_after_transaction_commit_works + assert_queries(4) { + chef = Chef.create(employable: DrinkDesignerWithPolymorphicTouchChef.new) + employable = chef.employable + + assert_equal chef, employable.chef + } + end + + def test_polymorphic_has_one_with_touch_option_on_update_will_touch_record_by_fetching_from_database_if_needed + DrinkDesignerWithPolymorphicTouchChef.create(chef: Chef.new) + designer = DrinkDesignerWithPolymorphicTouchChef.last + + assert_queries(3) { + designer.update(name: "foo") + } + end + def test_has_one_with_touch_option_on_update new_club = Club.create(name: "1000 Oaks") new_club.create_membership diff --git a/activerecord/test/models/drink_designer.rb b/activerecord/test/models/drink_designer.rb index 8258408f35e..fe7f5c9e03e 100644 --- a/activerecord/test/models/drink_designer.rb +++ b/activerecord/test/models/drink_designer.rb @@ -10,5 +10,11 @@ class DrinkDesignerWithPolymorphicDependentNullifyChef < ActiveRecord::Base has_one :chef, as: :employable, dependent: :nullify end +class DrinkDesignerWithPolymorphicTouchChef < ActiveRecord::Base + self.table_name = "drink_designers" + + has_one :chef, as: :employable, touch: true +end + class MocktailDesigner < DrinkDesigner end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index dd0ff759b67..cae2890c9e1 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -1070,6 +1070,7 @@ ActiveRecord::Schema.define do create_table :cake_designers, force: true do |t| end create_table :drink_designers, force: true do |t| + t.string :name end create_table :chefs, force: true do |t| t.integer :employable_id @@ -1077,6 +1078,7 @@ ActiveRecord::Schema.define do t.integer :department_id t.string :employable_list_type t.integer :employable_list_id + t.timestamps end create_table :recipes, force: true do |t| t.integer :chef_id