cache_keeper 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc9efe0608912e0c97f6f89659ad73e64588e33eb384701afe0c52afb981c568
4
- data.tar.gz: 22b645e8a15de837f4d72de70bc64a1d966c9ce96f9ada07a6fb2f7ba6a0d32f
3
+ metadata.gz: 79c0d48d919012cb372475c6b3d9a8e43d282b1841dd2149a3bbc541b1023bfd
4
+ data.tar.gz: 459d8fedbf19345e819aeb86e79f021d06eab4ecdaa31528c4a71c19a29fb865
5
5
  SHA512:
6
- metadata.gz: e60f463b994d17a08f047cd00cd7ef7114940cf5458a6c42d25efacf1968d93a01b2b9cbd6c1bad23252a9b357f88cae5909b291b24756a566ebe95efb0cf578
7
- data.tar.gz: 25e87146ea75af98f0112aa8ba6acb730d6314962357589e4b8c0a540a728e9e59c2ebecb4764612f1eba375a6ad45069686ad5e3934bec05cbe31781522eee6
6
+ metadata.gz: ea481143e9734f290cc27fb888783216dc1f639f9fcc78963723ab4c1e82396b899b899b7518511f2a242b93695b267548393cb14fced6dfe965812913128c40
7
+ data.tar.gz: fea933c28a8b492bcb20de5cbdf17841323b20a6d60fd22c3d0c82e746a9f9171149bec9a089297cbb59e2e3c46a451bf73f2ff251c706dad4eee5b59eb931c0
data/README.md CHANGED
@@ -29,19 +29,19 @@ bundle add cache_keeper
29
29
  CacheKeeper provides a `caches` method that will cache the result of the methods you give it:
30
30
 
31
31
  ```ruby
32
- class Recording < ApplicationRecord
33
- caches :slow_method, :really_slow_method, expires_in: 1.hour
34
- caches :incredibly_slow_method, expires_in: 2.hours, must_revalidate: true
32
+ class AlienAnecdoteAmplifier < ApplicationRecord
33
+ caches :amplify, :enhance_hilarity, expires_in: 1.hour
34
+ caches :generate_anecdotal_tales, expires_in: 2.hours, must_revalidate: true
35
35
 
36
- def slow_method
36
+ def amplify
37
37
  ...
38
38
  end
39
39
 
40
- def really_slow_method
40
+ def enhance_hilarity
41
41
  ...
42
42
  end
43
43
 
44
- def incredibly_slow_method
44
+ def generate_anecdotal_tales
45
45
  ...
46
46
  end
47
47
  end
@@ -62,8 +62,6 @@ class NebulaNoodleTwister
62
62
  caches :twist_noodles, :dish_of_the_day, key: ->(method_name) { [:recoding, id, method_name] }
63
63
  caches :synchronize_taste_buds, key: -> { [:recoding, id, :synchronize_taste_buds] }
64
64
  caches :space_soup_simulation, key: :space_soup_simulation
65
-
66
- ...
67
65
  end
68
66
  ```
69
67
 
@@ -75,16 +73,42 @@ CacheKeeper needs to pass the instance on which the cached method is called alon
75
73
  class QuantumQuackerator
76
74
  # Generate a new instance using an empty initializer (QuantumQuackerator.new)
77
75
  # Useful for controllers and for POROs with no arguments
78
- caches :slow_method, serializer: :new_instance
76
+ caches :quackify_particles, serializer: :new_instance
79
77
 
80
78
  # Replicate the old instance using Marshal.dump and Marshal.load
81
79
  # Useful in most other cases, but make sure the dump is not too big
82
- caches :slow_method, serializer: :marshal
80
+ caches :quackify_particles, serializer: :marshal
83
81
  end
84
82
  ```
85
83
 
86
84
  If those options don't work for you, you can always [write custom serializers](https://guides.rubyonrails.org/active_job_basics.html#serializers) for your classes.
87
85
 
86
+ ### Autorefresh
87
+
88
+ CacheKeeper can automatically refresh your cached methods so that they are always warm. You need to pass a block to the `caches` method that will be called periodically. It will receive a `cached_method` object that you can use to `autorefresh` the cache for a specific instance:
89
+
90
+ ```ruby
91
+ class LaughInducingLuminator < ApplicationRecord
92
+ caches :generate_chuckles, expires_in: 1.day do |cached_method|
93
+ find_each do { |luminator| cached_method.autorefresh luminator }
94
+ end
95
+ end
96
+ ```
97
+
98
+ The last step is to register the `CacheKeeper::AutorefreshJob` in whatever system you use to run jobs periodically. For example, if you use [GoodJob](https://github.com/bensheldon/good_job) you would do something like this:
99
+
100
+ ```ruby
101
+ Rails.application.configure do
102
+ config.good_job.enable_cron = true
103
+ config.good_job.cron = {
104
+ cache_keeper: {
105
+ cron: "*/15 * * * *", # every 15 minutes, every day
106
+ class: "CacheKeeper::AutorefreshJob"
107
+ }
108
+ }
109
+ end
110
+ ```
111
+
88
112
 
89
113
  ## Configuration
90
114
 
@@ -0,0 +1,9 @@
1
+ class CacheKeeper::AutorefreshJob < CacheKeeper::BaseJob
2
+ queue_as { CacheKeeper.configuration.queues[:refresh] }
3
+
4
+ def perform
5
+ CacheKeeper.manager.cached_methods.autorefreshed.each do |cached_method|
6
+ cached_method.autorefresh_block.call cached_method
7
+ end
8
+ end
9
+ end
@@ -8,4 +8,10 @@ module CacheKeeper::CachedMethod::Refreshable
8
8
  def refresh_later(target)
9
9
  CacheKeeper::RefreshJob.perform_later self, target
10
10
  end
11
+
12
+ def autorefresh(target)
13
+ return unless stale?(target)
14
+
15
+ refresh_later target
16
+ end
11
17
  end
@@ -2,18 +2,23 @@ class CacheKeeper::CachedMethod
2
2
  include Refreshable
3
3
  include SerializableTarget
4
4
 
5
- attr_accessor :klass, :method_name, :options
5
+ attr_accessor :klass, :method_name, :options, :autorefresh_block
6
6
 
7
- def initialize(klass, method_name, options = {})
7
+ def initialize(klass, method_name, options = {}, &block)
8
8
  self.klass = klass
9
9
  self.method_name = method_name
10
10
  self.options = options.with_indifferent_access
11
+ self.autorefresh_block = block
11
12
  end
12
13
 
13
14
  def alias_for_original_method
14
15
  :"__#{method_name}__hooked__"
15
16
  end
16
17
 
18
+ def stale?(target)
19
+ cache_entry(target).blank? || cache_entry(target).expired?
20
+ end
21
+
17
22
  def call(target)
18
23
  cache_entry = cache_entry(target)
19
24
 
@@ -3,9 +3,9 @@ module CacheKeeper
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- def self.caches(*method_names, **options)
6
+ def self.caches(*method_names, **options, &block)
7
7
  method_names.each do |method_name|
8
- CacheKeeper.manager.handle self, method_name, options
8
+ CacheKeeper.manager.handle self, method_name, options, &block
9
9
 
10
10
  # If the method is already defined, we need to hook it
11
11
  method_added method_name
@@ -10,8 +10,8 @@ module CacheKeeper
10
10
  cached_methods.find_by(klass, method_name).present?
11
11
  end
12
12
 
13
- def handle(klass, method_name, options)
14
- CacheKeeper::CachedMethod.new(klass, method_name, options).tap do |cached_method|
13
+ def handle(klass, method_name, options, &block)
14
+ CacheKeeper::CachedMethod.new(klass, method_name, options, &block).tap do |cached_method|
15
15
  if unsupported_options?(cached_method)
16
16
  raise "You're trying to autorefresh an ActiveRecord model, which we don't currently support."
17
17
  end
@@ -8,7 +8,7 @@ module CacheKeeper
8
8
 
9
9
  def autorefreshed
10
10
  select do |cached_method|
11
- cached_method.options[:autorefresh].present?
11
+ cached_method.autorefresh_block.present?
12
12
  end
13
13
  end
14
14
  end
@@ -1,3 +1,3 @@
1
1
  module CacheKeeper
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -11,4 +11,27 @@ class CacheKeeper::CachedMethod::RefreshableTest < ActiveSupport::TestCase
11
11
  cached_method.refresh_later recording
12
12
  end
13
13
  end
14
+
15
+ test "#autorefresh enqueues a refresh job if it's stale" do
16
+ with_clean_caching do
17
+ recording = Recording.create(number: 5)
18
+ cached_method = CacheKeeper.manager.cached_methods.first
19
+
20
+ assert_enqueued_with(job: CacheKeeper::RefreshJob) do
21
+ cached_method.autorefresh recording
22
+ end
23
+ end
24
+ end
25
+
26
+ test "#autorefresh doesn't enqueue a refresh job if it's fresh" do
27
+ with_clean_caching do
28
+ recording = Recording.create(number: 5)
29
+ cached_method = CacheKeeper.manager.cached_methods.first
30
+ cached_method.call(recording)
31
+
32
+ assert_no_enqueued_jobs do
33
+ cached_method.autorefresh recording
34
+ end
35
+ end
36
+ end
14
37
  end
@@ -2,21 +2,56 @@ require "test_helper"
2
2
 
3
3
  class CacheKeeper::CachedMethodTest < ActiveSupport::TestCase
4
4
  test "#call caches the result of the original method" do
5
- recording = Recording.create(number: 5)
6
- cached_method = manager.handle(Recording, :another_method, expires_in: 1.hour)
7
- manager.activate_if_handling(Recording, :another_method)
5
+ with_clean_caching do
6
+ recording = Recording.create(number: 5)
7
+ cached_method = manager.handle(Recording, :another_method, expires_in: 1.hour)
8
+ manager.activate_if_handling(Recording, :another_method)
8
9
 
9
- result = cached_method.call(recording)
10
+ result = cached_method.call(recording)
10
11
 
11
- assert_equal 5, result
12
- assert cache_has_key? "CacheKeeper/recordings/#{recording.id}/another_method"
12
+ assert_equal 5, result
13
+ assert cache_has_key? "CacheKeeper/recordings/#{recording.id}/another_method"
14
+ end
15
+ end
16
+
17
+ test "#stale? is true for cold caches" do
18
+ with_clean_caching do
19
+ recording = Recording.create(number: 5)
20
+ cached_method = manager.handle(Recording, :another_method, expires_in: 1.hour)
21
+
22
+ assert cached_method.stale?(recording)
23
+ end
24
+ end
25
+
26
+ test "#stale? is true for expired caches" do
27
+ with_clean_caching do
28
+ recording = Recording.create(number: 5)
29
+ cached_method = manager.handle(Recording, :another_method, expires_in: 0.01.seconds)
30
+ manager.activate_if_handling(Recording, :another_method)
31
+ cached_method.call(recording)
32
+
33
+ sleep 0.01
34
+
35
+ assert cached_method.stale?(recording)
36
+ end
37
+ end
38
+
39
+ test "#stale? is false for fresh caches" do
40
+ with_clean_caching do
41
+ recording = Recording.create(number: 5)
42
+ cached_method = manager.handle(Recording, :another_method, expires_in: 1.hour)
43
+ manager.activate_if_handling(Recording, :another_method)
44
+ cached_method.call(recording)
45
+
46
+ assert_not cached_method.stale?(recording)
47
+ end
13
48
  end
14
49
 
15
50
  test ":key option accepts arrays" do
16
- cached_method = manager.handle(Recording, :another_method, key: ["QuantumQuackerator", "dimensional_duckling"])
51
+ cached_method = manager.handle(Recording, :another_method, key: ["QuantumQuackerator", "quackify_particles"])
17
52
  cache_key = cached_method.send :cache_key, Recording.new
18
53
 
19
- assert_equal ["QuantumQuackerator", "dimensional_duckling"], cache_key
54
+ assert_equal ["QuantumQuackerator", "quackify_particles"], cache_key
20
55
  end
21
56
 
22
57
  test ":key option accepts procs with no arguments" do
data/test/store_test.rb CHANGED
@@ -11,7 +11,7 @@ class CacheKeeper::StoreTest < ActiveSupport::TestCase
11
11
 
12
12
  test "#autorefreshed returns only the ones with the correct option" do
13
13
  cached_method = CacheKeeper::CachedMethod.new(String, :slow_method, {})
14
- autorefreshed_cached_method = CacheKeeper::CachedMethod.new(String, :really_slow_method, { autorefresh: true })
14
+ autorefreshed_cached_method = CacheKeeper::CachedMethod.new(String, :really_slow_method, {}, &proc {})
15
15
  store = CacheKeeper::Store.new([cached_method, autorefreshed_cached_method])
16
16
 
17
17
  assert_equal [autorefreshed_cached_method], store.autorefreshed
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache_keeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Zamuner
@@ -36,6 +36,7 @@ files:
36
36
  - Gemfile
37
37
  - MIT-LICENSE
38
38
  - README.md
39
+ - app/jobs/cache_keeper/autorefresh_job.rb
39
40
  - app/jobs/cache_keeper/base_job.rb
40
41
  - app/jobs/cache_keeper/refresh_job.rb
41
42
  - app/models/cache_keeper/cached_method.rb