garner 0.3.3 → 0.4.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/LICENSE.md +1 -1
- data/README.md +116 -135
- data/lib/garner/cache/binding.rb +58 -0
- data/lib/garner/cache/context.rb +27 -0
- data/lib/garner/cache/identity.rb +45 -0
- data/lib/garner/cache.rb +41 -0
- data/lib/garner/config.rb +46 -15
- data/lib/garner/mixins/mongoid/document.rb +75 -0
- data/lib/garner/mixins/mongoid/identity.rb +106 -0
- data/lib/garner/mixins/mongoid.rb +4 -0
- data/lib/garner/mixins/rack.rb +45 -0
- data/lib/garner/strategies/binding/invalidation/base.rb +26 -0
- data/lib/garner/strategies/binding/invalidation/touch.rb +27 -0
- data/lib/garner/strategies/binding/key/base.rb +19 -0
- data/lib/garner/strategies/binding/key/cache_key.rb +19 -0
- data/lib/garner/strategies/binding/key/safe_cache_key.rb +33 -0
- data/lib/garner/strategies/context/key/base.rb +21 -0
- data/lib/garner/strategies/context/key/caller.rb +83 -0
- data/lib/garner/strategies/context/key/jsonp.rb +30 -0
- data/lib/garner/strategies/context/key/request_get.rb +30 -0
- data/lib/garner/strategies/context/key/request_path.rb +28 -0
- data/lib/garner/strategies/context/key/request_post.rb +30 -0
- data/lib/garner/version.rb +1 -1
- data/lib/garner.rb +29 -26
- metadata +122 -22
- data/lib/garner/cache/object_identity.rb +0 -249
- data/lib/garner/middleware/base.rb +0 -47
- data/lib/garner/middleware/cache/bust.rb +0 -20
- data/lib/garner/mixins/grape_cache.rb +0 -111
- data/lib/garner/mixins/mongoid_document.rb +0 -58
- data/lib/garner/strategies/cache/expiration_strategy.rb +0 -16
- data/lib/garner/strategies/etags/grape_strategy.rb +0 -32
- data/lib/garner/strategies/etags/marshal_strategy.rb +0 -16
- data/lib/garner/strategies/keys/caller_strategy.rb +0 -38
- data/lib/garner/strategies/keys/jsonp_strategy.rb +0 -24
- data/lib/garner/strategies/keys/key_strategy.rb +0 -21
- data/lib/garner/strategies/keys/request_get_strategy.rb +0 -24
- data/lib/garner/strategies/keys/request_path_strategy.rb +0 -21
- data/lib/garner/strategies/keys/request_post_strategy.rb +0 -24
- data/lib/garner/strategies/keys/version_strategy.rb +0 -29
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.4.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-06-
|
13
|
+
date: 2013-06-17 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -108,6 +108,22 @@ dependencies:
|
|
108
108
|
- - ! '>='
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 0.2.0
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sinatra
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ! '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
none: false
|
123
|
+
requirements:
|
124
|
+
- - ! '>='
|
125
|
+
- !ruby/object:Gem::Version
|
126
|
+
version: '0'
|
111
127
|
- !ruby/object:Gem::Dependency
|
112
128
|
name: rack-test
|
113
129
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,6 +188,22 @@ dependencies:
|
|
172
188
|
- - ! '>='
|
173
189
|
- !ruby/object:Gem::Version
|
174
190
|
version: 3.0.0
|
191
|
+
- !ruby/object:Gem::Dependency
|
192
|
+
name: mongoid_slug
|
193
|
+
requirement: !ruby/object:Gem::Requirement
|
194
|
+
none: false
|
195
|
+
requirements:
|
196
|
+
- - ! '>='
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: 1.0.0
|
199
|
+
type: :development
|
200
|
+
prerelease: false
|
201
|
+
version_requirements: !ruby/object:Gem::Requirement
|
202
|
+
none: false
|
203
|
+
requirements:
|
204
|
+
- - ! '>='
|
205
|
+
- !ruby/object:Gem::Version
|
206
|
+
version: 1.0.0
|
175
207
|
- !ruby/object:Gem::Dependency
|
176
208
|
name: dalli
|
177
209
|
requirement: !ruby/object:Gem::Requirement
|
@@ -188,6 +220,70 @@ dependencies:
|
|
188
220
|
- - ! '>='
|
189
221
|
- !ruby/object:Gem::Version
|
190
222
|
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: activerecord
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
none: false
|
227
|
+
requirements:
|
228
|
+
- - ! '>='
|
229
|
+
- !ruby/object:Gem::Version
|
230
|
+
version: '0'
|
231
|
+
type: :development
|
232
|
+
prerelease: false
|
233
|
+
version_requirements: !ruby/object:Gem::Requirement
|
234
|
+
none: false
|
235
|
+
requirements:
|
236
|
+
- - ! '>='
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: '0'
|
239
|
+
- !ruby/object:Gem::Dependency
|
240
|
+
name: sqlite3
|
241
|
+
requirement: !ruby/object:Gem::Requirement
|
242
|
+
none: false
|
243
|
+
requirements:
|
244
|
+
- - ! '>='
|
245
|
+
- !ruby/object:Gem::Version
|
246
|
+
version: '0'
|
247
|
+
type: :development
|
248
|
+
prerelease: false
|
249
|
+
version_requirements: !ruby/object:Gem::Requirement
|
250
|
+
none: false
|
251
|
+
requirements:
|
252
|
+
- - ! '>='
|
253
|
+
- !ruby/object:Gem::Version
|
254
|
+
version: '0'
|
255
|
+
- !ruby/object:Gem::Dependency
|
256
|
+
name: coveralls
|
257
|
+
requirement: !ruby/object:Gem::Requirement
|
258
|
+
none: false
|
259
|
+
requirements:
|
260
|
+
- - ! '>='
|
261
|
+
- !ruby/object:Gem::Version
|
262
|
+
version: '0'
|
263
|
+
type: :development
|
264
|
+
prerelease: false
|
265
|
+
version_requirements: !ruby/object:Gem::Requirement
|
266
|
+
none: false
|
267
|
+
requirements:
|
268
|
+
- - ! '>='
|
269
|
+
- !ruby/object:Gem::Version
|
270
|
+
version: '0'
|
271
|
+
- !ruby/object:Gem::Dependency
|
272
|
+
name: pry
|
273
|
+
requirement: !ruby/object:Gem::Requirement
|
274
|
+
none: false
|
275
|
+
requirements:
|
276
|
+
- - ! '>='
|
277
|
+
- !ruby/object:Gem::Version
|
278
|
+
version: '0'
|
279
|
+
type: :development
|
280
|
+
prerelease: false
|
281
|
+
version_requirements: !ruby/object:Gem::Requirement
|
282
|
+
none: false
|
283
|
+
requirements:
|
284
|
+
- - ! '>='
|
285
|
+
- !ruby/object:Gem::Version
|
286
|
+
version: '0'
|
191
287
|
- !ruby/object:Gem::Dependency
|
192
288
|
name: yard
|
193
289
|
requirement: !ruby/object:Gem::Requirement
|
@@ -236,8 +332,8 @@ dependencies:
|
|
236
332
|
- - ! '>='
|
237
333
|
- !ruby/object:Gem::Version
|
238
334
|
version: '0'
|
239
|
-
description: Garner is a
|
240
|
-
|
335
|
+
description: Garner is a cache layer for Ruby and Rack applications, supporting model
|
336
|
+
and instance binding and hierarchical invalidation.
|
241
337
|
email: dblock@dblock.org
|
242
338
|
executables: []
|
243
339
|
extensions: []
|
@@ -246,22 +342,26 @@ extra_rdoc_files:
|
|
246
342
|
- README.md
|
247
343
|
files:
|
248
344
|
- lib/garner.rb
|
249
|
-
- lib/garner/cache
|
345
|
+
- lib/garner/cache.rb
|
346
|
+
- lib/garner/cache/binding.rb
|
347
|
+
- lib/garner/cache/context.rb
|
348
|
+
- lib/garner/cache/identity.rb
|
250
349
|
- lib/garner/config.rb
|
251
|
-
- lib/garner/
|
252
|
-
- lib/garner/
|
253
|
-
- lib/garner/mixins/
|
254
|
-
- lib/garner/mixins/
|
255
|
-
- lib/garner/strategies/
|
256
|
-
- lib/garner/strategies/
|
257
|
-
- lib/garner/strategies/
|
258
|
-
- lib/garner/strategies/
|
259
|
-
- lib/garner/strategies/
|
260
|
-
- lib/garner/strategies/
|
261
|
-
- lib/garner/strategies/
|
262
|
-
- lib/garner/strategies/
|
263
|
-
- lib/garner/strategies/
|
264
|
-
- lib/garner/strategies/
|
350
|
+
- lib/garner/mixins/mongoid.rb
|
351
|
+
- lib/garner/mixins/mongoid/document.rb
|
352
|
+
- lib/garner/mixins/mongoid/identity.rb
|
353
|
+
- lib/garner/mixins/rack.rb
|
354
|
+
- lib/garner/strategies/binding/invalidation/base.rb
|
355
|
+
- lib/garner/strategies/binding/invalidation/touch.rb
|
356
|
+
- lib/garner/strategies/binding/key/base.rb
|
357
|
+
- lib/garner/strategies/binding/key/cache_key.rb
|
358
|
+
- lib/garner/strategies/binding/key/safe_cache_key.rb
|
359
|
+
- lib/garner/strategies/context/key/base.rb
|
360
|
+
- lib/garner/strategies/context/key/caller.rb
|
361
|
+
- lib/garner/strategies/context/key/jsonp.rb
|
362
|
+
- lib/garner/strategies/context/key/request_get.rb
|
363
|
+
- lib/garner/strategies/context/key/request_path.rb
|
364
|
+
- lib/garner/strategies/context/key/request_post.rb
|
265
365
|
- lib/garner/version.rb
|
266
366
|
- LICENSE.md
|
267
367
|
- README.md
|
@@ -280,7 +380,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
280
380
|
version: '0'
|
281
381
|
segments:
|
282
382
|
- 0
|
283
|
-
hash:
|
383
|
+
hash: 1734950059228816164
|
284
384
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
285
385
|
none: false
|
286
386
|
requirements:
|
@@ -292,6 +392,6 @@ rubyforge_project:
|
|
292
392
|
rubygems_version: 1.8.24
|
293
393
|
signing_key:
|
294
394
|
specification_version: 3
|
295
|
-
summary: Garner is a
|
296
|
-
|
395
|
+
summary: Garner is a cache layer for Ruby and Rack applications, supporting model
|
396
|
+
and instance binding and hierarchical invalidation.
|
297
397
|
test_files: []
|
@@ -1,249 +0,0 @@
|
|
1
|
-
module Garner
|
2
|
-
module Cache
|
3
|
-
#
|
4
|
-
# A cache that uses an object identity binding strategy.
|
5
|
-
#
|
6
|
-
# Allows some flexibility in how caller binds objects in cache.
|
7
|
-
# The binding can be an object, class, array of objects, or array of classes
|
8
|
-
# on which to bind the validity of the cached result contained in the subsequent
|
9
|
-
# block.
|
10
|
-
#
|
11
|
-
# @example `bind: { klass: Widget, object: { id: params[:id] } }` will cause a cached instance to be
|
12
|
-
# invalidated on any change to the `Widget` object whose slug attribute equals `params[:id]`
|
13
|
-
#
|
14
|
-
# @example `bind: { klass: User, object: { id: current_user.id } }` will cause a cached instance to be
|
15
|
-
# invalidated on any change to the `User` object whose id attribute equals current_user.id.
|
16
|
-
# This is one way to bind a cache result to any change in the current user.
|
17
|
-
#
|
18
|
-
# @example `bind: { klass: Widget }` will cause the cached instance to be invalidated on any change to
|
19
|
-
# any object of class Widget. This is the appropriate strategy for index paths like /widgets.
|
20
|
-
#
|
21
|
-
# @example `bind: [{ klass: Widget }, { klass: User, object: { id: current_user.id } }]` will cause a
|
22
|
-
# cached instance to be invalidated on any change to either the current user, or any object of class Widget.
|
23
|
-
#
|
24
|
-
# @example `bind: [Artwork]` is shorthand for `bind: { klass: Artwork }`
|
25
|
-
#
|
26
|
-
# @example `bind: [Artwork, params[:id]]` is shorthand for `bind: { klass: Artwork, object: { id: params[:id] } }`
|
27
|
-
#
|
28
|
-
# @example `bind: [User, { id: current_user.id }] is shorthand for `bind: { klass: User, object: { id: current_user.id } }`
|
29
|
-
#
|
30
|
-
# @example `bind: [[Artwork], [User, { id: current_user.id }]]` is shorthand for
|
31
|
-
# `bind: [{ klass: Artwork }, { klass: User, object: { id: current_user.id } }]`
|
32
|
-
#
|
33
|
-
module ObjectIdentity
|
34
|
-
|
35
|
-
IDENTITY_FIELDS = [ :id ]
|
36
|
-
|
37
|
-
KEY_STRATEGIES = [
|
38
|
-
Garner::Strategies::Keys::Caller,
|
39
|
-
Garner::Strategies::Keys::Version,
|
40
|
-
Garner::Strategies::Keys::RequestPath,
|
41
|
-
Garner::Strategies::Keys::RequestGet,
|
42
|
-
Garner::Strategies::Keys::RequestPost
|
43
|
-
]
|
44
|
-
|
45
|
-
CACHE_STRATEGIES = [
|
46
|
-
Garner::Strategies::Cache::Expiration
|
47
|
-
]
|
48
|
-
|
49
|
-
ETAG_STRATEGY = Garner::Strategies::ETags::Grape
|
50
|
-
|
51
|
-
class << self
|
52
|
-
|
53
|
-
# cache the result of an executable block
|
54
|
-
def cache(binding = nil, context = {}, &block)
|
55
|
-
cache_options = apply_cache_options(context)
|
56
|
-
ctx = key_context(context)
|
57
|
-
fetch(key(binding, ctx), nil, cache_options, &block)
|
58
|
-
end
|
59
|
-
|
60
|
-
# cache a collection of results
|
61
|
-
def cache_multi(bindings, context = {}, &block)
|
62
|
-
cache_options = apply_cache_options(context)
|
63
|
-
ctx = key_context(context)
|
64
|
-
keys = bindings.map do |binding|
|
65
|
-
key(binding, ctx)
|
66
|
-
end
|
67
|
-
# attempt to do a read_multi if the cache supports it
|
68
|
-
read_multi = keys.size > 1 && Garner.config.cache.respond_to?(:read_multi)
|
69
|
-
local_cache = read_multi ? Garner.config.cache.read_multi(*keys) : {}
|
70
|
-
# fetch all missing values
|
71
|
-
bindings.each_with_index.map do |binding, index|
|
72
|
-
key = keys[index]
|
73
|
-
# just write the value if the key was not fetched in read_multi
|
74
|
-
local_cache[key] || (read_multi ?
|
75
|
-
write(key, binding, cache_options, &block) :
|
76
|
-
fetch(key, binding, cache_options, &block))
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# invalidate an object that has been cached
|
81
|
-
def invalidate(* args)
|
82
|
-
options = index(*args)
|
83
|
-
reset_key_prefix_for(options[:klass], options[:object])
|
84
|
-
reset_key_prefix_for(options[:klass]) if options[:object]
|
85
|
-
end
|
86
|
-
|
87
|
-
# metadata for cached objects:
|
88
|
-
# :etag - Unique hash of object content
|
89
|
-
# :last_modified - Timestamp of last modification event
|
90
|
-
def cache_metadata(binding, context = {})
|
91
|
-
key = key(binding, key_context(context))
|
92
|
-
Garner.config.cache.read(meta(key))
|
93
|
-
end
|
94
|
-
|
95
|
-
private
|
96
|
-
|
97
|
-
# fetch an object from cache, write if not present
|
98
|
-
def fetch(key, binding, cache_options = {}, &block)
|
99
|
-
result = Garner.config.cache.fetch(key, cache_options) do
|
100
|
-
object = binding ? yield(binding) : yield
|
101
|
-
reset_cache_metadata key, object
|
102
|
-
object
|
103
|
-
end
|
104
|
-
Garner.config.cache.delete(key) unless result
|
105
|
-
result
|
106
|
-
end
|
107
|
-
|
108
|
-
# write an object to cache
|
109
|
-
def write(key, binding, cache_options = {}, &block)
|
110
|
-
object = binding ? yield(binding) : yield
|
111
|
-
if object
|
112
|
-
Garner.config.cache.write(key, object, cache_options)
|
113
|
-
reset_cache_metadata key, object
|
114
|
-
end
|
115
|
-
Garner.config.cache.delete(key) unless object
|
116
|
-
object
|
117
|
-
end
|
118
|
-
|
119
|
-
# applied cache options
|
120
|
-
def apply_cache_options(context)
|
121
|
-
cache_options = context[:cache_options] || {}
|
122
|
-
CACHE_STRATEGIES.each do |strategy|
|
123
|
-
cache_options = strategy.apply(cache_options)
|
124
|
-
end
|
125
|
-
cache_options
|
126
|
-
end
|
127
|
-
|
128
|
-
# applied key context
|
129
|
-
def key_context(context)
|
130
|
-
new_context = {}
|
131
|
-
context ||= {}
|
132
|
-
KEY_STRATEGIES.each do |strategy|
|
133
|
-
new_context = strategy.apply(new_context, context)
|
134
|
-
end
|
135
|
-
new_context
|
136
|
-
end
|
137
|
-
|
138
|
-
def reset_key_prefix_for(klass, object = nil)
|
139
|
-
Garner.config.cache.delete(index_string_for(klass, object))
|
140
|
-
end
|
141
|
-
|
142
|
-
def reset_cache_metadata(key, object)
|
143
|
-
return unless object
|
144
|
-
metadata = {
|
145
|
-
:etag => ETAG_STRATEGY.apply(object),
|
146
|
-
:last_modified => Time.now
|
147
|
-
}
|
148
|
-
meta_key = meta(key)
|
149
|
-
Garner.config.cache.write(meta_key, metadata)
|
150
|
-
end
|
151
|
-
|
152
|
-
def new_key_prefix_for(klass, object = nil)
|
153
|
-
Digest::MD5.hexdigest("#{klass}/#{object || "*"}:#{new_key_postfix}")
|
154
|
-
end
|
155
|
-
|
156
|
-
# Generate a key in the Klass/id format.
|
157
|
-
# @example Widget/id=1,Gadget/slug=forty-two,Fudget/*
|
158
|
-
def key(binding = nil, context = {})
|
159
|
-
bound = binding && binding[:bind] ? standardize(binding[:bind]) : {}
|
160
|
-
bound = (bound.is_a?(Array) ? bound : [ bound ]).compact
|
161
|
-
bound.collect { |el|
|
162
|
-
if el[:object] && ! IDENTITY_FIELDS.map { |id| el[:object][id] }.compact.any?
|
163
|
-
raise ArgumentError, ":bind object arguments (#{bound}) can only be keyed by #{IDENTITY_FIELDS.join(", ")}"
|
164
|
-
end
|
165
|
-
find_or_create_key_prefix_for(el[:klass], el[:object])
|
166
|
-
}.join(",") + ":" +
|
167
|
-
Digest::MD5.hexdigest(
|
168
|
-
KEY_STRATEGIES.map { |strategy| context[strategy.field] }.uniq.compact.join("\n")
|
169
|
-
)
|
170
|
-
end
|
171
|
-
|
172
|
-
# Generate an index key from args
|
173
|
-
def index(* args)
|
174
|
-
case args[0]
|
175
|
-
when Hash
|
176
|
-
args[0]
|
177
|
-
when Class
|
178
|
-
case args[1]
|
179
|
-
when Hash
|
180
|
-
{ :klass => args[0], :object => args[1] }
|
181
|
-
when NilClass
|
182
|
-
{ :klass => args[0] }
|
183
|
-
else
|
184
|
-
{ :klass => args[0], :object => { IDENTITY_FIELDS.first => args[1] } }
|
185
|
-
end
|
186
|
-
else
|
187
|
-
raise ArgumentError, "invalid args, must be (klass, identifier) or hash (#{args})"
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
def find_or_create_key_prefix_for(klass, object = nil)
|
192
|
-
Garner.config.cache.fetch(index_string_for(klass, object), {}) do
|
193
|
-
new_key_prefix_for(klass, object)
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
def new_key_prefix_for(klass, object = nil)
|
198
|
-
Digest::MD5.hexdigest("#{klass}/#{object || "*"}:#{new_key_postfix}")
|
199
|
-
end
|
200
|
-
|
201
|
-
def new_key_postfix
|
202
|
-
SecureRandom.respond_to?(:uuid) ? SecureRandom.uuid : (0...16).map{ ('a'..'z').to_a[rand(26)] }.join
|
203
|
-
end
|
204
|
-
|
205
|
-
def standardize(binding)
|
206
|
-
case binding
|
207
|
-
when Hash
|
208
|
-
binding
|
209
|
-
when Array
|
210
|
-
bind_array(binding)
|
211
|
-
when NilClass
|
212
|
-
nil
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# Generate a metadata key.
|
217
|
-
def meta(key)
|
218
|
-
"#{key}:meta"
|
219
|
-
end
|
220
|
-
|
221
|
-
def bind_array(ary)
|
222
|
-
case ary[0]
|
223
|
-
when Array, Hash
|
224
|
-
ary.collect { |subary| standardize(subary) }
|
225
|
-
when Class
|
226
|
-
h = { :klass => ary[0] }
|
227
|
-
h.merge!({
|
228
|
-
:object => (ary[1].is_a?(Hash) ? ary[1] : { IDENTITY_FIELDS.first => ary[1] })
|
229
|
-
}) if ary[1]
|
230
|
-
h
|
231
|
-
else
|
232
|
-
raise ArgumentError, "invalid argument type #{ary[0].class} in :bind (#{ary[0]})"
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
|
-
def index_string_for(klass, object = nil)
|
237
|
-
prefix = "INDEX"
|
238
|
-
IDENTITY_FIELDS.each do |field|
|
239
|
-
if object && object[field]
|
240
|
-
return "#{prefix}:#{klass}/#{field}=#{object[field]}"
|
241
|
-
end
|
242
|
-
end
|
243
|
-
"#{prefix}:#{klass}/*"
|
244
|
-
end
|
245
|
-
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
249
|
-
end
|
@@ -1,47 +0,0 @@
|
|
1
|
-
# Based on https://github.com/intridea/grape/blob/master/lib/grape/middleware/base.rb.
|
2
|
-
module Garner
|
3
|
-
module Middleware
|
4
|
-
class Base
|
5
|
-
attr_reader :app, :env, :options
|
6
|
-
|
7
|
-
# @param [Rack Application] app The standard argument for a Rack middleware.
|
8
|
-
# @param [Hash] options A hash of options, simply stored for use by subclasses.
|
9
|
-
def initialize(app, options = {})
|
10
|
-
@app = app
|
11
|
-
@options = default_options.merge(options)
|
12
|
-
end
|
13
|
-
|
14
|
-
def default_options; {} end
|
15
|
-
|
16
|
-
def call(env)
|
17
|
-
dup.call!(env)
|
18
|
-
end
|
19
|
-
|
20
|
-
def call!(env)
|
21
|
-
@env = env
|
22
|
-
before
|
23
|
-
@app_response = @app.call(@env)
|
24
|
-
after || @app_response
|
25
|
-
end
|
26
|
-
|
27
|
-
# @abstract
|
28
|
-
# Called before the application is called in the middleware lifecycle.
|
29
|
-
def before; end
|
30
|
-
|
31
|
-
# @abstract
|
32
|
-
# Called after the application is called in the middleware lifecycle.
|
33
|
-
# @return [Response, nil] a Rack SPEC response or nil to call the application afterwards.
|
34
|
-
def after; end
|
35
|
-
|
36
|
-
def request
|
37
|
-
Rack::Request.new(self.env)
|
38
|
-
end
|
39
|
-
|
40
|
-
def response
|
41
|
-
Rack::Response.new(@app_response)
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module Garner
|
2
|
-
module Middleware
|
3
|
-
module Cache
|
4
|
-
# @abstract
|
5
|
-
# Add the necessary Cache-Control and Expires headers to bust client cache.
|
6
|
-
class Bust < Garner::Middleware::Base
|
7
|
-
def after
|
8
|
-
# private: ok to store API results in a private cache
|
9
|
-
# max-age: don't reuse the cached result without checking with the server (server might say 304 Not Modified)
|
10
|
-
# must-revalidate: prevent gateways from returning a response if the API server is not reachable
|
11
|
-
@app_response[1]["Cache-Control"] = "private, max-age=0, must-revalidate"
|
12
|
-
# don't reuse the cached result without checking with the server
|
13
|
-
@app_response[1]["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
|
14
|
-
@app_response
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
@@ -1,111 +0,0 @@
|
|
1
|
-
module Garner
|
2
|
-
module Mixins
|
3
|
-
module Grape
|
4
|
-
#
|
5
|
-
# A cache that supports conditional GETs
|
6
|
-
#
|
7
|
-
# Borrows generously from http://themomorohoax.com/2009/01/07/using-stale-with-rails-to-return-304-not-modified
|
8
|
-
# See also RFC 2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.4
|
9
|
-
# for explanation of how If-Modified-Since and If-None-Match request headers are handled.
|
10
|
-
#
|
11
|
-
module Cache
|
12
|
-
|
13
|
-
def cache_enabled?
|
14
|
-
true
|
15
|
-
end
|
16
|
-
|
17
|
-
# cache a record
|
18
|
-
def cache(options = {}, &block)
|
19
|
-
unless cache_enabled?
|
20
|
-
yield
|
21
|
-
else
|
22
|
-
binding, context = cache_binding_and_context(options)
|
23
|
-
Garner::Cache::ObjectIdentity.cache(binding, context) do
|
24
|
-
yield
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# invalidate a cache record
|
30
|
-
def invalidate(*args)
|
31
|
-
Garner::Cache::ObjectIdentity.invalidate(* args)
|
32
|
-
end
|
33
|
-
|
34
|
-
def cache_or_304(options = {}, &block)
|
35
|
-
unless cache_enabled?
|
36
|
-
yield
|
37
|
-
else
|
38
|
-
binding, context = cache_binding_and_context(options)
|
39
|
-
# metadata written in a previous GET
|
40
|
-
metadata = Garner::Cache::ObjectIdentity.cache_metadata(binding, context)
|
41
|
-
error!("Not Modified", 304) if metadata && fresh?(metadata)
|
42
|
-
rc = cache(options, &block)
|
43
|
-
# metadata has been generated by cache
|
44
|
-
metadata = Garner::Cache::ObjectIdentity.cache_metadata(binding, context)
|
45
|
-
if metadata
|
46
|
-
self.last_modified = metadata[:last_modified]
|
47
|
-
self.etag = metadata[:etag]
|
48
|
-
end
|
49
|
-
rc
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
private
|
54
|
-
|
55
|
-
def cache_binding_and_context(options)
|
56
|
-
cache_context = {}
|
57
|
-
cache_context.merge!(options.dup)
|
58
|
-
cache_context[:request] = request
|
59
|
-
cache_context[:version] = version if self.respond_to?(:version) && version
|
60
|
-
cache_context.delete(:bind)
|
61
|
-
cache_binding = (options || {})[:bind]
|
62
|
-
cache_binding = cache_binding ? { :bind => cache_binding } : {}
|
63
|
-
[ cache_binding, cache_context ]
|
64
|
-
end
|
65
|
-
|
66
|
-
def fresh?(metadata = {})
|
67
|
-
case
|
68
|
-
when if_modified_since && if_none_match
|
69
|
-
not_modified?(metadata[:last_modified]) && etag_matches?(metadata[:etag])
|
70
|
-
when if_modified_since
|
71
|
-
not_modified?(metadata[:last_modified])
|
72
|
-
when if_none_match
|
73
|
-
etag_matches?(metadata[:etag])
|
74
|
-
else
|
75
|
-
false
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def if_modified_since
|
80
|
-
if since = env["HTTP_IF_MODIFIED_SINCE"]
|
81
|
-
Time.rfc2822(since) rescue nil
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def if_none_match
|
86
|
-
env["HTTP_IF_NONE_MATCH"]
|
87
|
-
end
|
88
|
-
|
89
|
-
def not_modified?(modified_at)
|
90
|
-
if_modified_since && modified_at && if_modified_since >= modified_at
|
91
|
-
end
|
92
|
-
|
93
|
-
def etag_matches?(etag)
|
94
|
-
if_none_match && if_none_match == etag
|
95
|
-
end
|
96
|
-
|
97
|
-
def last_modified=(utc_time)
|
98
|
-
return unless utc_time
|
99
|
-
header "Last-Modified", utc_time.httpdate
|
100
|
-
end
|
101
|
-
|
102
|
-
def etag=(etag)
|
103
|
-
return unless etag
|
104
|
-
header "ETag", etag
|
105
|
-
end
|
106
|
-
|
107
|
-
end
|
108
|
-
end
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
@@ -1,58 +0,0 @@
|
|
1
|
-
module Garner
|
2
|
-
module Mixins
|
3
|
-
module Mongoid
|
4
|
-
module Document
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
included do
|
8
|
-
after_create :invalidate_api_cache
|
9
|
-
after_update :invalidate_api_cache
|
10
|
-
after_destroy :invalidate_api_cache
|
11
|
-
cattr_accessor :api_cache_class
|
12
|
-
end
|
13
|
-
|
14
|
-
# invalidate API cache
|
15
|
-
def invalidate_api_cache
|
16
|
-
self.all_embedding_documents.each { |doc| doc.invalidate_api_cache }
|
17
|
-
cache_class = self.class.api_cache_class || self.class
|
18
|
-
Garner::Cache::ObjectIdentity::IDENTITY_FIELDS.each do |identity_field|
|
19
|
-
next unless self.respond_to?(identity_field)
|
20
|
-
Garner::Cache::ObjectIdentity.invalidate(cache_class, { identity_field => self.send(identity_field) })
|
21
|
-
end
|
22
|
-
Garner::Cache::ObjectIdentity.invalidate(cache_class)
|
23
|
-
end
|
24
|
-
|
25
|
-
def invalidate_api_cache_for_class
|
26
|
-
cache_class = self.class.api_cache_class || self.class
|
27
|
-
Garner::Cache::ObjectIdentity.invalidate(cache_class)
|
28
|
-
end
|
29
|
-
|
30
|
-
# navigate the parent embedding document hierarchy
|
31
|
-
def all_embedding_documents
|
32
|
-
obj = self
|
33
|
-
docs = []
|
34
|
-
while obj.metadata && obj.embedded?
|
35
|
-
# FIXME: This is not a robust check for cycles
|
36
|
-
break if docs.detect { |doc| doc.class == obj.class }
|
37
|
-
break unless obj.metadata.inverse
|
38
|
-
parent = obj.send(obj.metadata.inverse)
|
39
|
-
break unless parent
|
40
|
-
docs << parent
|
41
|
-
obj = parent
|
42
|
-
end
|
43
|
-
docs
|
44
|
-
end
|
45
|
-
|
46
|
-
module ClassMethods
|
47
|
-
# Including classes can call `cache_as` to specify a different class
|
48
|
-
# on which to bind API cache objects.
|
49
|
-
# @example `Admin`, which extends `User` should call `cache_as User`
|
50
|
-
def cache_as(klass)
|
51
|
-
self.api_cache_class = klass
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|