cacheable 1.0.3 → 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +180 -77
- data/cache-adapters.md +2 -2
- data/lib/cacheable/cache_adapter.rb +2 -2
- data/lib/cacheable/method_generator.rb +2 -1
- data/lib/cacheable/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ba379f0770988ff42a99397f056206bbbb9b60a59b51a5fee51d687263adb96e
|
4
|
+
data.tar.gz: ef68f492cf221457515452df0324ff87d634914ac67e286a4e698c096e5a2a3e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79e94e9ec720e03d2be5370ac8554c2c1635589b5cfffec5a56f27fe228944e659da1e5aa52b94836559fc06301a6f1e41d0df418c7305db925a175754ce73d8
|
7
|
+
data.tar.gz: e56db78b1855a5cb4bcba7b1f408d4c4cff9cbef03f05641596e721c0b332bfc7878769632ef2eda6e6d2476505bdaf38b477fa32abc6513170b88806d21209b
|
data/README.md
CHANGED
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
By [Splitwise](https://www.splitwise.com)
|
4
4
|
|
5
|
-
Cacheable is a gem which
|
5
|
+
Cacheable is a gem which adds method caching in Ruby following an [aspect-oriented programming (AOP)](https://en.wikipedia.org/wiki/Aspect-oriented_programming) paradigm. Its core goals are:
|
6
6
|
|
7
7
|
* ease of use (method annotation)
|
8
|
-
* flexibility (simple
|
8
|
+
* flexibility (simple adaptability for any cache backend)
|
9
9
|
* portability (plain Ruby for use with any framework)
|
10
10
|
|
11
|
-
While Rails is not a requirement, Cacheable was built inside a mature Rails app and later extracted.
|
11
|
+
While using Ruby on Rails is not a requirement, Cacheable was built inside a mature Rails app and later extracted. The current release is designed for drop-in support in Rails, and includes an adapter for an in-memory cache backed by a simple hash. This may be enough for your needs, but it's more likely that additional cache adapters will need to be written for other projects.
|
12
12
|
|
13
13
|
See more about [Cache Adapters](cache-adapters.md).
|
14
14
|
|
@@ -32,47 +32,58 @@ Cacheable.cache_adapter = :memory
|
|
32
32
|
|
33
33
|
### Simple Implementation Example
|
34
34
|
|
35
|
-
Cacheable is designed to work
|
35
|
+
Cacheable is designed to work seamlessly with your already existing codebase. Consider the following example where we fetch the star count for Cacheable from GitHub's API. Feel free to copy/paste it into your IRB console or use the code in `examples/simple_example.rb`.
|
36
36
|
|
37
37
|
```ruby
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
require 'json'
|
39
|
+
require 'net/http'
|
40
|
+
|
41
|
+
class GitHubApiAdapter
|
42
|
+
def star_count
|
43
|
+
puts 'Fetching data from GitHub'
|
44
|
+
url = 'https://api.github.com/repos/splitwise/cacheable'
|
45
|
+
|
46
|
+
JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
|
43
47
|
end
|
44
48
|
end
|
45
49
|
```
|
46
50
|
|
47
|
-
To cache this method and
|
51
|
+
To cache this method and its result, simply add the following:
|
48
52
|
|
49
53
|
```ruby
|
54
|
+
# From examples/simple_example.rb
|
55
|
+
|
50
56
|
require 'cacheable' # this may not be necessary depending on your autoloading system
|
57
|
+
require 'json'
|
58
|
+
require 'net/http'
|
51
59
|
|
52
|
-
class
|
60
|
+
class GitHubApiAdapter
|
53
61
|
include Cacheable
|
54
62
|
|
55
|
-
cacheable :
|
63
|
+
cacheable :star_count
|
64
|
+
|
65
|
+
def star_count
|
66
|
+
puts 'Fetching data from GitHub'
|
67
|
+
url = 'https://api.github.com/repos/splitwise/cacheable'
|
56
68
|
|
57
|
-
|
58
|
-
|
59
|
-
…
|
60
|
-
return 'my_result'
|
69
|
+
|
70
|
+
JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
|
61
71
|
end
|
62
72
|
end
|
63
73
|
```
|
64
74
|
|
65
|
-
**
|
75
|
+
**That's it!** There's some complex Ruby magic going on under the hood but to the end user you can simply call `star_count` and the result will be retrieved from the cache, if available, or fetched from the network and placed into the cache. To confirm it is working, fire up an IRB console try the following:
|
66
76
|
|
67
77
|
```irb
|
68
|
-
>
|
69
|
-
>
|
70
|
-
|
71
|
-
=>
|
72
|
-
>
|
73
|
-
=>
|
74
|
-
|
75
|
-
# Notice that
|
78
|
+
> a = GitHubApiAdapter.new
|
79
|
+
> a.star_count
|
80
|
+
Fetching data from GitHub
|
81
|
+
=> 19
|
82
|
+
> a.star_count
|
83
|
+
=> 19
|
84
|
+
|
85
|
+
# Notice that "Fetching data from GitHub" was not output the 2nd time the method was invoked.
|
86
|
+
# The network call and result parsing would also not be performed again.
|
76
87
|
```
|
77
88
|
|
78
89
|
### Additional Methods
|
@@ -81,44 +92,46 @@ Cacheable also adds two useful methods to your class.
|
|
81
92
|
|
82
93
|
#### Skip the Cache via `#{method}_without_cache`
|
83
94
|
|
84
|
-
The cache can intentionally be skipped by appending `_without_cache` to the method name. This invocation
|
95
|
+
The cache can intentionally be skipped by appending `_without_cache` to the method name. This invocation will neither check the cache nor populate it. It is as if you called the original method and never used Cacheable.
|
85
96
|
|
86
97
|
```irb
|
87
|
-
>
|
88
|
-
>
|
89
|
-
|
90
|
-
=>
|
91
|
-
>
|
92
|
-
|
93
|
-
=>
|
94
|
-
|
98
|
+
> a = GitHubApiAdapter.new
|
99
|
+
> a.star_count
|
100
|
+
Fetching data from GitHub
|
101
|
+
=> 19
|
102
|
+
> a.star_count_without_cache
|
103
|
+
Fetching data from GitHub
|
104
|
+
=> 19
|
105
|
+
> a.star_count
|
106
|
+
=> 19
|
107
|
+
```
|
95
108
|
|
96
109
|
#### Remove the Value via `clear_#{method}_cache`
|
97
110
|
|
98
|
-
The
|
111
|
+
The cached value can be cleared at any time by calling `clear_#{your_method_name}_cache`.
|
99
112
|
|
100
113
|
```irb
|
101
|
-
>
|
102
|
-
>
|
103
|
-
|
104
|
-
=>
|
105
|
-
>
|
106
|
-
=>
|
107
|
-
|
108
|
-
>
|
114
|
+
> a = GitHubApiAdapter.new
|
115
|
+
> a.star_count
|
116
|
+
Fetching data from GitHub
|
117
|
+
=> 19
|
118
|
+
> a.star_count
|
119
|
+
=> 19
|
120
|
+
|
121
|
+
> a.clear_star_count_cache
|
109
122
|
=> true
|
110
|
-
>
|
111
|
-
|
112
|
-
=>
|
123
|
+
> a.star_count
|
124
|
+
Fetching data from GitHub
|
125
|
+
=> 19
|
113
126
|
```
|
114
127
|
|
115
128
|
## Additional Configuration
|
116
129
|
|
117
|
-
### Cache
|
130
|
+
### Cache Keys
|
118
131
|
|
119
132
|
#### Default
|
120
133
|
|
121
|
-
|
134
|
+
By default, Cacheable will construct key a key in the format `[cache_key || class_name, method_name]` without using method arguments.
|
122
135
|
|
123
136
|
If the object responds to `cache_key` its return value will be the first element in the array. `ActiveRecord` provides [`cache_key`](https://api.rubyonrails.org/classes/ActiveRecord/Integration.html#method-i-cache_key) but it can be added to any Ruby object or overwritten. If the object does not respond to it, the name of the class will be used instead. The second element will be the name of the method as a symbol.
|
124
137
|
|
@@ -126,55 +139,109 @@ It is up to the cache adapter what to do with this array. For example, Rails wil
|
|
126
139
|
|
127
140
|
#### Set Your Own
|
128
141
|
|
129
|
-
If (re)defining `cache_key` does not provide enough flexibility you can pass a proc to the `key_format:` option of `cacheable`.
|
142
|
+
If (re)defining `cache_key` does not provide enough flexibility, you can pass a proc to the `key_format:` option of `cacheable`.
|
130
143
|
|
131
144
|
```ruby
|
132
|
-
|
145
|
+
# From examples/custom_key_example.rb
|
146
|
+
|
147
|
+
require 'cacheable'
|
148
|
+
require 'json'
|
149
|
+
require 'net/http'
|
150
|
+
|
151
|
+
class GitHubApiAdapter
|
133
152
|
include Cacheable
|
134
153
|
|
135
|
-
cacheable :
|
136
|
-
|
137
|
-
"#{method_name} called on #{target} with #{args}"
|
154
|
+
cacheable :star_count, key_format: ->(target, method_name, method_args) do
|
155
|
+
[target.class, method_name, method_args.first, Time.now.strftime('%Y-%m-%d')].join('/')
|
138
156
|
end
|
139
157
|
|
140
|
-
def
|
141
|
-
|
158
|
+
def star_count(repo)
|
159
|
+
puts "Fetching data from GitHub for #{repo}"
|
160
|
+
url = "https://api.github.com/repos/splitwise/#{repo}"
|
161
|
+
|
162
|
+
JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
|
142
163
|
end
|
143
164
|
end
|
144
165
|
```
|
145
166
|
|
146
|
-
* `target` is the object the method is being called on (`#<
|
147
|
-
* `method_name` is the name of the method being cached (`:
|
148
|
-
* `method_args` is an array of arguments being passed to the method (`[
|
167
|
+
* `target` is the object the method is being called on (`#<GitHubApiAdapter:0x0…0>`)
|
168
|
+
* `method_name` is the name of the method being cached (`:star_count`)
|
169
|
+
* `method_args` is an array of arguments being passed to the method (`[params]`)
|
149
170
|
|
150
|
-
|
171
|
+
Including the method argument(s) allows you to cache different calls to the same method. Without the arguments in the cache key, a call to `star_count('cacheable')` would populate the cache and `star_count('tokenautocomplete')` would return the number of stars for Cacheable instead of what you want.
|
151
172
|
|
152
|
-
|
173
|
+
In addition, we're including the current date in the cache key so calling this method tomorrow will return an updated value.
|
153
174
|
|
154
|
-
|
175
|
+
```irb
|
176
|
+
> a = GitHubApiAdapter.new
|
177
|
+
> a.star_count('cacheable')
|
178
|
+
Fetching data from GitHub for cacheable
|
179
|
+
=> 19
|
180
|
+
> a.star_count('cacheable')
|
181
|
+
=> 19
|
182
|
+
> a.star_count('tokenautocomplete')
|
183
|
+
Fetching data from GitHub for tokenautocomplete
|
184
|
+
=> 1164
|
185
|
+
> a.star_count('tokenautocomplete')
|
186
|
+
=> 1164
|
187
|
+
|
188
|
+
# In this example the follow cache keys are generated:
|
189
|
+
# GitHubApiAdapter/star_count/cacheable/2018-09-21
|
190
|
+
# GitHubApiAdapter/star_count/tokenautocomplete/2018-09-21
|
191
|
+
```
|
155
192
|
|
156
|
-
|
193
|
+
### Conditional Caching
|
157
194
|
|
195
|
+
You can control if a method should be cached by supplying a proc to the `unless:` option which will get the same arguments as `key_format:`. This logic can be defined in a method on the class and the name of the method as a symbol can be passed as well. **Note**: When using a symbol, the first argument, `target`, will not be passed but will be available as `self`.
|
158
196
|
|
159
197
|
```ruby
|
160
|
-
|
198
|
+
# From examples/conditional_example.rb
|
199
|
+
|
200
|
+
require 'cacheable'
|
201
|
+
require 'json'
|
202
|
+
require 'net/http'
|
203
|
+
|
204
|
+
class GitHubApiAdapter
|
161
205
|
include Cacheable
|
162
206
|
|
163
|
-
cacheable :
|
207
|
+
cacheable :star_count, unless: :growing_fast?, key_format: ->(target, method_name, method_args) do
|
208
|
+
[target.class, method_name, method_args.first].join('/')
|
209
|
+
end
|
210
|
+
|
211
|
+
def star_count(repo)
|
212
|
+
puts "Fetching data from GitHub for #{repo}"
|
213
|
+
url = "https://api.github.com/repos/splitwise/#{repo}"
|
164
214
|
|
165
|
-
|
166
|
-
…
|
215
|
+
JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
|
167
216
|
end
|
168
217
|
|
169
|
-
def
|
170
|
-
method_args.first ==
|
218
|
+
def growing_fast?(_method_name, method_args)
|
219
|
+
method_args.first == 'cacheable'
|
171
220
|
end
|
172
221
|
end
|
173
222
|
```
|
174
223
|
|
224
|
+
Cacheable is new so we don't want to cache the number of stars it has as we expect it to change quickly.
|
225
|
+
|
226
|
+
```irb
|
227
|
+
> a = GitHubApiAdapter.new
|
228
|
+
> a.star_count('tokenautocomplete')
|
229
|
+
Fetching data from GitHub for tokenautocomplete
|
230
|
+
=> 1164
|
231
|
+
a.star_count('tokenautocomplete')
|
232
|
+
=> 1164
|
233
|
+
|
234
|
+
> a.star_count('cacheable')
|
235
|
+
Fetching data from GitHub for cacheable
|
236
|
+
=> 19
|
237
|
+
> a.star_count('cacheable')
|
238
|
+
Fetching data from GitHub for cacheable
|
239
|
+
=> 19
|
240
|
+
```
|
241
|
+
|
175
242
|
### Cache Options
|
176
243
|
|
177
|
-
If your cache backend supports options you can pass them as the `cache_options:` option. This will be passed
|
244
|
+
If your cache backend supports options, you can pass them as the `cache_options:` option. This will be passed through untouched to the cache's `fetch` method.
|
178
245
|
|
179
246
|
```ruby
|
180
247
|
cacheable :with_options, cache_options: {expires_in: 3_600}
|
@@ -189,28 +256,64 @@ cacheable :these, :methods, :share, :options, key_format: key_proc, unless: unle
|
|
189
256
|
cacheable :this_method_has_its_own_options, unless: unless_proc2
|
190
257
|
```
|
191
258
|
|
192
|
-
### Class Method
|
259
|
+
### Class Method Caching
|
193
260
|
|
194
|
-
You can cache class methods
|
261
|
+
You can cache static (class) methods as well by including Cacheable in your class' [eigenclass](https://en.wikipedia.org/wiki/Metaclass#In_Ruby). This is because all Ruby classes are instances of the `Class` class. Understanding how Ruby's class structure works is powerful and useful, however, further explanation is beyond the scope of this README and not necessary to proceed.
|
262
|
+
|
263
|
+
Simply put `include Cacheable` and the `cacheable` directive within a `class << self` block as in the example below. The methods you want to cache can be defined in this block or outside using the `def self.#{method_name}` syntax.
|
195
264
|
|
196
265
|
```ruby
|
197
|
-
|
266
|
+
# From examples/class_method_example.rb
|
267
|
+
|
268
|
+
require 'cacheable'
|
269
|
+
require 'json'
|
270
|
+
require 'net/http'
|
271
|
+
|
272
|
+
class GitHubApiAdapter
|
198
273
|
class << self
|
199
274
|
include Cacheable
|
200
275
|
|
201
|
-
cacheable :
|
276
|
+
cacheable :star_count_for_cacheable, :star_count_for_tokenautocomplete
|
277
|
+
|
278
|
+
def star_count_for_cacheable
|
279
|
+
star_count('cacheable')
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
202
283
|
|
203
|
-
def
|
204
|
-
puts
|
284
|
+
def star_count(repo)
|
285
|
+
puts "Fetching data from GitHub for #{repo}"
|
286
|
+
url = "https://api.github.com/repos/splitwise/#{repo}"
|
287
|
+
|
288
|
+
JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
|
205
289
|
end
|
206
290
|
end
|
207
291
|
|
208
|
-
def self.
|
209
|
-
|
292
|
+
def self.star_count_for_tokenautocomplete
|
293
|
+
star_count('tokenautocomplete')
|
210
294
|
end
|
211
295
|
end
|
212
296
|
```
|
213
297
|
|
298
|
+
```irb
|
299
|
+
> GitHubApiAdapter.star_count_for_cacheable
|
300
|
+
Fetching data from GitHub for cacheable
|
301
|
+
=> 19
|
302
|
+
> GitHubApiAdapter.star_count_for_cacheable
|
303
|
+
=> 19
|
304
|
+
|
305
|
+
> GitHubApiAdapter.star_count_for_tokenautocomplete
|
306
|
+
Fetching data from GitHub for tokenautocomplete
|
307
|
+
=> 1164
|
308
|
+
> GitHubApiAdapter.star_count_for_tokenautocomplete
|
309
|
+
=> 1164
|
310
|
+
```
|
311
|
+
|
312
|
+
### Other Notes / Frequently Asked Questions
|
313
|
+
|
314
|
+
- Q: How does Cacheable handle cache invalidation?
|
315
|
+
- A: Cacheable takes Rails' cue and sidesteps the difficult problem of cache invalidation in favor of [key-based expiration](https://signalvnoise.com/posts/3113-how-key-based-cache-expiration-works). As DHH mentions in the blog post, `ActiveRecord`'s `cache_key` uses the `updated_at` timestamp so the cache is recalculated as the object changes. This results in new cache values being calculated, and your cache implementation can be configured to expire least recently used (LRU) values. In other applications, care must be taken to include a mechanism of key-based expiration in the `cache_key` method or [`key_format` proc](#set-your-own) or you risk serving stale data. Alternatively the generated [cache clearing](#remove-the-value-via-clear_method_cache) method can be used to explicitly invalidate the cache.
|
316
|
+
|
214
317
|
### Contributors (alphabetical by last name)
|
215
318
|
|
216
319
|
* [Jess Hottenstein](https://github.com/jhottenstein)
|
data/cache-adapters.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
A cache adapter is an object that Cacheable can use as an interface to your system's cache. Cacheable will work out of the box using the object returned by `Rails.cache` as a cache adapter.
|
4
4
|
|
5
|
-
The other adapter provided with the library is the [Memory Adapter](lib/cacheable/cache_adapters/memory_adapter.rb). Is a simple memoizing cache used in testing. It is little more than an object that conforms to the protocol and is backed by a Ruby Hash. When
|
5
|
+
The other adapter provided with the library is the [Memory Adapter](lib/cacheable/cache_adapters/memory_adapter.rb). Is a simple memoizing cache used in testing. It is little more than an object that conforms to the protocol and is backed by a Ruby Hash. When writing a new cache adapter it can be used as a template.
|
6
6
|
|
7
7
|
### Protocol
|
8
8
|
|
@@ -16,7 +16,7 @@ There are only two methods the cache adapter protocol requires.
|
|
16
16
|
|
17
17
|
#### `delete(key)`
|
18
18
|
|
19
|
-
`delete` takes a key and removes it's associated value in the cache. While not currently
|
19
|
+
`delete` takes a key and removes it's associated value in the cache. While not currently depended on by Cacheable, it appears the standard is to return `true` if the value was present and removed and `false` if not present to begin with.
|
20
20
|
|
21
21
|
#### Additional useful methods
|
22
22
|
|
@@ -15,12 +15,12 @@ module Cacheable
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def cache_adapter=(name_or_adapter)
|
18
|
-
@_cache_adapter =
|
18
|
+
@_cache_adapter = interpret_adapter(name_or_adapter)
|
19
19
|
end
|
20
20
|
|
21
21
|
private
|
22
22
|
|
23
|
-
def
|
23
|
+
def interpret_adapter(name_or_adapter)
|
24
24
|
return name_or_adapter if cache_adapter?(name_or_adapter)
|
25
25
|
|
26
26
|
unless [Symbol, String].include?(name_or_adapter.class)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'English'
|
3
4
|
|
4
5
|
module Cacheable
|
@@ -12,7 +13,7 @@ module Cacheable
|
|
12
13
|
private
|
13
14
|
|
14
15
|
def method_interceptor_module_name
|
15
|
-
class_name = name || to_s.gsub(/[^a-zA-Z_0-9]/, '')
|
16
|
+
class_name = name&.gsub(/:/, '') || to_s.gsub(/[^a-zA-Z_0-9]/, '')
|
16
17
|
"#{class_name}Cacher"
|
17
18
|
end
|
18
19
|
|
data/lib/cacheable/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cacheable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jess Hottenstein
|
@@ -12,7 +12,7 @@ bindir: bin
|
|
12
12
|
cert_chain: []
|
13
13
|
date: 2018-07-31 00:00:00.000000000 Z
|
14
14
|
dependencies: []
|
15
|
-
description: Add caching simply without modifying your existing code.
|
15
|
+
description: Add caching simply without modifying your existing code. Includes configurable
|
16
16
|
options for simple cache invalidation. See README on github for more information.
|
17
17
|
email: support@splitwise.com
|
18
18
|
executables: []
|
@@ -39,7 +39,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
39
|
requirements:
|
40
40
|
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 2.
|
42
|
+
version: 2.3.0
|
43
43
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - ">="
|
@@ -47,7 +47,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
47
47
|
version: '0'
|
48
48
|
requirements: []
|
49
49
|
rubyforge_project:
|
50
|
-
rubygems_version: 2.7.
|
50
|
+
rubygems_version: 2.7.8
|
51
51
|
signing_key:
|
52
52
|
specification_version: 4
|
53
53
|
summary: Add caching to any Ruby method in a aspect orientated programming approach.
|