cache_keeper 0.1.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.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +38 -0
  3. data/.gitignore +10 -0
  4. data/Gemfile +8 -0
  5. data/Gemfile.lock +197 -0
  6. data/MIT-LICENSE +20 -0
  7. data/README.md +69 -0
  8. data/app/jobs/cache_keeper/base_job.rb +16 -0
  9. data/app/jobs/cache_keeper/refresh_job.rb +7 -0
  10. data/app/models/cache_keeper/cached_method/refreshable.rb +11 -0
  11. data/app/models/cache_keeper/cached_method.rb +49 -0
  12. data/app/serializers/cache_keeper/cached_method_serializer.rb +17 -0
  13. data/app/serializers/cache_keeper/whatever_serializer.rb +15 -0
  14. data/bin/rails +13 -0
  15. data/bin/release +13 -0
  16. data/bin/test +11 -0
  17. data/cache_keeper.gemspec +18 -0
  18. data/lib/cache_keeper/caching.rb +20 -0
  19. data/lib/cache_keeper/configuration.rb +20 -0
  20. data/lib/cache_keeper/engine.rb +22 -0
  21. data/lib/cache_keeper/manager.rb +42 -0
  22. data/lib/cache_keeper/replace_method.rb +48 -0
  23. data/lib/cache_keeper/version.rb +3 -0
  24. data/lib/cache_keeper.rb +13 -0
  25. data/test/cache_helper.rb +29 -0
  26. data/test/dummy/Rakefile +3 -0
  27. data/test/dummy/app/assets/config/manifest.js +3 -0
  28. data/test/dummy/app/assets/images/.keep +0 -0
  29. data/test/dummy/app/assets/javascripts/application.js +15 -0
  30. data/test/dummy/app/assets/javascripts/cable.js +13 -0
  31. data/test/dummy/app/assets/javascripts/channels/.keep +0 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  33. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  34. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  35. data/test/dummy/app/controllers/application_controller.rb +2 -0
  36. data/test/dummy/app/controllers/concerns/.keep +0 -0
  37. data/test/dummy/app/controllers/recordings_controller.rb +5 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/jobs/application_job.rb +2 -0
  40. data/test/dummy/app/mailers/application_mailer.rb +4 -0
  41. data/test/dummy/app/models/application_record.rb +3 -0
  42. data/test/dummy/app/models/concerns/.keep +0 -0
  43. data/test/dummy/app/models/recording.rb +13 -0
  44. data/test/dummy/app/views/layouts/application.html.erb +15 -0
  45. data/test/dummy/app/views/layouts/mailer.html.erb +13 -0
  46. data/test/dummy/app/views/layouts/mailer.text.erb +1 -0
  47. data/test/dummy/bin/rails +4 -0
  48. data/test/dummy/config/application.rb +21 -0
  49. data/test/dummy/config/boot.rb +5 -0
  50. data/test/dummy/config/cable.yml +10 -0
  51. data/test/dummy/config/database.yml +25 -0
  52. data/test/dummy/config/environment.rb +5 -0
  53. data/test/dummy/config/environments/development.rb +46 -0
  54. data/test/dummy/config/environments/production.rb +94 -0
  55. data/test/dummy/config/environments/test.rb +51 -0
  56. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  57. data/test/dummy/config/initializers/assets.rb +6 -0
  58. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  59. data/test/dummy/config/initializers/content_security_policy.rb +25 -0
  60. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  61. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  62. data/test/dummy/config/initializers/inflections.rb +16 -0
  63. data/test/dummy/config/initializers/mime_types.rb +4 -0
  64. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  65. data/test/dummy/config/locales/en.yml +33 -0
  66. data/test/dummy/config/puma.rb +34 -0
  67. data/test/dummy/config/routes.rb +3 -0
  68. data/test/dummy/config/spring.rb +6 -0
  69. data/test/dummy/config/storage.yml +34 -0
  70. data/test/dummy/config.ru +5 -0
  71. data/test/dummy/db/migrate/20200504213548_create_recordings.rb +7 -0
  72. data/test/dummy/db/schema.rb +19 -0
  73. data/test/dummy/db/seeds.rb +7 -0
  74. data/test/dummy/lib/assets/.keep +0 -0
  75. data/test/dummy/lib/tasks/.keep +0 -0
  76. data/test/dummy/log/.keep +0 -0
  77. data/test/dummy/public/404.html +67 -0
  78. data/test/dummy/public/422.html +67 -0
  79. data/test/dummy/public/500.html +66 -0
  80. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  81. data/test/dummy/public/apple-touch-icon.png +0 -0
  82. data/test/dummy/public/favicon.ico +0 -0
  83. data/test/dummy/public/robots.txt +1 -0
  84. data/test/dummy/test/application_system_test_case.rb +5 -0
  85. data/test/dummy/test/controllers/.keep +0 -0
  86. data/test/dummy/test/fixtures/.keep +0 -0
  87. data/test/dummy/test/fixtures/files/.keep +0 -0
  88. data/test/dummy/test/helpers/.keep +0 -0
  89. data/test/dummy/test/integration/.keep +0 -0
  90. data/test/dummy/test/mailers/.keep +0 -0
  91. data/test/dummy/test/models/.keep +0 -0
  92. data/test/dummy/test/system/.keep +0 -0
  93. data/test/dummy/test/test_helper.rb +10 -0
  94. data/test/dummy/vendor/.keep +0 -0
  95. data/test/engine_test.rb +11 -0
  96. data/test/manager_test.rb +8 -0
  97. data/test/models/cached_method_test.rb +20 -0
  98. data/test/serializers/cached_method_serializer_test.rb +36 -0
  99. data/test/serializers/whatever_serializer_test.rb +26 -0
  100. data/test/test_helper.rb +10 -0
  101. metadata +231 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a594d57869401830f847424c24851913039af6979f2e5f9665d77b96bb2c955e
4
+ data.tar.gz: c80f3edb0b84157a3e49ef8438347931c515ecffe2a55c9a08a630b2fca1c6c0
5
+ SHA512:
6
+ metadata.gz: 46806eb4e857047be29c784b9c8455a2ce92ac79ebe19fac9bcec704fa9c29c48b63ebf546e78d615cf8d429044bc7557dbac7e94abc939b1288353f856729b3
7
+ data.tar.gz: b1902aa78ec63d686c7435924c412c997828c117cc498a6a474d0b00c3175c621542d5677a74bb97172523d0a24df38ee916ab8c0a0edafc42047a31dfa87a21
@@ -0,0 +1,38 @@
1
+ name: CI
2
+ on: [push, pull_request]
3
+ jobs:
4
+ tests:
5
+ strategy:
6
+ matrix:
7
+ ruby-version:
8
+ - "2.7"
9
+ - "3.0"
10
+ rails-version:
11
+ - "6.1"
12
+ - "7.0"
13
+ - "main"
14
+
15
+ env:
16
+ RAILS_VERSION: "${{ matrix.rails-version }}"
17
+
18
+ name: ${{ format('Tests (Ruby {0}, Rails {1})', matrix.ruby-version, matrix.rails-version) }}
19
+ runs-on: ubuntu-latest
20
+ continue-on-error: true
21
+
22
+ steps:
23
+ - uses: actions/checkout@v1
24
+
25
+ - name: Install Ruby
26
+ uses: ruby/setup-ruby@v1
27
+ with:
28
+ ruby-version: ${{ matrix.ruby-version }}
29
+ bundler-cache: true
30
+
31
+ - name: Run tests
32
+ run: |
33
+ bin/test test/**/*_test.rb
34
+
35
+ - name: Fail when generated changes are not checked-in
36
+ run: |
37
+ git update-index --refresh
38
+ git diff-index --quiet HEAD --
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ *.gem
2
+ .byebug_history
3
+
4
+ test/dummy/db/*.sqlite3
5
+ test/dummy/db/*.sqlite3*
6
+ test/dummy/log/*.log
7
+ test/dummy/node_modules/
8
+ test/dummy/yarn-error.log
9
+ test/dummy/storage/
10
+ test/dummy/tmp/
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ gem "rake"
6
+
7
+ gem "rails", ">= 6.1"
8
+ gem "sqlite3"
data/Gemfile.lock ADDED
@@ -0,0 +1,197 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ cache_keeper (0.1.0)
5
+ rails (>= 6.1.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ actioncable (7.1.1)
11
+ actionpack (= 7.1.1)
12
+ activesupport (= 7.1.1)
13
+ nio4r (~> 2.0)
14
+ websocket-driver (>= 0.6.1)
15
+ zeitwerk (~> 2.6)
16
+ actionmailbox (7.1.1)
17
+ actionpack (= 7.1.1)
18
+ activejob (= 7.1.1)
19
+ activerecord (= 7.1.1)
20
+ activestorage (= 7.1.1)
21
+ activesupport (= 7.1.1)
22
+ mail (>= 2.7.1)
23
+ net-imap
24
+ net-pop
25
+ net-smtp
26
+ actionmailer (7.1.1)
27
+ actionpack (= 7.1.1)
28
+ actionview (= 7.1.1)
29
+ activejob (= 7.1.1)
30
+ activesupport (= 7.1.1)
31
+ mail (~> 2.5, >= 2.5.4)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
35
+ rails-dom-testing (~> 2.2)
36
+ actionpack (7.1.1)
37
+ actionview (= 7.1.1)
38
+ activesupport (= 7.1.1)
39
+ nokogiri (>= 1.8.5)
40
+ rack (>= 2.2.4)
41
+ rack-session (>= 1.0.1)
42
+ rack-test (>= 0.6.3)
43
+ rails-dom-testing (~> 2.2)
44
+ rails-html-sanitizer (~> 1.6)
45
+ actiontext (7.1.1)
46
+ actionpack (= 7.1.1)
47
+ activerecord (= 7.1.1)
48
+ activestorage (= 7.1.1)
49
+ activesupport (= 7.1.1)
50
+ globalid (>= 0.6.0)
51
+ nokogiri (>= 1.8.5)
52
+ actionview (7.1.1)
53
+ activesupport (= 7.1.1)
54
+ builder (~> 3.1)
55
+ erubi (~> 1.11)
56
+ rails-dom-testing (~> 2.2)
57
+ rails-html-sanitizer (~> 1.6)
58
+ activejob (7.1.1)
59
+ activesupport (= 7.1.1)
60
+ globalid (>= 0.3.6)
61
+ activemodel (7.1.1)
62
+ activesupport (= 7.1.1)
63
+ activerecord (7.1.1)
64
+ activemodel (= 7.1.1)
65
+ activesupport (= 7.1.1)
66
+ timeout (>= 0.4.0)
67
+ activestorage (7.1.1)
68
+ actionpack (= 7.1.1)
69
+ activejob (= 7.1.1)
70
+ activerecord (= 7.1.1)
71
+ activesupport (= 7.1.1)
72
+ marcel (~> 1.0)
73
+ activesupport (7.1.1)
74
+ base64
75
+ bigdecimal
76
+ concurrent-ruby (~> 1.0, >= 1.0.2)
77
+ connection_pool (>= 2.2.5)
78
+ drb
79
+ i18n (>= 1.6, < 2)
80
+ minitest (>= 5.1)
81
+ mutex_m
82
+ tzinfo (~> 2.0)
83
+ base64 (0.1.1)
84
+ bigdecimal (3.1.4)
85
+ builder (3.2.4)
86
+ concurrent-ruby (1.2.2)
87
+ connection_pool (2.4.1)
88
+ crass (1.0.6)
89
+ date (3.3.3)
90
+ drb (2.1.1)
91
+ ruby2_keywords
92
+ erubi (1.12.0)
93
+ globalid (1.2.1)
94
+ activesupport (>= 6.1)
95
+ i18n (1.14.1)
96
+ concurrent-ruby (~> 1.0)
97
+ io-console (0.6.0)
98
+ irb (1.8.3)
99
+ rdoc
100
+ reline (>= 0.3.8)
101
+ loofah (2.21.4)
102
+ crass (~> 1.0.2)
103
+ nokogiri (>= 1.12.0)
104
+ mail (2.8.1)
105
+ mini_mime (>= 0.1.1)
106
+ net-imap
107
+ net-pop
108
+ net-smtp
109
+ marcel (1.0.2)
110
+ mini_mime (1.1.5)
111
+ minitest (5.20.0)
112
+ mutex_m (0.1.2)
113
+ net-imap (0.4.2)
114
+ date
115
+ net-protocol
116
+ net-pop (0.1.2)
117
+ net-protocol
118
+ net-protocol (0.2.1)
119
+ timeout
120
+ net-smtp (0.4.0)
121
+ net-protocol
122
+ nio4r (2.5.9)
123
+ nokogiri (1.15.4-arm64-darwin)
124
+ racc (~> 1.4)
125
+ nokogiri (1.15.4-x86_64-linux)
126
+ racc (~> 1.4)
127
+ psych (5.1.1.1)
128
+ stringio
129
+ racc (1.7.1)
130
+ rack (3.0.8)
131
+ rack-session (2.0.0)
132
+ rack (>= 3.0.0)
133
+ rack-test (2.1.0)
134
+ rack (>= 1.3)
135
+ rackup (2.1.0)
136
+ rack (>= 3)
137
+ webrick (~> 1.8)
138
+ rails (7.1.1)
139
+ actioncable (= 7.1.1)
140
+ actionmailbox (= 7.1.1)
141
+ actionmailer (= 7.1.1)
142
+ actionpack (= 7.1.1)
143
+ actiontext (= 7.1.1)
144
+ actionview (= 7.1.1)
145
+ activejob (= 7.1.1)
146
+ activemodel (= 7.1.1)
147
+ activerecord (= 7.1.1)
148
+ activestorage (= 7.1.1)
149
+ activesupport (= 7.1.1)
150
+ bundler (>= 1.15.0)
151
+ railties (= 7.1.1)
152
+ rails-dom-testing (2.2.0)
153
+ activesupport (>= 5.0.0)
154
+ minitest
155
+ nokogiri (>= 1.6)
156
+ rails-html-sanitizer (1.6.0)
157
+ loofah (~> 2.21)
158
+ nokogiri (~> 1.14)
159
+ railties (7.1.1)
160
+ actionpack (= 7.1.1)
161
+ activesupport (= 7.1.1)
162
+ irb
163
+ rackup (>= 1.0.0)
164
+ rake (>= 12.2)
165
+ thor (~> 1.0, >= 1.2.2)
166
+ zeitwerk (~> 2.6)
167
+ rake (13.0.6)
168
+ rdoc (6.5.0)
169
+ psych (>= 4.0.0)
170
+ reline (0.3.9)
171
+ io-console (~> 0.5)
172
+ ruby2_keywords (0.0.5)
173
+ sqlite3 (1.6.7-arm64-darwin)
174
+ sqlite3 (1.6.7-x86_64-linux)
175
+ stringio (3.0.8)
176
+ thor (1.3.0)
177
+ timeout (0.4.0)
178
+ tzinfo (2.0.6)
179
+ concurrent-ruby (~> 1.0)
180
+ webrick (1.8.1)
181
+ websocket-driver (0.7.6)
182
+ websocket-extensions (>= 0.1.0)
183
+ websocket-extensions (0.1.5)
184
+ zeitwerk (2.6.12)
185
+
186
+ PLATFORMS
187
+ arm64-darwin-22
188
+ x86_64-linux
189
+
190
+ DEPENDENCIES
191
+ cache_keeper!
192
+ rails (>= 6.1)
193
+ rake
194
+ sqlite3
195
+
196
+ BUNDLED WITH
197
+ 2.4.19
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2017 David Heinemeier Hansson, Basecamp
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,69 @@
1
+ <h1 align="center">
2
+ CacheKeeper
3
+ <br>
4
+ </h1>
5
+
6
+ <h3 align="center">Keep cached methods always fresh in your Rails application.</h3>
7
+
8
+ <p align="center">
9
+ <img alt="Build" src="https://img.shields.io/github/actions/workflow/status/martinzamuner/cache_keeper/ci.yml?branch=main">
10
+ <img alt="Gem" src="https://img.shields.io/gem/v/cache_keeper">
11
+ <img alt="rails version" src="https://img.shields.io/badge/rails-%3E%3D%206.1.0-informational">
12
+ <img alt="License" src="https://img.shields.io/github/license/martinzamuner/cache_keeper">
13
+ </p>
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.
16
+
17
+
18
+ ## Installation
19
+
20
+ Add CacheKeeper to your Gemfile:
21
+
22
+ ```sh
23
+ bundle add cache_keeper
24
+ ```
25
+
26
+
27
+ ## Usage
28
+
29
+ CacheKeeper provides a `caches` method that will cache the result of the methods you give it:
30
+
31
+ ```ruby
32
+ caches :slow_method, :really_slow_method, expires_in: 1.hour
33
+ caches :incredibly_slow_method, expires_in: 2.hours, must_revalidate: true
34
+ ```
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`.
37
+
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
+
40
+
41
+ ## Configuration
42
+
43
+ CacheKeeper can be configured in an initializer, in any environment file or in your `config/application.rb` file. The following options are available:
44
+
45
+ ```ruby
46
+ Rails.application.configure do
47
+ # If a stale entry is requested, refresh immediately instead of enqueuing a refresh job.
48
+ # Default: false
49
+ config.cache_keeper.must_revalidate = true
50
+
51
+ # The queue to use for the refresh jobs.
52
+ # Default: nil (uses the default queue)
53
+ config.cache_keeper.queues.refresh = :low_priority
54
+ end
55
+ ```
56
+
57
+
58
+ ## Development
59
+
60
+ <details>
61
+ <summary>Running the tests</summary><br>
62
+
63
+ - You can run the whole suite with `./bin/test test/**/*_test.rb`
64
+ </details>
65
+
66
+
67
+ ## License
68
+
69
+ CacheKeeper is released under the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,16 @@
1
+ class CacheKeeper::BaseJob < ActiveJob::Base
2
+ discard_on StandardError
3
+
4
+ private
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
8
+ # want to register the serializer as it would affect the whole application.
9
+ def serialize_arguments(arguments)
10
+ arguments.map do |argument|
11
+ ActiveJob::Arguments.send :serialize_argument, argument
12
+ rescue ActiveJob::SerializationError
13
+ CacheKeeper::WhateverSerializer.serialize argument
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ class CacheKeeper::RefreshJob < CacheKeeper::BaseJob
2
+ queue_as { CacheKeeper.configuration.queues[:refresh] }
3
+
4
+ def perform(cached_method, instance)
5
+ cached_method.refresh instance
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module CacheKeeper::CachedMethod::Refreshable
2
+ def refresh(instance)
3
+ Rails.cache.fetch(cache_key, expires_in: expires_in) do
4
+ instance.send alias_for_original_method
5
+ end
6
+ end
7
+
8
+ def refresh_later(instance)
9
+ CacheKeeper::RefreshJob.perform_later self, instance
10
+ end
11
+ end
@@ -0,0 +1,49 @@
1
+ class CacheKeeper::CachedMethod
2
+ include Refreshable
3
+
4
+ attr_accessor :klass, :method_name, :options
5
+
6
+ def initialize(klass, method_name, options = {})
7
+ self.klass = klass
8
+ self.method_name = method_name
9
+ self.options = options.with_indifferent_access
10
+ end
11
+
12
+ def alias_for_original_method
13
+ :"__#{method_name}__hooked__"
14
+ end
15
+
16
+ def call(instance)
17
+ if cache_entry.blank?
18
+ refresh instance
19
+ elsif cache_entry.expired?
20
+ if must_revalidate?
21
+ refresh instance
22
+ else
23
+ refresh_later instance
24
+
25
+ cache_entry.value
26
+ end
27
+ else
28
+ cache_entry.value
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def cache_entry
35
+ Rails.cache.send :read_entry, Rails.cache.send(:normalize_key, cache_key)
36
+ end
37
+
38
+ def cache_key
39
+ ["CacheKeeper", klass, method_name]
40
+ end
41
+
42
+ def expires_in
43
+ options[:expires_in]
44
+ end
45
+
46
+ def must_revalidate?
47
+ options[:must_revalidate].nil? ? CacheKeeper.configuration.must_revalidate : options[:must_revalidate]
48
+ end
49
+ end
@@ -0,0 +1,17 @@
1
+ class CacheKeeper::CachedMethodSerializer < ActiveJob::Serializers::ObjectSerializer
2
+ def serialize?(argument)
3
+ argument.is_a? CacheKeeper::CachedMethod
4
+ end
5
+
6
+ def serialize(cached_method)
7
+ super(
8
+ "klass" => cached_method.klass,
9
+ "method_name" => cached_method.method_name,
10
+ "options" => cached_method.options
11
+ )
12
+ end
13
+
14
+ def deserialize(hash)
15
+ CacheKeeper::CachedMethod.new hash["klass"], hash["method_name"], hash["options"]
16
+ end
17
+ end
@@ -0,0 +1,15 @@
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
data/bin/rails ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails gems
3
+ # installed from the root of your application.
4
+
5
+ ENGINE_ROOT = File.expand_path('..', __dir__)
6
+ APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
7
+
8
+ # Set up gems listed in the Gemfile.
9
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
10
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
11
+
12
+ require 'rails/all'
13
+ require 'rails/engine/commands'
data/bin/release ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env bash
2
+
3
+ VERSION=$1
4
+
5
+ printf "module CacheKeeper\n VERSION = \"$VERSION\"\nend\n" > ./lib/cache_keeper/version.rb
6
+ bundle
7
+ git add Gemfile.lock lib/cache_keeper/version.rb
8
+ git commit -m "Bump version for $VERSION"
9
+ git push
10
+ git tag v$VERSION
11
+ git push --tags
12
+ gem build cache_keeper.gemspec
13
+ gem push "cache_keeper-$VERSION.gem" --host https://rubygems.org
data/bin/test ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("../test", __dir__)
3
+
4
+ puts "Installing Ruby dependencies"
5
+ `bundle install`
6
+
7
+ puts "Preparing test database"
8
+ `cd test/dummy; ./bin/rails db:test:prepare`
9
+
10
+ require "bundler/setup"
11
+ require "rails/plugin/test"
@@ -0,0 +1,18 @@
1
+ require_relative "lib/cache_keeper/version"
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "cache_keeper"
5
+ s.version = CacheKeeper::VERSION
6
+ s.authors = ["Martin Zamuner"]
7
+ s.email = "martinzamuner@gmail.com"
8
+ s.summary = "Keep a fresh copy of any method in your cache"
9
+ s.homepage = "https://github.com/martinzamuner/cache_keeper"
10
+ s.license = "MIT"
11
+
12
+ s.required_ruby_version = ">= 2.5.0"
13
+
14
+ s.add_dependency "rails", ">= 6.1.0"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- test/*`.split("\n")
18
+ end
@@ -0,0 +1,20 @@
1
+ module CacheKeeper::Caching
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ def self.caches(*method_names, **options)
6
+ method_names.each do |method_name|
7
+ CacheKeeper.manager.handle self, method_name, options
8
+
9
+ # If the method is already defined, we need to hook it
10
+ method_added method_name
11
+ end
12
+ end
13
+
14
+ def self.method_added(method_name)
15
+ super
16
+
17
+ CacheKeeper.manager.activate_if_handling self, method_name
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ class CacheKeeper::Configuration
2
+ DEFAULT_MUST_REVALIDATE = false
3
+ DEFAULT_QUEUES = {}
4
+
5
+ def must_revalidate
6
+ return rails_config[:must_revalidate] unless rails_config[:must_revalidate].nil?
7
+
8
+ DEFAULT_MUST_REVALIDATE
9
+ end
10
+
11
+ def queues
12
+ rails_config[:queues] || DEFAULT_QUEUES
13
+ end
14
+
15
+ private
16
+
17
+ def rails_config
18
+ Rails.application.config.cache_keeper
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ class CacheKeeper::Engine < ::Rails::Engine
2
+ isolate_namespace CacheKeeper
3
+
4
+ config.cache_keeper = ActiveSupport::OrderedOptions.new
5
+ config.cache_keeper.queues = ActiveSupport::InheritableOptions.new
6
+
7
+ initializer "cache_keeper.active_job_serializer" do |app|
8
+ config.to_prepare do
9
+ Rails.application.config.active_job.custom_serializers << CacheKeeper::CachedMethodSerializer
10
+ end
11
+ end
12
+
13
+ initializer "cache_keeper.caching_methods" do |app|
14
+ ActiveSupport.on_load :action_controller do
15
+ ActionController::Base.send :include, CacheKeeper::Caching
16
+ end
17
+
18
+ ActiveSupport.on_load :active_record do
19
+ include CacheKeeper::Caching
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ class CacheKeeper::Manager
2
+ attr_accessor :cached_methods
3
+
4
+ def initialize
5
+ self.cached_methods = []
6
+ end
7
+
8
+ def find(klass, method_name)
9
+ cached_methods.find do |cached_method|
10
+ cached_method.klass == klass && cached_method.method_name == method_name
11
+ end
12
+ end
13
+
14
+ def handled?(klass, method_name)
15
+ find(klass, method_name).present?
16
+ end
17
+
18
+ def handle(klass, method_name, options)
19
+ CacheKeeper::CachedMethod.new(klass, method_name, options).tap do |cached_method|
20
+ cached_methods << cached_method
21
+ end
22
+ end
23
+
24
+ def activate_if_handling(klass, method_name)
25
+ cached_method = find(klass, method_name) or return
26
+
27
+ return unless requires_activation?(cached_method)
28
+
29
+ CacheKeeper::ReplaceMethod.replace(cached_method) do
30
+ cached_method.call(self)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def requires_activation?(cached_method)
37
+ return false if cached_method.klass.instance_methods.exclude?(cached_method.method_name) && cached_method.klass.private_instance_methods.exclude?(cached_method.method_name)
38
+ return false if cached_method.klass.private_instance_methods.include?(cached_method.alias_for_original_method)
39
+
40
+ true
41
+ end
42
+ end