summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorReuben Pereira <rpereira@gitlab.com>2019-04-04 17:27:29 +0000
committerMichael Kozono <mkozono@gmail.com>2019-04-04 17:27:29 +0000
commit5caced3650bf51bd1035347b9823367dd9095e02 (patch)
treee0a1467406fdc7a530d66a0e4fdc07546466238c
parenta2d044bf97ec350019b2daebd962ab4901070818 (diff)
downloadgitlab-ce-5caced3650bf51bd1035347b9823367dd9095e02.tar.gz
Allow reactive caching to be used in services
Add support for defining a reactive_cache_worker_finder function that will be used by the reactive_caching_worker to generate/initialize the calling object. This allows reactive caching to work with Services where the object cannot be obtained from DB but a new object can be initialized.
-rw-r--r--app/models/concerns/reactive_caching.rb39
-rw-r--r--app/workers/reactive_caching_worker.rb7
-rw-r--r--changelogs/unreleased/58375-reactive-caching-changes.yml5
-rw-r--r--spec/models/concerns/reactive_caching_spec.rb44
4 files changed, 92 insertions, 3 deletions
diff --git a/app/models/concerns/reactive_caching.rb b/app/models/concerns/reactive_caching.rb
index 1ab3b3ddc46..1e09cd89550 100644
--- a/app/models/concerns/reactive_caching.rb
+++ b/app/models/concerns/reactive_caching.rb
@@ -29,6 +29,40 @@
# However, it will enqueue a background worker to call `#calculate_reactive_cache`
# and set an initial cache lifetime of ten minutes.
#
+# The background worker needs to find or generate the object on which
+# `with_reactive_cache` was called.
+# The default behaviour can be overridden by defining a custom
+# `reactive_cache_worker_finder`.
+# Otherwise the background worker will use the class name and primary key to get
+# the object using the ActiveRecord find_by method.
+#
+# class Bar
+# include ReactiveCaching
+#
+# self.reactive_cache_key = ->() { ["bar", "thing"] }
+# self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+#
+# def self.from_cache(var1, var2)
+# # This method will be called by the background worker with "bar1" and
+# # "bar2" as arguments.
+# new(var1, var2)
+# end
+#
+# def initialize(var1, var2)
+# # ...
+# end
+#
+# def calculate_reactive_cache
+# # Expensive operation here. The return value of this method is cached
+# end
+#
+# def result
+# with_reactive_cache("bar1", "bar2") do |data|
+# # ...
+# end
+# end
+# end
+#
# Each time the background job completes, it stores the return value of
# `#calculate_reactive_cache`. It is also re-enqueued to run again after
# `reactive_cache_refresh_interval`, so keeping the stored value up to date.
@@ -52,6 +86,7 @@ module ReactiveCaching
class_attribute :reactive_cache_key
class_attribute :reactive_cache_lifetime
class_attribute :reactive_cache_refresh_interval
+ class_attribute :reactive_cache_worker_finder
# defaults
self.reactive_cache_lease_timeout = 2.minutes
@@ -59,6 +94,10 @@ module ReactiveCaching
self.reactive_cache_refresh_interval = 1.minute
self.reactive_cache_lifetime = 10.minutes
+ self.reactive_cache_worker_finder = ->(id, *_args) do
+ find_by(primary_key => id)
+ end
+
def calculate_reactive_cache(*args)
raise NotImplementedError
end
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index 9ec8bcca4f3..b30864db802 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -3,7 +3,6 @@
class ReactiveCachingWorker
include ApplicationWorker
- # rubocop: disable CodeReuse/ActiveRecord
def perform(class_name, id, *args)
klass = begin
class_name.constantize
@@ -12,7 +11,9 @@ class ReactiveCachingWorker
end
return unless klass
- klass.find_by(klass.primary_key => id).try(:exclusively_update_reactive_cache!, *args)
+ klass
+ .reactive_cache_worker_finder
+ .call(id, *args)
+ .try(:exclusively_update_reactive_cache!, *args)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/changelogs/unreleased/58375-reactive-caching-changes.yml b/changelogs/unreleased/58375-reactive-caching-changes.yml
new file mode 100644
index 00000000000..cf73736b8ef
--- /dev/null
+++ b/changelogs/unreleased/58375-reactive-caching-changes.yml
@@ -0,0 +1,5 @@
+---
+title: Allow reactive caching to be used in services
+merge_request: 26839
+author:
+type: added
diff --git a/spec/models/concerns/reactive_caching_spec.rb b/spec/models/concerns/reactive_caching_spec.rb
index 32e13d5abed..53df9e0bc05 100644
--- a/spec/models/concerns/reactive_caching_spec.rb
+++ b/spec/models/concerns/reactive_caching_spec.rb
@@ -16,6 +16,10 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
attr_reader :id
+ def self.primary_key
+ :id
+ end
+
def initialize(id, &blk)
@id = id
@calculator = blk
@@ -106,6 +110,46 @@ describe ReactiveCaching, :use_clean_rails_memory_store_caching do
end
end
+ describe '.reactive_cache_worker_finder' do
+ context 'with default reactive_cache_worker_finder' do
+ let(:args) { %w(other args) }
+
+ before do
+ allow(instance.class).to receive(:find_by).with(id: instance.id)
+ .and_return(instance)
+ end
+
+ it 'calls the activerecord find_by method' do
+ result = instance.class.reactive_cache_worker_finder.call(instance.id, *args)
+
+ expect(result).to eq(instance)
+ expect(instance.class).to have_received(:find_by).with(id: instance.id)
+ end
+ end
+
+ context 'with custom reactive_cache_worker_finder' do
+ let(:args) { %w(arg1 arg2) }
+ let(:instance) { CustomFinderCacheTest.new(666, &calculation) }
+
+ class CustomFinderCacheTest < CacheTest
+ self.reactive_cache_worker_finder = ->(_id, *args) { from_cache(*args) }
+
+ def self.from_cache(*args); end
+ end
+
+ before do
+ allow(instance.class).to receive(:from_cache).with(*args).and_return(instance)
+ end
+
+ it 'overrides the default reactive_cache_worker_finder' do
+ result = instance.class.reactive_cache_worker_finder.call(instance.id, *args)
+
+ expect(result).to eq(instance)
+ expect(instance.class).to have_received(:from_cache).with(*args)
+ end
+ end
+ end
+
describe '#clear_reactive_cache!' do
before do
stub_reactive_cache(instance, 4)