garner 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.
- data/README.md +28 -3
- data/lib/garner/cache/object_identity.rb +50 -30
- data/lib/garner/version.rb +1 -1
- metadata +19 -3
data/README.md
CHANGED
@@ -24,7 +24,7 @@ To cache a value, invoke `cache` from within your API. Without any parameters it
|
|
24
24
|
``` ruby
|
25
25
|
get "/" do
|
26
26
|
cache do
|
27
|
-
{ :
|
27
|
+
{ counter: 42 }
|
28
28
|
end
|
29
29
|
end
|
30
30
|
```
|
@@ -34,7 +34,7 @@ To enable support for the date-based `If-Modified-Since` and the ETag-based `If-
|
|
34
34
|
``` ruby
|
35
35
|
get "/" do
|
36
36
|
cache_or_304 do
|
37
|
-
{ :
|
37
|
+
{ counter: 42 }
|
38
38
|
end
|
39
39
|
end
|
40
40
|
```
|
@@ -43,7 +43,7 @@ The cached value can also be bound to other models. For example, if a user has a
|
|
43
43
|
|
44
44
|
``` ruby
|
45
45
|
get "/me/address" do
|
46
|
-
cache_or_304({ :
|
46
|
+
cache_or_304({ bind: [ User, current_user.id ] }) do
|
47
47
|
current_user.address
|
48
48
|
end
|
49
49
|
end
|
@@ -144,6 +144,31 @@ Available Key Strategies
|
|
144
144
|
* `Garner::Strategies::Keys::RequestGet` inserts the value of HTTP request's GET parameters into the cache key when `:request` is present in the context.
|
145
145
|
* `Garner::Strategies::Keys::RequestPath` inserts the value of the HTTP request's path into the cache key when `:request` is present in the context.
|
146
146
|
|
147
|
+
Fetching Objects Directly from Cache
|
148
|
+
------------------------------------
|
149
|
+
|
150
|
+
Garner supports fetching objects or collections of objects directly from cache by supplying a binding or an array of bindings.
|
151
|
+
|
152
|
+
``` ruby
|
153
|
+
object_id = ...
|
154
|
+
Garner::Cache::ObjectIdentity.cache({ bind: [ Model, { id: object_id }] }) do
|
155
|
+
Model.find(object_id)
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
Various cache stores, including Memcached, support bulk read operations. The [Dalli gem](https://github.com/mperham/dalli) exposes this via the `read_multi` method. When invoked with a collection of bindings, Garner will call `read_multi` if available. This may significantly reduce the number of network roundtrips to the cache servers.
|
160
|
+
|
161
|
+
``` ruby
|
162
|
+
object_ids = [ ... ]
|
163
|
+
bindings = object_ids.map do |object_id|
|
164
|
+
{ bind: [ Model, { id: object_id }]}
|
165
|
+
end
|
166
|
+
Garner::Cache::ObjectIdentity.cache_multi(bindings) do |binding|
|
167
|
+
# the object binding is passed into the block for every cache miss
|
168
|
+
Model.find(binding[:bind][1][:id])
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
147
172
|
Configuration
|
148
173
|
-------------
|
149
174
|
|
@@ -31,7 +31,7 @@ module Garner
|
|
31
31
|
# `bind: [{ klass: Artwork }, { klass: User, object: { id: current_user.id } }]`
|
32
32
|
#
|
33
33
|
module ObjectIdentity
|
34
|
-
|
34
|
+
|
35
35
|
IDENTITY_FIELDS = [ :id ]
|
36
36
|
|
37
37
|
KEY_STRATEGIES = [
|
@@ -40,39 +40,45 @@ module Garner
|
|
40
40
|
Garner::Strategies::Keys::RequestPath,
|
41
41
|
Garner::Strategies::Keys::RequestGet
|
42
42
|
]
|
43
|
-
|
43
|
+
|
44
44
|
CACHE_STRATEGIES = [
|
45
45
|
Garner::Strategies::Cache::Expiration
|
46
46
|
]
|
47
47
|
|
48
48
|
ETAG_STRATEGY = Garner::Strategies::ETags::Grape
|
49
|
-
|
49
|
+
|
50
50
|
class << self
|
51
51
|
|
52
52
|
# cache the result of an executable block
|
53
|
-
def cache(binding = nil, context = {})
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
53
|
+
def cache(binding = nil, context = {}, &block)
|
54
|
+
cache_options = apply_cache_options(context)
|
55
|
+
ctx = key_context(context)
|
56
|
+
fetch(key(binding, ctx), nil, cache_options, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
# cache a collection of results
|
60
|
+
def cache_multi(bindings, context = {}, &block)
|
61
|
+
cache_options = apply_cache_options(context)
|
62
|
+
ctx = key_context(context)
|
63
|
+
keys = bindings.map do |binding|
|
64
|
+
key(binding, ctx)
|
65
|
+
end
|
66
|
+
# attempt to do a read_multi if the cache supports it
|
67
|
+
local_cache = keys.size > 1 && Garner.config.cache.respond_to?(:read_multi) ? Garner.config.cache.read_multi(keys) : {}
|
68
|
+
# fetch all missing values
|
69
|
+
bindings.each_with_index.map do |binding, index|
|
70
|
+
key = keys[index]
|
71
|
+
local_cache[key] || fetch(key, binding, cache_options, &block)
|
64
72
|
end
|
65
|
-
Garner.config.cache.delete(key) unless result
|
66
|
-
result
|
67
73
|
end
|
68
|
-
|
74
|
+
|
69
75
|
# invalidate an object that has been cached
|
70
76
|
def invalidate(* args)
|
71
77
|
options = index(*args)
|
72
78
|
reset_key_prefix_for(options[:klass], options[:object])
|
73
79
|
reset_key_prefix_for(options[:klass]) if options[:object]
|
74
80
|
end
|
75
|
-
|
81
|
+
|
76
82
|
# metadata for cached objects:
|
77
83
|
# :etag - Unique hash of object content
|
78
84
|
# :last_modified - Timestamp of last modification event
|
@@ -80,14 +86,28 @@ module Garner
|
|
80
86
|
key = key(binding, key_context(context))
|
81
87
|
Garner.config.cache.read(meta(key))
|
82
88
|
end
|
83
|
-
|
89
|
+
|
84
90
|
private
|
85
91
|
|
92
|
+
def fetch(key, binding, cache_options = {}, &block)
|
93
|
+
result = Garner.config.cache.fetch(key, cache_options) do
|
94
|
+
object = binding ? yield(binding) : yield
|
95
|
+
reset_cache_metadata key, object
|
96
|
+
object
|
97
|
+
end
|
98
|
+
Garner.config.cache.delete(key) unless result
|
99
|
+
result
|
100
|
+
end
|
101
|
+
|
86
102
|
# applied cache options
|
87
|
-
def
|
88
|
-
context[:cache_options] || {}
|
103
|
+
def apply_cache_options(context)
|
104
|
+
cache_options = context[:cache_options] || {}
|
105
|
+
CACHE_STRATEGIES.each do |strategy|
|
106
|
+
cache_options = strategy.apply(cache_options)
|
107
|
+
end
|
108
|
+
cache_options
|
89
109
|
end
|
90
|
-
|
110
|
+
|
91
111
|
# applied key context
|
92
112
|
def key_context(context)
|
93
113
|
new_context = {}
|
@@ -111,11 +131,11 @@ module Garner
|
|
111
131
|
meta_key = meta(key)
|
112
132
|
Garner.config.cache.write(meta_key, metadata)
|
113
133
|
end
|
114
|
-
|
134
|
+
|
115
135
|
def new_key_prefix_for(klass, object = nil)
|
116
136
|
Digest::MD5.hexdigest("#{klass}/#{object || "*"}:#{new_key_postfix}")
|
117
137
|
end
|
118
|
-
|
138
|
+
|
119
139
|
# Generate a key in the Klass/id format.
|
120
140
|
# @example Widget/id=1,Gadget/slug=forty-two,Fudget/*
|
121
141
|
def key(binding = nil, context = {})
|
@@ -131,7 +151,7 @@ module Garner
|
|
131
151
|
KEY_STRATEGIES.map { |strategy| context[strategy.field] }.compact.join("\n")
|
132
152
|
)
|
133
153
|
end
|
134
|
-
|
154
|
+
|
135
155
|
# Generate an index key from args
|
136
156
|
def index(* args)
|
137
157
|
case args[0]
|
@@ -150,7 +170,7 @@ module Garner
|
|
150
170
|
raise ArgumentError, "invalid args, must be (klass, identifier) or hash (#{args})"
|
151
171
|
end
|
152
172
|
end
|
153
|
-
|
173
|
+
|
154
174
|
def find_or_create_key_prefix_for(klass, object = nil)
|
155
175
|
Garner.config.cache.fetch(index_string_for(klass, object), {}) do
|
156
176
|
new_key_prefix_for(klass, object)
|
@@ -160,7 +180,7 @@ module Garner
|
|
160
180
|
def new_key_prefix_for(klass, object = nil)
|
161
181
|
Digest::MD5.hexdigest("#{klass}/#{object || "*"}:#{new_key_postfix}")
|
162
182
|
end
|
163
|
-
|
183
|
+
|
164
184
|
def new_key_postfix
|
165
185
|
SecureRandom.respond_to?(:uuid) ? SecureRandom.uuid : (0...16).map{ ('a'..'z').to_a[rand(26)] }.join
|
166
186
|
end
|
@@ -175,7 +195,7 @@ module Garner
|
|
175
195
|
nil
|
176
196
|
end
|
177
197
|
end
|
178
|
-
|
198
|
+
|
179
199
|
# Generate a metadata key.
|
180
200
|
def meta(key)
|
181
201
|
"#{key}:meta"
|
@@ -195,7 +215,7 @@ module Garner
|
|
195
215
|
raise ArgumentError, "invalid argument type #{ary[0].class} in :bind (#{ary[0]})"
|
196
216
|
end
|
197
217
|
end
|
198
|
-
|
218
|
+
|
199
219
|
def index_string_for(klass, object = nil)
|
200
220
|
prefix = "INDEX"
|
201
221
|
IDENTITY_FIELDS.each do |field|
|
@@ -205,7 +225,7 @@ module Garner
|
|
205
225
|
end
|
206
226
|
"#{prefix}:#{klass}/*"
|
207
227
|
end
|
208
|
-
|
228
|
+
|
209
229
|
end
|
210
230
|
end
|
211
231
|
end
|
data/lib/garner/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: garner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2013-01-
|
13
|
+
date: 2013-01-24 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -172,6 +172,22 @@ dependencies:
|
|
172
172
|
- - ~>
|
173
173
|
- !ruby/object:Gem::Version
|
174
174
|
version: '3.0'
|
175
|
+
- !ruby/object:Gem::Dependency
|
176
|
+
name: dalli
|
177
|
+
requirement: !ruby/object:Gem::Requirement
|
178
|
+
none: false
|
179
|
+
requirements:
|
180
|
+
- - ~>
|
181
|
+
- !ruby/object:Gem::Version
|
182
|
+
version: '2.6'
|
183
|
+
type: :development
|
184
|
+
prerelease: false
|
185
|
+
version_requirements: !ruby/object:Gem::Requirement
|
186
|
+
none: false
|
187
|
+
requirements:
|
188
|
+
- - ~>
|
189
|
+
- !ruby/object:Gem::Version
|
190
|
+
version: '2.6'
|
175
191
|
- !ruby/object:Gem::Dependency
|
176
192
|
name: yard
|
177
193
|
requirement: !ruby/object:Gem::Requirement
|
@@ -263,7 +279,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
263
279
|
version: '0'
|
264
280
|
segments:
|
265
281
|
- 0
|
266
|
-
hash:
|
282
|
+
hash: 4499796191639143111
|
267
283
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
268
284
|
none: false
|
269
285
|
requirements:
|