cache_keeper 0.2.1 → 0.3.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: d50a51737d307549be39ed49fb7edcfe391a270c0b55d25b52e70092276c548c
4
- data.tar.gz: 5f2d9f844f4eb18bd41eb27a51558d4dd4aec13672abaf13cdcf2598cdfe8263
3
+ metadata.gz: 167f1cc0d9fdfca15b5882a90dde733b9d74b65f3adfad47f71ef34606b6e9d0
4
+ data.tar.gz: 278285596e0e0bdcbab9a687c07244c0c2f57d19e588b275ccb3e3281a8f3f78
5
5
  SHA512:
6
- metadata.gz: '0519ed2a41f0c66ffcd0e8911f22d87f7e62b4b0fee2d3c1d1f9f9c567f9ebcebfa196b7253eb89b1534e04f5dbec873c94715ebc39fcb0e7836d4c5927b84d8'
7
- data.tar.gz: 6b341290d842a7c6ec3c04170989428ea52bbfbd5a6f916a649aa0617326fb42766111f4dedbfcf714b86d403d26f3f5b57635c4cc3e9a34630ab130111da278
6
+ metadata.gz: 62d8e87244040101cb4f345c1e11a6b857c23bd7d7074877e0358d18e44c8acf7b76182ce0a922fa2998699ea74ede71484430c4f701cd12978710e7420b3022
7
+ data.tar.gz: 72a3207e7df9c92dcaf5764ba3055bed5cba3e943c406f824e6087d741c245301b7b11f05c00091cd8c77ed6a5739e31a843134f08d6856f9d677ee54a2f645d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cache_keeper (0.2.1)
4
+ cache_keeper (0.3.0)
5
5
  rails (>= 6.1.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -39,15 +39,23 @@ By default, it will immediately run the method call if it hasn't been cached bef
39
39
 
40
40
  It's important to note that it will only work with methods that don't take any arguments.
41
41
 
42
- ### Autorefresh
42
+ ### Serialization
43
43
 
44
- You can tell CacheKeeper to automatically refresh the cache after a certain amount of time by setting the `autorefresh` option:
44
+ CacheKeeper needs to pass the instance on which the cached method is called along to the refresh job. As any other job argument, ActiveJob requires it to be serializable. ActiveRecord instances are serializable by default, but controllers, POROs and other classes are not. CacheKeeper provides a `serializer` option that will work in most cases:
45
45
 
46
46
  ```ruby
47
- caches :i_cant_even, expires_in: 2.hours, autorefresh: true
47
+ class Example
48
+ # Generate a new instance using an empty initializer (Example.new)
49
+ # Useful for controllers and for POROs with no arguments
50
+ caches :slow_method, serializer: :new_instance
51
+
52
+ # Replicate the old instance using Marshal.dump and Marshal.load
53
+ # Useful in most other cases, but make sure the dump is not too big
54
+ caches :slow_method, serializer: :marshal
55
+ end
48
56
  ```
49
57
 
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.
58
+ If those options don't work for you, you can always [write custom serializer](https://guides.rubyonrails.org/active_job_basics.html#serializers) for your classes.
51
59
 
52
60
 
53
61
  ## Configuration
@@ -63,15 +71,6 @@ Rails.application.configure do
63
71
  # The queue to use for the refresh jobs.
64
72
  # Default: nil (uses the default queue)
65
73
  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 * * * *"
75
74
  end
76
75
  ```
77
76
 
@@ -3,14 +3,18 @@ class CacheKeeper::BaseJob < ActiveJob::Base
3
3
 
4
4
  private
5
5
 
6
- # Monkey patch ActiveJob::Core#serialize_arguments to use CacheKeeper::WhateverSerializer
7
- # in case there's no serializer for the argument. I'm doing it this way because I don't
6
+ # Monkey patch ActiveJob::Core#serialize_arguments to use our custom serializers
7
+ # in case the `serializer` option is present. I'm doing it this way because I don't
8
8
  # want to register the serializer as it would affect the whole application.
9
9
  def serialize_arguments(arguments)
10
10
  arguments.map do |argument|
11
11
  ActiveJob::Arguments.send :serialize_argument, argument
12
- rescue ActiveJob::SerializationError
13
- CacheKeeper::WhateverSerializer.serialize argument
12
+ rescue ActiveJob::SerializationError => e
13
+ if arguments.first.serialize_target?
14
+ arguments.first.serialize_target argument
15
+ else
16
+ raise e
17
+ end
14
18
  end
15
19
  end
16
20
  end
@@ -1,7 +1,7 @@
1
1
  class CacheKeeper::RefreshJob < CacheKeeper::BaseJob
2
2
  queue_as { CacheKeeper.configuration.queues[:refresh] }
3
3
 
4
- def perform(cached_method, instance)
5
- cached_method.refresh instance
4
+ def perform(cached_method, target)
5
+ cached_method.refresh target
6
6
  end
7
7
  end
@@ -1,13 +1,11 @@
1
1
  module CacheKeeper::CachedMethod::Refreshable
2
- def refresh(instance)
2
+ def refresh(target)
3
3
  Rails.cache.fetch(cache_key, expires_in: expires_in) do
4
- instance.send alias_for_original_method
4
+ target.send alias_for_original_method
5
5
  end
6
6
  end
7
7
 
8
- def refresh_later(instance = nil)
9
- instance ||= klass.new
10
-
11
- CacheKeeper::RefreshJob.perform_later self, instance
8
+ def refresh_later(target)
9
+ CacheKeeper::RefreshJob.perform_later self, target
12
10
  end
13
11
  end
@@ -0,0 +1,18 @@
1
+ module CacheKeeper::CachedMethod::SerializableTarget
2
+ def serialize_target?
3
+ options[:serializer].present?
4
+ end
5
+
6
+ def serialize_target(target)
7
+ case options[:serializer]
8
+ when :new_instance
9
+ CacheKeeper::NewInstanceSerializer.serialize target
10
+ when :marshal
11
+ CacheKeeper::MarshalSerializer.serialize target
12
+ else
13
+ raise "Unknown serializer: #{options[:serializer]}"
14
+ end
15
+ rescue StandardError => e
16
+ raise "Error serializing target using #{options[:serializer]}: #{e}"
17
+ end
18
+ end
@@ -1,5 +1,6 @@
1
1
  class CacheKeeper::CachedMethod
2
2
  include Refreshable
3
+ include SerializableTarget
3
4
 
4
5
  attr_accessor :klass, :method_name, :options
5
6
 
@@ -17,14 +18,14 @@ class CacheKeeper::CachedMethod
17
18
  cache_entry.blank? || cache_entry.expired?
18
19
  end
19
20
 
20
- def call(instance)
21
+ def call(target)
21
22
  if cache_entry.blank?
22
- refresh instance
23
+ refresh target
23
24
  elsif cache_entry.expired?
24
25
  if must_revalidate?
25
- refresh instance
26
+ refresh target
26
27
  else
27
- refresh_later instance
28
+ refresh_later target
28
29
 
29
30
  cache_entry.value
30
31
  end
@@ -0,0 +1,9 @@
1
+ class CacheKeeper::MarshalSerializer < ActiveJob::Serializers::ObjectSerializer
2
+ def serialize(target)
3
+ super("dump" => Marshal.dump(target).force_encoding("ISO-8859-1").encode("UTF-8"))
4
+ end
5
+
6
+ def deserialize(json)
7
+ Marshal.load(json["dump"].encode("ISO-8859-1").force_encoding("ASCII-8BIT"))
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class CacheKeeper::NewInstanceSerializer < ActiveJob::Serializers::ObjectSerializer
2
+ def serialize(target)
3
+ super("klass" => target.class.to_s)
4
+ end
5
+
6
+ def deserialize(json)
7
+ json["klass"].constantize.new
8
+ end
9
+ end
@@ -2,7 +2,6 @@ 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
6
5
 
7
6
  def must_revalidate
8
7
  return rails_config.must_revalidate unless rails_config.must_revalidate.nil?
@@ -14,21 +13,6 @@ module CacheKeeper
14
13
  rails_config.queues || DEFAULT_QUEUES
15
14
  end
16
15
 
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
30
- end
31
-
32
16
  private
33
17
 
34
18
  def rails_config
@@ -2,16 +2,10 @@ module CacheKeeper
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace CacheKeeper
4
4
 
5
+ config.eager_load_namespaces << CacheKeeper
6
+
5
7
  config.cache_keeper = ActiveSupport::OrderedOptions.new
6
8
  config.cache_keeper.queues = ActiveSupport::OrderedOptions.new
7
- config.cache_keeper.cron = ActiveSupport::OrderedOptions.new
8
-
9
- config.eager_load_namespaces << CacheKeeper
10
- config.autoload_once_paths = %W(
11
- #{root}/app/jobs
12
- #{root}/app/models
13
- #{root}/app/serializers
14
- )
15
9
 
16
10
  initializer "cache_keeper.active_job_serializer" do |app|
17
11
  config.to_prepare do
@@ -19,12 +13,6 @@ module CacheKeeper
19
13
  end
20
14
  end
21
15
 
22
- initializer "cache_keeper.cron_adapter" do |app|
23
- config.to_prepare do
24
- CacheKeeper.configuration.cron_adapter&.setup
25
- end
26
- end
27
-
28
16
  initializer "cache_keeper.caching_methods" do |app|
29
17
  ActiveSupport.on_load :action_controller do
30
18
  ActionController::Base.send :include, CacheKeeper::Caching
@@ -29,7 +29,7 @@ module CacheKeeper
29
29
  end
30
30
 
31
31
  CacheKeeper::ReplaceMethod.replace(cached_method) do
32
- cached_method.call(self)
32
+ instance_variable_get(:"@#{method_name}") || instance_variable_set(:"@#{method_name}", cached_method.call(self))
33
33
  end
34
34
  end
35
35
  end
@@ -1,3 +1,3 @@
1
1
  module CacheKeeper
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/cache_keeper.rb CHANGED
@@ -8,7 +8,6 @@ module CacheKeeper
8
8
  autoload :Manager
9
9
  autoload :ReplaceMethod
10
10
  autoload :Store
11
- autoload :Cron
12
11
 
13
12
  mattr_accessor :logger, default: ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new($stdout))
14
13
 
@@ -2,8 +2,6 @@ class Recording < ApplicationRecord
2
2
  caches :slow_method, expires_in: 1.hour
3
3
 
4
4
  def slow_method
5
- sleep 2
6
-
7
5
  42
8
6
  end
9
7
 
data/test/engine_test.rb CHANGED
@@ -5,7 +5,11 @@ class CacheKeeper::EngineTest < ActiveSupport::TestCase
5
5
  assert_includes Rails.application.config.active_job.custom_serializers, CacheKeeper::CachedMethodSerializer
6
6
  end
7
7
 
8
- test "doesn't register the ActiveJob serializer for whatever" do
9
- assert_not_includes Rails.application.config.active_job.custom_serializers, CacheKeeper::WhateverSerializer
8
+ test "doesn't register the ActiveJob new_instance serializer" do
9
+ assert_not_includes Rails.application.config.active_job.custom_serializers, CacheKeeper::NewInstanceSerializer
10
+ end
11
+
12
+ test "doesn't register the ActiveJob marshal serializer" do
13
+ assert_not_includes Rails.application.config.active_job.custom_serializers, CacheKeeper::MarshalSerializer
10
14
  end
11
15
  end
@@ -0,0 +1,14 @@
1
+ require "test_helper"
2
+
3
+ class CacheKeeper::RefreshJobTest < ActiveSupport::TestCase
4
+ include ActiveJob::TestHelper
5
+
6
+ test "calls #refresh" do
7
+ recording = Recording.create(number: 5)
8
+ cached_method = CacheKeeper.manager.cached_methods.first
9
+
10
+ assert_performed_with(queue: :default) do
11
+ CacheKeeper::RefreshJob.perform_later cached_method, recording
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require "test_helper"
2
+
3
+ class CacheKeeper::CachedMethod::RefreshableTest < ActiveSupport::TestCase
4
+ include ActiveJob::TestHelper
5
+
6
+ test "#refresh_later enqueues a refresh job" do
7
+ recording = Recording.create(number: 5)
8
+ cached_method = CacheKeeper.manager.cached_methods.first
9
+
10
+ assert_enqueued_with(job: CacheKeeper::RefreshJob, queue: :default) do
11
+ cached_method.refresh_later recording
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,24 @@
1
+ require "test_helper"
2
+
3
+ class CacheKeeper::CachedMethod::SerializableTargetTest < ActiveSupport::TestCase
4
+ test "#serialize_target doesn't allow unknown serializers" do
5
+ recording = Recording.new(number: 5)
6
+ cached_method = CacheKeeper::CachedMethod.new(Recording, :another_method, serializer: :unknown_serializer)
7
+
8
+ error = assert_raises RuntimeError do
9
+ cached_method.serialize_target(recording)
10
+ end
11
+
12
+ assert_includes error.message, "Unknown serializer: unknown_serializer"
13
+ end
14
+
15
+ test "#serialize_target raises an error if unable to serialize" do
16
+ cached_method = CacheKeeper::CachedMethod.new(Recording, :another_method, serializer: :marshal)
17
+
18
+ error = assert_raises RuntimeError do
19
+ cached_method.serialize_target(Proc.new {})
20
+ end
21
+
22
+ assert_includes error.message, "Error serializing target using marshal:"
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ require "test_helper"
2
+
3
+ class CacheKeeper::MarshalSerializerTest < ActiveSupport::TestCase
4
+ test "serializes the marshal dump" do
5
+ target = Recording.new
6
+ serialized = serializer.serialize(target)
7
+
8
+ assert_equal serialized["dump"], Marshal.dump(target).force_encoding("ISO-8859-1").encode("UTF-8")
9
+ end
10
+
11
+ test "deserializes the marshal dump" do
12
+ target = Recording.new
13
+ serialized = serializer.serialize(target)
14
+ deserialized = serializer.deserialize(serialized)
15
+
16
+ assert_equal Recording, deserialized.class
17
+ end
18
+
19
+ private
20
+
21
+ def serializer
22
+ CacheKeeper::MarshalSerializer
23
+ end
24
+ end
@@ -1,10 +1,6 @@
1
1
  require "test_helper"
2
2
 
3
- class CacheKeeper::WhateverSerializerTest < ActiveSupport::TestCase
4
- test "serializes whatever" do
5
- assert serializer.serialize?(RecordingsController.new)
6
- end
7
-
3
+ class CacheKeeper::NewInstanceSerializerTest < ActiveSupport::TestCase
8
4
  test "serializes the class name" do
9
5
  serialized = serializer.serialize(RecordingsController.new)
10
6
 
@@ -21,6 +17,6 @@ class CacheKeeper::WhateverSerializerTest < ActiveSupport::TestCase
21
17
  private
22
18
 
23
19
  def serializer
24
- CacheKeeper::WhateverSerializer
20
+ CacheKeeper::NewInstanceSerializer
25
21
  end
26
22
  end
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.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Zamuner
@@ -37,12 +37,13 @@ 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
41
40
  - app/jobs/cache_keeper/refresh_job.rb
42
41
  - app/models/cache_keeper/cached_method.rb
43
42
  - app/models/cache_keeper/cached_method/refreshable.rb
43
+ - app/models/cache_keeper/cached_method/serializable_target.rb
44
44
  - app/serializers/cache_keeper/cached_method_serializer.rb
45
- - app/serializers/cache_keeper/whatever_serializer.rb
45
+ - app/serializers/cache_keeper/marshal_serializer.rb
46
+ - app/serializers/cache_keeper/new_instance_serializer.rb
46
47
  - bin/rails
47
48
  - bin/release
48
49
  - bin/test
@@ -50,8 +51,6 @@ files:
50
51
  - lib/cache_keeper.rb
51
52
  - lib/cache_keeper/caching.rb
52
53
  - lib/cache_keeper/configuration.rb
53
- - lib/cache_keeper/cron.rb
54
- - lib/cache_keeper/cron/good_job_adapter.rb
55
54
  - lib/cache_keeper/engine.rb
56
55
  - lib/cache_keeper/manager.rb
57
56
  - lib/cache_keeper/replace_method.rb
@@ -128,10 +127,14 @@ files:
128
127
  - test/dummy/test/test_helper.rb
129
128
  - test/dummy/vendor/.keep
130
129
  - test/engine_test.rb
130
+ - test/jobs/refresh_job_test.rb
131
131
  - test/manager_test.rb
132
+ - test/models/cached_method/refreshable_test.rb
133
+ - test/models/cached_method/serializable_target_test.rb
132
134
  - test/models/cached_method_test.rb
133
135
  - test/serializers/cached_method_serializer_test.rb
134
- - test/serializers/whatever_serializer_test.rb
136
+ - test/serializers/marshal_serializer_test.rb
137
+ - test/serializers/new_instance_serializer_test.rb
135
138
  - test/store_test.rb
136
139
  - test/test_helper.rb
137
140
  homepage: https://github.com/martinzamuner/cache_keeper
@@ -229,9 +232,13 @@ test_files:
229
232
  - test/dummy/test/test_helper.rb
230
233
  - test/dummy/vendor/.keep
231
234
  - test/engine_test.rb
235
+ - test/jobs/refresh_job_test.rb
232
236
  - test/manager_test.rb
237
+ - test/models/cached_method/refreshable_test.rb
238
+ - test/models/cached_method/serializable_target_test.rb
233
239
  - test/models/cached_method_test.rb
234
240
  - test/serializers/cached_method_serializer_test.rb
235
- - test/serializers/whatever_serializer_test.rb
241
+ - test/serializers/marshal_serializer_test.rb
242
+ - test/serializers/new_instance_serializer_test.rb
236
243
  - test/store_test.rb
237
244
  - test/test_helper.rb
@@ -1,11 +0,0 @@
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
@@ -1,15 +0,0 @@
1
- class CacheKeeper::WhateverSerializer < ActiveJob::Serializers::ObjectSerializer
2
- def serialize?(argument)
3
- true
4
- end
5
-
6
- def serialize(whatever)
7
- super(
8
- "klass" => whatever.class.to_s
9
- )
10
- end
11
-
12
- def deserialize(hash)
13
- hash["klass"].constantize.new
14
- end
15
- end
@@ -1,10 +0,0 @@
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_expression
7
- }
8
- end
9
- end
10
- end
@@ -1,7 +0,0 @@
1
- module CacheKeeper
2
- module Cron
3
- extend ActiveSupport::Autoload
4
-
5
- autoload :GoodJobAdapter
6
- end
7
- end