From 28ac7a128115c44b764d61de3d6b04dc00e2d6ad Mon Sep 17 00:00:00 2001 From: Jimmy Bourassa Date: Mon, 6 Jan 2020 10:08:45 -0500 Subject: [PATCH] Add test that reproduces lazy cache invalidation bug --- test/helpers/active_record_objects.rb | 2 ++ test/helpers/database_connection.rb | 5 ++- test/helpers/lazy_model/a.rb | 8 +++++ test/helpers/lazy_model/b.rb | 9 ++++++ test/helpers/lazy_model/c.rb | 7 +++++ test/helpers/models.rb | 6 ++++ test/lazy_hook_test.rb | 44 +++++++++++++++++++++++++++ 7 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 test/helpers/lazy_model/a.rb create mode 100644 test/helpers/lazy_model/b.rb create mode 100644 test/helpers/lazy_model/c.rb create mode 100644 test/lazy_hook_test.rb diff --git a/test/helpers/active_record_objects.rb b/test/helpers/active_record_objects.rb index cfed8994..e6e60cb6 100644 --- a/test/helpers/active_record_objects.rb +++ b/test/helpers/active_record_objects.rb @@ -45,6 +45,8 @@ def teardown_models Object.send(:remove_const, 'Deeply') Object.send(:remove_const, 'CustomMasterRecord') Object.send(:remove_const, 'CustomChildRecord') + Object.send(:remove_const, 'LazyModel') + $LOADED_FEATURES.reject! { |feature| feature.include?("test/helpers/lazy_model/") } IdentityCache.const_get(:ParentModelExpiration).send(:lazy_hooks).clear end end diff --git a/test/helpers/database_connection.rb b/test/helpers/database_connection.rb index 7c7539bb..713e4280 100644 --- a/test/helpers/database_connection.rb +++ b/test/helpers/database_connection.rb @@ -59,7 +59,10 @@ def self.create_tables custom_master_records: [[:integer, :master_primary_key], id: false, primary_key: 'master_primary_key'], custom_child_records: [ [:integer, :child_primary_key], [:integer, :master_id], id: false, primary_key: 'child_primary_key' - ] + ], + lazy_as: [[:string, :name]], + lazy_bs: [[:string, :name], [:integer, :a_id]], + lazy_cs: [[:string, :name], [:integer, :b_id]], } DEFAULT_CONFIG = { diff --git a/test/helpers/lazy_model/a.rb b/test/helpers/lazy_model/a.rb new file mode 100644 index 00000000..68d83d62 --- /dev/null +++ b/test/helpers/lazy_model/a.rb @@ -0,0 +1,8 @@ +module LazyModel + class A < ActiveRecord::Base + self.table_name = "lazy_as" + include IdentityCache + has_many :bs, class_name: "::LazyModel::B" + cache_has_many :bs, embed: true + end +end diff --git a/test/helpers/lazy_model/b.rb b/test/helpers/lazy_model/b.rb new file mode 100644 index 00000000..345f35c6 --- /dev/null +++ b/test/helpers/lazy_model/b.rb @@ -0,0 +1,9 @@ +module LazyModel + class B < ActiveRecord::Base + self.table_name = "lazy_bs" + include IdentityCache + belongs_to :a, class_name: "::LazyModel::A", inverse_of: :bs + has_one :c, class_name: "::LazyModel::C", inverse_of: :b + cache_has_one :c + end +end diff --git a/test/helpers/lazy_model/c.rb b/test/helpers/lazy_model/c.rb new file mode 100644 index 00000000..a2ce4633 --- /dev/null +++ b/test/helpers/lazy_model/c.rb @@ -0,0 +1,7 @@ +module LazyModel + class C < ActiveRecord::Base + self.table_name = "lazy_cs" + include IdentityCache + belongs_to :b, class_name: "::LazyModel::B", inverse_of: :c + end +end diff --git a/test/helpers/models.rb b/test/helpers/models.rb index d09e14ec..8852daac 100644 --- a/test/helpers/models.rb +++ b/test/helpers/models.rb @@ -88,3 +88,9 @@ class CustomChildRecord < ActiveRecord::Base belongs_to :custom_master_record, foreign_key: :master_id self.primary_key = 'child_primary_key' end + +module LazyModel + autoload :A, "helpers/lazy_model/a.rb" + autoload :B, "helpers/lazy_model/b.rb" + autoload :C, "helpers/lazy_model/c.rb" +end diff --git a/test/lazy_hook_test.rb b/test/lazy_hook_test.rb new file mode 100644 index 00000000..17c3f92d --- /dev/null +++ b/test/lazy_hook_test.rb @@ -0,0 +1,44 @@ +require "test_helper" + +class LazyHookTest < IdentityCache::TestCase + def test_expires_unloaded_lazy_parent_models + # Populate cache + a = LazyModel::A.create!(name: "Initial A") + b = a.bs.create!(name: "Initial B") + c = b.create_c!(name: "Initial C") + + ids = { + a: a.id, + b: b.id, + c: c.id, + } + + # Warm cache + LazyModel::A.fetch(ids[:a]).fetch_bs.first.fetch_c + + # Clear lazyloaded code to simulate code that isn't eager loaded + reset_loaded_code + LazyModel::C.find(ids[:c]).update!(name: "Updated C") + + refute(lazy_model_loaded?(:A)) + refute(lazy_model_loaded?(:B)) + + queries = count_queries do + c = LazyModel::A.fetch(ids[:a]).fetch_bs.first.fetch_c + assert_equal("Updated C", c.name) + end + + assert_equal(0, queries) + end + + private + + def reset_loaded_code + teardown_models + setup_models + end + + def lazy_model_loaded?(name) + !LazyModel.autoload?(name) + end +end