garner 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|