cache_keeper 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c67412b5f220a3adf468e0dbb26e7ebe170e2585806e5b1573f8ebba0c9e464f
4
- data.tar.gz: 86ae3889c37ab555a6c7fd517cc6782de5cc3d3b11c793491f81a97f23a2b3ac
3
+ metadata.gz: d0f5c8db74c46228eecf241e59f4862d187d42a44dfac6a8f65615b72cd82758
4
+ data.tar.gz: 52bdb5061f2f12149db22cbdb850336a4ed95f76929312402145c7c861d44314
5
5
  SHA512:
6
- metadata.gz: d500ec9f67aaa5d61d3ffed9246ca9ac30fe08a76130b9687ae015859f22af5ac27881c95794310428f315e23d870f59e42d4fbd433f3c66ae2d469dd0480577
7
- data.tar.gz: 1468c273f8f91f0761d9bc0b0ca31641732a3d424da4a85c247e776ff8eb68d5a9c162a02be3cff51f832cf55ac3d35b6ecf5193220c9f1174faff26e62affab
6
+ metadata.gz: d3957283c2f968d645ec2c6939c04b62ef5d877d51c0e52ef86d72c63d2033e30fbbf0999e3a870fa527c7b26e6b14fc29ce86734942ce77a9b9b0bdd0d99b66
7
+ data.tar.gz: 0201bd970c5b569cdf5849f7dfc4675a734b9090000ad36b9173b5cd30809abd8acf72098b1760d6f51d3a7e6d59066fc7e22edaa8d8eeab2b3ea1b0709d7120
data/Gemfile CHANGED
@@ -3,6 +3,7 @@ source "https://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem "rake"
6
+ gem "byebug"
6
7
 
7
8
  gem "rails", ">= 6.1"
8
9
  gem "sqlite3"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cache_keeper (0.1.1)
4
+ cache_keeper (0.2.0)
5
5
  rails (>= 6.1.0)
6
6
 
7
7
  GEM
@@ -83,6 +83,7 @@ GEM
83
83
  base64 (0.1.1)
84
84
  bigdecimal (3.1.4)
85
85
  builder (3.2.4)
86
+ byebug (11.1.3)
86
87
  concurrent-ruby (1.2.2)
87
88
  connection_pool (2.4.1)
88
89
  crass (1.0.6)
@@ -188,6 +189,7 @@ PLATFORMS
188
189
  x86_64-linux
189
190
 
190
191
  DEPENDENCIES
192
+ byebug
191
193
  cache_keeper!
192
194
  rails (>= 6.1)
193
195
  rake
data/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  <br>
4
4
  </h1>
5
5
 
6
- <h3 align="center">Keep cached methods always fresh in your Rails application.</h3>
6
+ <h3 align="center">Have your cached methods refreshed asynchronously and automatically.</h3>
7
7
 
8
8
  <p align="center">
9
9
  <img alt="Build" src="https://img.shields.io/github/actions/workflow/status/martinzamuner/cache_keeper/ci.yml?branch=main">
@@ -12,7 +12,7 @@
12
12
  <img alt="License" src="https://img.shields.io/github/license/martinzamuner/cache_keeper">
13
13
  </p>
14
14
 
15
- CacheKeeper allows you to mark any method to be kept fresh in your Rails cache. It uses ActiveJob to refresh the cache in the background.
15
+ CacheKeeper is a Rails gem that allows you to mark any method to be kept fresh in your cache. It uses ActiveJob to refresh the cache in the background, either on demand or periodically.
16
16
 
17
17
 
18
18
  ## Installation
@@ -33,10 +33,22 @@ caches :slow_method, :really_slow_method, expires_in: 1.hour
33
33
  caches :incredibly_slow_method, expires_in: 2.hours, must_revalidate: true
34
34
  ```
35
35
 
36
- It is automatically available in your ActiveRecord models and in your controllers. You can also use it in any other class by including `CacheKeeper::Caching`.
36
+ It's automatically available in your ActiveRecord models and in your controllers. You can also use it in any other class by including `CacheKeeper::Caching`.
37
37
 
38
38
  By default, it will immediately run the method call if it hasn't been cached before. The next time it is called, it will return the cached value if it hasn't expired yet. If it has expired, it will enqueue a job to refresh the cache in the background and return the stale value in the meantime. You can avoid returning stale values by setting `must_revalidate: true` in the options.
39
39
 
40
+ It's important to note that it will only work with methods that don't take any arguments.
41
+
42
+ ### Autorefresh
43
+
44
+ You can tell CacheKeeper to automatically refresh the cache after a certain amount of time by setting the `autorefresh` option:
45
+
46
+ ```ruby
47
+ caches :i_cant_even, expires_in: 2.hours, autorefresh: true
48
+ ```
49
+
50
+ This works by running a job in cron mode that will periodically check for stale entries and enqueue a job to refresh them. You need to specify an adapter as explained in the configuration section below.
51
+
40
52
 
41
53
  ## Configuration
42
54
 
@@ -51,6 +63,15 @@ Rails.application.configure do
51
63
  # The queue to use for the refresh jobs.
52
64
  # Default: nil (uses the default queue)
53
65
  config.cache_keeper.queues.refresh = :low_priority
66
+
67
+ # The adapter to use for the autorefresh cron job.
68
+ # Options: :good_job
69
+ # Default: nil
70
+ config.cache_keeper.cron.adapter = :good_job
71
+
72
+ # The cron expression to use for the autorefresh cron job.
73
+ # Default: "*/15 * * * *" (every 15 minutes)
74
+ config.cache_keeper.cron.expression = "0 * * * *"
54
75
  end
55
76
  ```
56
77
 
@@ -0,0 +1,11 @@
1
+ class CacheKeeper::RefreshAllJob < CacheKeeper::BaseJob
2
+ queue_as { CacheKeeper.configuration.queues[:refresh] }
3
+
4
+ def perform
5
+ CacheKeeper.manager.cached_methods.autorefreshed.each do |cached_method|
6
+ next unless cached_method.stale?
7
+
8
+ cached_method.refresh_later
9
+ end
10
+ end
11
+ end
@@ -5,7 +5,9 @@ module CacheKeeper::CachedMethod::Refreshable
5
5
  end
6
6
  end
7
7
 
8
- def refresh_later(instance)
8
+ def refresh_later(instance = nil)
9
+ instance ||= klass.new
10
+
9
11
  CacheKeeper::RefreshJob.perform_later self, instance
10
12
  end
11
13
  end
@@ -13,6 +13,10 @@ class CacheKeeper::CachedMethod
13
13
  :"__#{method_name}__hooked__"
14
14
  end
15
15
 
16
+ def stale?
17
+ cache_entry.blank? || cache_entry.expired?
18
+ end
19
+
16
20
  def call(instance)
17
21
  if cache_entry.blank?
18
22
  refresh instance
@@ -32,7 +36,7 @@ class CacheKeeper::CachedMethod
32
36
  private
33
37
 
34
38
  def cache_entry
35
- Rails.cache.send :read_entry, Rails.cache.send(:normalize_key, cache_key)
39
+ Rails.cache.send :read_entry, Rails.cache.send(:normalize_key, cache_key, {})
36
40
  end
37
41
 
38
42
  def cache_key
data/cache_keeper.gemspec CHANGED
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
5
5
  s.version = CacheKeeper::VERSION
6
6
  s.authors = ["Martin Zamuner"]
7
7
  s.email = "martinzamuner@gmail.com"
8
- s.summary = "Keep a fresh copy of any method in your cache"
8
+ s.summary = "Have your cached methods refreshed asynchronously and automatically"
9
9
  s.homepage = "https://github.com/martinzamuner/cache_keeper"
10
10
  s.license = "MIT"
11
11
 
@@ -2,15 +2,31 @@ module CacheKeeper
2
2
  class Configuration
3
3
  DEFAULT_MUST_REVALIDATE = false
4
4
  DEFAULT_QUEUES = {}
5
+ DEFAULT_CRON_EXPRESSION = "*/15 * * * *" # Every 15 minutes, every day
5
6
 
6
7
  def must_revalidate
7
- return rails_config[:must_revalidate] unless rails_config[:must_revalidate].nil?
8
+ return rails_config.must_revalidate unless rails_config.must_revalidate.nil?
8
9
 
9
10
  DEFAULT_MUST_REVALIDATE
10
11
  end
11
12
 
12
13
  def queues
13
- rails_config[:queues] || DEFAULT_QUEUES
14
+ rails_config.queues || DEFAULT_QUEUES
15
+ end
16
+
17
+ def cron_adapter
18
+ return if rails_config.cron.adapter.nil?
19
+
20
+ case rails_config.cron.adapter
21
+ when :good_job
22
+ CacheKeeper::Cron::GoodJobAdapter
23
+ else
24
+ raise "Unknown cron adapter: #{rails_config.cron.adapter}"
25
+ end
26
+ end
27
+
28
+ def cron_expression
29
+ rails_config.cron.expression || DEFAULT_CRON_EXPRESSION
14
30
  end
15
31
 
16
32
  private
@@ -0,0 +1,10 @@
1
+ module CacheKeeper
2
+ class Cron::GoodJobAdapter
3
+ def self.setup
4
+ Rails.application.config.good_job.cron[:cache_keeper] = {
5
+ class: "CacheKeeper::RefreshAllJob",
6
+ cron: CacheKeeper.configuration.cron_format
7
+ }
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module CacheKeeper
2
+ module Cron
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :GoodJobAdapter
6
+ end
7
+ end
@@ -3,7 +3,8 @@ module CacheKeeper
3
3
  isolate_namespace CacheKeeper
4
4
 
5
5
  config.cache_keeper = ActiveSupport::OrderedOptions.new
6
- config.cache_keeper.queues = ActiveSupport::InheritableOptions.new
6
+ config.cache_keeper.queues = ActiveSupport::OrderedOptions.new
7
+ config.cache_keeper.cron = ActiveSupport::OrderedOptions.new
7
8
 
8
9
  config.eager_load_namespaces << CacheKeeper
9
10
  config.autoload_once_paths = %W(
@@ -18,6 +19,12 @@ module CacheKeeper
18
19
  end
19
20
  end
20
21
 
22
+ initializer "cache_keeper.cron_adapter" do |app|
23
+ config.to_prepare do
24
+ CacheKeeper.configuration.cron_adapter&.setup
25
+ end
26
+ end
27
+
21
28
  initializer "cache_keeper.caching_methods" do |app|
22
29
  ActiveSupport.on_load :action_controller do
23
30
  ActionController::Base.send :include, CacheKeeper::Caching
@@ -3,32 +3,34 @@ module CacheKeeper
3
3
  attr_accessor :cached_methods
4
4
 
5
5
  def initialize
6
- self.cached_methods = []
7
- end
8
-
9
- def find(klass, method_name)
10
- cached_methods.find do |cached_method|
11
- cached_method.klass == klass && cached_method.method_name == method_name
12
- end
6
+ self.cached_methods = CacheKeeper::Store.new
13
7
  end
14
8
 
15
9
  def handled?(klass, method_name)
16
- find(klass, method_name).present?
10
+ cached_methods.find_by(klass, method_name).present?
17
11
  end
18
12
 
19
13
  def handle(klass, method_name, options)
20
14
  CacheKeeper::CachedMethod.new(klass, method_name, options).tap do |cached_method|
15
+ if unsupported_options?(cached_method)
16
+ raise "You're trying to autorefresh an ActiveRecord model, which we don't currently support."
17
+ end
18
+
21
19
  cached_methods << cached_method
22
20
  end
23
21
  end
24
22
 
25
23
  def activate_if_handling(klass, method_name)
26
- cached_method = find(klass, method_name) or return
24
+ cached_method = cached_methods.find_by(klass, method_name) or return
27
25
 
28
- return unless requires_activation?(cached_method)
26
+ if requires_activation?(cached_method)
27
+ if unsupported_arity?(cached_method)
28
+ raise "You're trying to cache a method with parameters, which we don't currently support."
29
+ end
29
30
 
30
- CacheKeeper::ReplaceMethod.replace(cached_method) do
31
- cached_method.call(self)
31
+ CacheKeeper::ReplaceMethod.replace(cached_method) do
32
+ cached_method.call(self)
33
+ end
32
34
  end
33
35
  end
34
36
 
@@ -40,5 +42,17 @@ module CacheKeeper
40
42
 
41
43
  true
42
44
  end
45
+
46
+ def unsupported_options?(cached_method)
47
+ cached_method.klass < ActiveRecord::Base && cached_method.options[:autorefresh].present?
48
+ end
49
+
50
+ def unsupported_arity?(cached_method)
51
+ original_method =
52
+ cached_method.klass.instance_method(cached_method.method_name) ||
53
+ cached_method.klass.privateinstance_method(cached_method.method_name)
54
+
55
+ original_method.arity.nonzero?
56
+ end
43
57
  end
44
58
  end
@@ -0,0 +1,15 @@
1
+ module CacheKeeper
2
+ class Store < Array
3
+ def find_by(klass, method_name)
4
+ find do |cached_method|
5
+ cached_method.klass == klass && cached_method.method_name == method_name
6
+ end
7
+ end
8
+
9
+ def autorefreshed
10
+ select do |cached_method|
11
+ cached_method.options[:autorefresh].present?
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  module CacheKeeper
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/cache_keeper.rb CHANGED
@@ -7,6 +7,8 @@ module CacheKeeper
7
7
  autoload :Configuration
8
8
  autoload :Manager
9
9
  autoload :ReplaceMethod
10
+ autoload :Store
11
+ autoload :Cron
10
12
 
11
13
  mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
12
14
 
@@ -10,4 +10,8 @@ class Recording < ApplicationRecord
10
10
  def another_method
11
11
  number
12
12
  end
13
+
14
+ def unsupported_method(parameter)
15
+ parameter * 2
16
+ end
13
17
  end
data/test/manager_test.rb CHANGED
@@ -8,4 +8,21 @@ class CacheKeeper::ManagerTest < ActiveSupport::TestCase
8
8
  assert_equal 1, CacheKeeper.manager.cached_methods.count
9
9
  assert_equal :slow_method, CacheKeeper.manager.cached_methods.first.method_name
10
10
  end
11
+
12
+ test "doesn't allow to autorefresh ActiveRecord models" do
13
+ manager = CacheKeeper::Manager.new
14
+
15
+ assert_raises RuntimeError do
16
+ manager.handle Recording, :slow_method, autorefresh: true
17
+ end
18
+ end
19
+
20
+ test "doesn't allow to activate methods with parameters" do
21
+ manager = CacheKeeper::Manager.new
22
+ manager.handle Recording, :unsupported_method, {}
23
+
24
+ assert_raises RuntimeError do
25
+ manager.activate_if_handling Recording, :unsupported_method
26
+ end
27
+ end
11
28
  end
@@ -0,0 +1,19 @@
1
+ require "test_helper"
2
+
3
+ class CacheKeeper::StoreTest < ActiveSupport::TestCase
4
+ test "#find_by returns only the one with the requested parameters" do
5
+ cached_method = CacheKeeper::CachedMethod.new(String, :slow_method, {})
6
+ autorefreshed_cached_method = CacheKeeper::CachedMethod.new(String, :really_slow_method, { autorefresh: true })
7
+ store = CacheKeeper::Store.new([cached_method, autorefreshed_cached_method])
8
+
9
+ assert_equal autorefreshed_cached_method, store.find_by(String, :really_slow_method)
10
+ end
11
+
12
+ test "#autorefreshed returns only the ones with the correct option" do
13
+ cached_method = CacheKeeper::CachedMethod.new(String, :slow_method, {})
14
+ autorefreshed_cached_method = CacheKeeper::CachedMethod.new(String, :really_slow_method, { autorefresh: true })
15
+ store = CacheKeeper::Store.new([cached_method, autorefreshed_cached_method])
16
+
17
+ assert_equal [autorefreshed_cached_method], store.autorefreshed
18
+ end
19
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cache_keeper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Zamuner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-23 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -37,6 +37,7 @@ files:
37
37
  - MIT-LICENSE
38
38
  - README.md
39
39
  - app/jobs/cache_keeper/base_job.rb
40
+ - app/jobs/cache_keeper/refresh_all_job.rb
40
41
  - app/jobs/cache_keeper/refresh_job.rb
41
42
  - app/models/cache_keeper/cached_method.rb
42
43
  - app/models/cache_keeper/cached_method/refreshable.rb
@@ -49,9 +50,12 @@ files:
49
50
  - lib/cache_keeper.rb
50
51
  - lib/cache_keeper/caching.rb
51
52
  - lib/cache_keeper/configuration.rb
53
+ - lib/cache_keeper/cron.rb
54
+ - lib/cache_keeper/cron/good_job_adapter.rb
52
55
  - lib/cache_keeper/engine.rb
53
56
  - lib/cache_keeper/manager.rb
54
57
  - lib/cache_keeper/replace_method.rb
58
+ - lib/cache_keeper/store.rb
55
59
  - lib/cache_keeper/version.rb
56
60
  - test/cache_helper.rb
57
61
  - test/dummy/Rakefile
@@ -128,6 +132,7 @@ files:
128
132
  - test/models/cached_method_test.rb
129
133
  - test/serializers/cached_method_serializer_test.rb
130
134
  - test/serializers/whatever_serializer_test.rb
135
+ - test/store_test.rb
131
136
  - test/test_helper.rb
132
137
  homepage: https://github.com/martinzamuner/cache_keeper
133
138
  licenses:
@@ -151,7 +156,7 @@ requirements: []
151
156
  rubygems_version: 3.4.10
152
157
  signing_key:
153
158
  specification_version: 4
154
- summary: Keep a fresh copy of any method in your cache
159
+ summary: Have your cached methods refreshed asynchronously and automatically
155
160
  test_files:
156
161
  - test/cache_helper.rb
157
162
  - test/dummy/Rakefile
@@ -228,4 +233,5 @@ test_files:
228
233
  - test/models/cached_method_test.rb
229
234
  - test/serializers/cached_method_serializer_test.rb
230
235
  - test/serializers/whatever_serializer_test.rb
236
+ - test/store_test.rb
231
237
  - test/test_helper.rb