cache_keeper 0.4.1 → 0.5.0
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 +4 -4
- data/README.md +34 -10
- data/app/jobs/cache_keeper/autorefresh_job.rb +9 -0
- data/app/models/cache_keeper/cached_method/refreshable.rb +6 -0
- data/app/models/cache_keeper/cached_method.rb +7 -2
- data/lib/cache_keeper/caching.rb +2 -2
- data/lib/cache_keeper/manager.rb +2 -2
- data/lib/cache_keeper/store.rb +1 -1
- data/lib/cache_keeper/version.rb +1 -1
- data/test/models/cached_method/refreshable_test.rb +23 -0
- data/test/models/cached_method_test.rb +43 -8
- data/test/store_test.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: edca690bc194349e3a062521687f2e91fc91415f66e7f0e976293a0e7470f8a8
|
4
|
+
data.tar.gz: 78e3725f253be829e474602792fb3316889ffd8d6485bff0b1a6baae2525d0d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03a22be5703c831beeb55f3a30e3f2a7534afdccf581cbb16e0f556eedc9b518c279267a4e2db06d8dfb9bcfe255f70f68313e9118b53f5522059168a75ecd70
|
7
|
+
data.tar.gz: 5cc2b635e0766a2b4dc269cf59aef03c27f19fb1ba516771885df0157835057f1479816f0110d1fbfffb3794d9510e567386e7320864c48505cff3af40058cc2
|
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
|
33
|
-
caches :
|
34
|
-
caches :
|
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
|
36
|
+
def amplify
|
37
37
|
...
|
38
38
|
end
|
39
39
|
|
40
|
-
def
|
40
|
+
def enhance_hilarity
|
41
41
|
...
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
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 :
|
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 :
|
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
|
|
@@ -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
|
|
data/lib/cache_keeper/caching.rb
CHANGED
@@ -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
|
data/lib/cache_keeper/manager.rb
CHANGED
@@ -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
|
data/lib/cache_keeper/store.rb
CHANGED
data/lib/cache_keeper/version.rb
CHANGED
@@ -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
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
10
|
+
result = cached_method.call(recording)
|
10
11
|
|
11
|
-
|
12
|
-
|
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", "
|
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", "
|
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, {
|
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
|
+
version: 0.5.0
|
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
|