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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc1692072ddcd1c2c657224d22f14b9705a36fc341753afa37ffa49d0f0baa0e
4
- data.tar.gz: 27dbec107256dd9e0468635cf6121ee10c139a8424cb07d9af7ddb2abd6cc77d
3
+ metadata.gz: ba379f0770988ff42a99397f056206bbbb9b60a59b51a5fee51d687263adb96e
4
+ data.tar.gz: ef68f492cf221457515452df0324ff87d634914ac67e286a4e698c096e5a2a3e
5
5
  SHA512:
6
- metadata.gz: e7b0757cd1a849bc0be1801d166676bb0856646494261251d6d9ddf5a3171f6b3e920039b53cf11afa5553f6778dc2aa31182d8d2752eb765aed841b0cc30c68
7
- data.tar.gz: a9225a74404e0b0a4b1a2cb646bb6ff5e2b6fe88a002ad0060312bc688b5124f5f08f729abb811414e18dd45874d00a67037cf28c70f49570616d8437565b1c0
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 intends to adds method caching in an [aspect-oriented programming (AOP)](https://en.wikipedia.org/wiki/Aspect-oriented_programming) fashion in Ruby. Its core goals are:
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 adaptablability for any cache backend)
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. This first release will seemlyless work in Rails and only includes an adapter for an in-memory cache backed by a simple Hash. This may be enough for you needs but it is more likely that additional cache adapters will need to be written.
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 seemlessly with your already existings codebase. Consider the following contrived class:
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
- class SimpleExample
39
- def expensive_calculation
40
- puts 'beginning expensive method'
41
-
42
- return 'my_result'
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 it's result, simply add the following:
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 SimpleExample
60
+ class GitHubApiAdapter
53
61
  include Cacheable
54
62
 
55
- cacheable :expensive_calculation
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
- def expensive_calculation
58
- puts 'beginning expensive method'
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
- **Thats it!** There's some complex Ruby magic going on under the hood but to the end user you can simply call `expensive_calculation` and the result will be retreived from the cache, if available, or generated and placed into the cache. To confirm it is working, fire up an IRB console try the following:
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
- > s = SimpleExample.new
69
- > s.expensive_calculation
70
- beginning expensive method
71
- => "my_result"
72
- > s.expensive_calculation
73
- => "my_result"
74
-
75
- # Notice that the `puts` was not output the 2nd time the method was invoked.
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 with neither check the cache nor populate it as if you called the original method and never used Cacheable.
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
- > s = SimpleExample.new
88
- > s.expensive_calculation_without_cache
89
- beginning expensive method
90
- => "my_result"
91
- > s.expensive_calculation_without_cache
92
- beginning expensive method
93
- => "my_result"
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 cache can be cleared at any time by calling `clear_#{your_method_name}_cache`.
111
+ The cached value can be cleared at any time by calling `clear_#{your_method_name}_cache`.
99
112
 
100
113
  ```irb
101
- > s = SimpleExample.new
102
- > s.expensive_calculation
103
- beginning expensive method
104
- => "my_result"
105
- > s.expensive_calculation
106
- => "my_result"
107
-
108
- > s.clear_expensive_calculation_cache
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
- > s.expensive_calculation
111
- beginning expensive method
112
- => "my_result"
123
+ > a.star_count
124
+ Fetching data from GitHub
125
+ => 19
113
126
  ```
114
127
 
115
128
  ## Additional Configuration
116
129
 
117
- ### Cache Invalidation
130
+ ### Cache Keys
118
131
 
119
132
  #### Default
120
133
 
121
- One of the hardest things to do correctly is cache invalidation. Cacheable handles this in a variety of ways. By default Cacheable will construct key a key in the format `[cache_key || class_name, method_name]`.
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
- class CustomKeyExample
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 :my_method, key_format: -> (target, method_name, method_args) do
136
- args = method_args.collect { |argument| "#{argument.class}::#{argument}" }.join
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 my_method(arg1)
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 (`#<CustomKeyExample:0x0…0>`)
147
- * `method_name` is the name of the method being cached (`:my_method`)
148
- * `method_args` is an array of arguments being passed to the method (`[arg1]`)
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
- So if we called `CustomKeyExample.new.my_method(123)` we would get the cache key
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
- `"my_method called on #<CustomKeyExample:0x0…0> with Integer::123"`.
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
- ### Conditional Cacheing
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
- 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:`. Alternatively this method can be defined on the class and a symbol of the name of the method can be passed. **Note**: When using a symbol, the first argument will not be passed but will be available in the method as `self`. The following example will not cache the value if the first argument to the method is `false`.
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
- class ConditionalCachingExample
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 :maybe_cache, unless: :should_not_cache?
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
- def maybe_cache(cache)
166
-
215
+ JSON.parse(Net::HTTP.get(URI.parse(url)))['stargazers_count']
167
216
  end
168
217
 
169
- def should_not_cache?(_method_name, method_args)
170
- method_args.first == false
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 though untouched to the cache's `fetch` method.
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 Cacheing
259
+ ### Class Method Caching
193
260
 
194
- You can cache class methods just as easily as a Ruby class is just an instance of `Class`. You simply need to `include Cacheable` within the `class << self` block. Methods can be defined in this block or outside using the `def self.` syntax.
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
- class StaticMethodExample
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 :class_method, :self_class_method
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 class_method
204
- puts 'class_method called'
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.self_class_method
209
- puts 'self_class_method called'
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)
@@ -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 writting a new cache adapter it can be used as a template.
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 dependend 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.
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 = interprete_adapter(name_or_adapter)
18
+ @_cache_adapter = interpret_adapter(name_or_adapter)
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- def interprete_adapter(name_or_adapter)
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
 
@@ -4,7 +4,7 @@ module Cacheable
4
4
  module VERSION
5
5
  MAJOR = 1
6
6
  MINOR = 0
7
- TINY = 3
7
+ TINY = 4
8
8
  PRE = nil
9
9
 
10
10
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.').freeze
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.3
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. Inlcudes configurable
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.0.0
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.6
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.