From 5e58c70d836a5ecc521daf25c8f4af889048d4ec Mon Sep 17 00:00:00 2001 From: Praveenram Balachandar Date: Mon, 27 Sep 2021 14:23:39 -0400 Subject: [PATCH] Allowing filtering on the primary model when a dependency has changed (#39) --- lib/thermos/beverage.rb | 8 ++- lib/thermos/refill_job.rb | 6 +-- lib/thermos/version.rb | 2 +- test/dummy/app/models/category.rb | 4 ++ test/fixtures/stores.yml | 2 + test/thermos_test.rb | 83 +++++++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 6 deletions(-) diff --git a/lib/thermos/beverage.rb b/lib/thermos/beverage.rb index 6058e0b..fd050f2 100644 --- a/lib/thermos/beverage.rb +++ b/lib/thermos/beverage.rb @@ -19,9 +19,15 @@ def lookup_keys_for_dep_model(dep_model) @deps.select do |dep| dep.klass == dep_model.class end.flat_map do |dep| + lookup_keys = [] + @model.joins(dep.path) .where(dep.table => { id: dep_model.id }) - .pluck(@lookup_key) + .find_each do |model| + lookup_keys << model.send(@lookup_key) if should_fill?(model) + end + + lookup_keys end.uniq end diff --git a/lib/thermos/refill_job.rb b/lib/thermos/refill_job.rb index 8eb515d..2e6268a 100644 --- a/lib/thermos/refill_job.rb +++ b/lib/thermos/refill_job.rb @@ -17,10 +17,8 @@ def refill_primary_caches(model) def refill_dependency_caches(model) BeverageStorage.instance.beverages.each do |beverage| - if beverage.should_fill?(model) - beverage.lookup_keys_for_dep_model(model).each do |lookup_key| - Thermos::RebuildCacheJob.perform_later(beverage.key, lookup_key) - end + beverage.lookup_keys_for_dep_model(model).each do |lookup_key| + Thermos::RebuildCacheJob.perform_later(beverage.key, lookup_key) end end end diff --git a/lib/thermos/version.rb b/lib/thermos/version.rb index dfd8c1d..530a565 100644 --- a/lib/thermos/version.rb +++ b/lib/thermos/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Thermos - VERSION = '0.5.1' + VERSION = '0.5.2' end diff --git a/test/dummy/app/models/category.rb b/test/dummy/app/models/category.rb index ead25f5..b2cf602 100644 --- a/test/dummy/app/models/category.rb +++ b/test/dummy/app/models/category.rb @@ -3,6 +3,10 @@ class Category < ActiveRecord::Base has_many :products, through: :category_items belongs_to :store + def ball? + name.match("ball") + end + def as_json(*args) { name: name, diff --git a/test/fixtures/stores.yml b/test/fixtures/stores.yml index e2d4da3..93feb54 100644 --- a/test/fixtures/stores.yml +++ b/test/fixtures/stores.yml @@ -1,2 +1,4 @@ sports: name: sports store +supermarket: + name: supermarket store diff --git a/test/thermos_test.rb b/test/thermos_test.rb index e8d315c..8e63fc5 100644 --- a/test/thermos_test.rb +++ b/test/thermos_test.rb @@ -161,6 +161,28 @@ def teardown assert_raises(MockExpectationError) { mock.verify } end + test "allows filtering based on the beverage when multiple beverages are configured and only one of them has a filter" do + mock = Minitest::Mock.new + store = stores(:supermarket) + category = categories(:baseball) + # filter method specific to one model + # store.ball? doesn't exist + filter = ->(model) { model.ball? } + + Thermos.fill(key: "key", model: Category, lookup_key: "name", filter: filter) do |name| + mock.call(name) + end + + Thermos.fill(key: "key_2", model: Store, lookup_key: "name") do |name| + mock.call(name) + end + + mock.expect(:call, 1, ["groceries"]) + store.update!(name: "groceries") + assert_equal 1, Thermos.drink(key: "key_2", id: "groceries") + mock.verify + end + # has_many model changes test "rebuilds the cache on has_many model change" do mock = Minitest::Mock.new @@ -223,6 +245,26 @@ def teardown assert_raises(MockExpectationError) { mock.verify } end + test "re-builds the cache for has_many record changes when filter condition is met" do + mock = Minitest::Mock.new + category = categories(:baseball) + filter = ->(model) { model.ball? } + + Thermos.fill(key: "key", model: Category, deps: [:category_items], filter: filter) do |id| + mock.call(id) + end + + mock.expect(:call, 1, [category.id]) + CategoryItem.create!(category: category) + mock.verify + + category.update!(name: "hockey") + + mock.expect(:call, 1, [category.id]) + CategoryItem.create!(category: category) + assert_raises(MockExpectationError) { mock.verify } + end + # belongs_to model changes test "rebuilds the cache on belongs_to model change" do mock = Minitest::Mock.new @@ -286,6 +328,27 @@ def teardown assert_raises(MockExpectationError) { mock.verify } end + test "re-builds the cache for belongs_to record changes when filter condition is met" do + mock = Minitest::Mock.new + category = categories(:baseball) + filter = ->(model) { model.ball? } + + Thermos.fill(key: "key", model: Category, deps: [:store], filter: filter) do |id| + mock.call(id) + end + + mock.expect(:call, 1, [category.id]) + mock.expect(:call, 1, [category.id]) + Store.create!(name: "foo", categories: [category]) + mock.verify + + category.update!(name: "hockey") + + mock.expect(:call, 2, [category.id]) + Store.create!(name: "bar", categories: [category]) + assert_raises(MockExpectationError) { mock.verify } + end + # has_many through model changes test "rebuilds the cache on has_many through model change" do mock = Minitest::Mock.new @@ -348,6 +411,26 @@ def teardown assert_raises(MockExpectationError) { mock.verify } end + test "re-builds the cache for has_many through record changes when filter condition is met" do + mock = Minitest::Mock.new + category = categories(:baseball) + filter = ->(model) { model.ball? } + + Thermos.fill(key: "key", model: Category, deps: [:products], filter: filter) do |id| + mock.call(id) + end + + mock.expect(:call, 1, [category.id]) + Product.create!(categories: [category]) + mock.verify + + category.update!(name: "hockey") + + mock.expect(:call, 2, [category.id]) + Product.create!(categories: [category]) + assert_raises(MockExpectationError) { mock.verify } + end + test "handles indirect associations" do mock = Minitest::Mock.new category = categories(:baseball)