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.
Files changed (40) hide show
  1. data/LICENSE.md +1 -1
  2. data/README.md +116 -135
  3. data/lib/garner/cache/binding.rb +58 -0
  4. data/lib/garner/cache/context.rb +27 -0
  5. data/lib/garner/cache/identity.rb +45 -0
  6. data/lib/garner/cache.rb +41 -0
  7. data/lib/garner/config.rb +46 -15
  8. data/lib/garner/mixins/mongoid/document.rb +75 -0
  9. data/lib/garner/mixins/mongoid/identity.rb +106 -0
  10. data/lib/garner/mixins/mongoid.rb +4 -0
  11. data/lib/garner/mixins/rack.rb +45 -0
  12. data/lib/garner/strategies/binding/invalidation/base.rb +26 -0
  13. data/lib/garner/strategies/binding/invalidation/touch.rb +27 -0
  14. data/lib/garner/strategies/binding/key/base.rb +19 -0
  15. data/lib/garner/strategies/binding/key/cache_key.rb +19 -0
  16. data/lib/garner/strategies/binding/key/safe_cache_key.rb +33 -0
  17. data/lib/garner/strategies/context/key/base.rb +21 -0
  18. data/lib/garner/strategies/context/key/caller.rb +83 -0
  19. data/lib/garner/strategies/context/key/jsonp.rb +30 -0
  20. data/lib/garner/strategies/context/key/request_get.rb +30 -0
  21. data/lib/garner/strategies/context/key/request_path.rb +28 -0
  22. data/lib/garner/strategies/context/key/request_post.rb +30 -0
  23. data/lib/garner/version.rb +1 -1
  24. data/lib/garner.rb +29 -26
  25. metadata +122 -22
  26. data/lib/garner/cache/object_identity.rb +0 -249
  27. data/lib/garner/middleware/base.rb +0 -47
  28. data/lib/garner/middleware/cache/bust.rb +0 -20
  29. data/lib/garner/mixins/grape_cache.rb +0 -111
  30. data/lib/garner/mixins/mongoid_document.rb +0 -58
  31. data/lib/garner/strategies/cache/expiration_strategy.rb +0 -16
  32. data/lib/garner/strategies/etags/grape_strategy.rb +0 -32
  33. data/lib/garner/strategies/etags/marshal_strategy.rb +0 -16
  34. data/lib/garner/strategies/keys/caller_strategy.rb +0 -38
  35. data/lib/garner/strategies/keys/jsonp_strategy.rb +0 -24
  36. data/lib/garner/strategies/keys/key_strategy.rb +0 -21
  37. data/lib/garner/strategies/keys/request_get_strategy.rb +0 -24
  38. data/lib/garner/strategies/keys/request_path_strategy.rb +0 -21
  39. data/lib/garner/strategies/keys/request_post_strategy.rb +0 -24
  40. 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.3.3
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-10 00:00:00.000000000 Z
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 set of Rack middleware and cache helpers that implement various
240
- strategies.
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/object_identity.rb
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/middleware/base.rb
252
- - lib/garner/middleware/cache/bust.rb
253
- - lib/garner/mixins/grape_cache.rb
254
- - lib/garner/mixins/mongoid_document.rb
255
- - lib/garner/strategies/cache/expiration_strategy.rb
256
- - lib/garner/strategies/etags/grape_strategy.rb
257
- - lib/garner/strategies/etags/marshal_strategy.rb
258
- - lib/garner/strategies/keys/caller_strategy.rb
259
- - lib/garner/strategies/keys/jsonp_strategy.rb
260
- - lib/garner/strategies/keys/key_strategy.rb
261
- - lib/garner/strategies/keys/request_get_strategy.rb
262
- - lib/garner/strategies/keys/request_path_strategy.rb
263
- - lib/garner/strategies/keys/request_post_strategy.rb
264
- - lib/garner/strategies/keys/version_strategy.rb
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: -1943683014266943396
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 set of Rack middleware and cache helpers that implement various
296
- strategies.
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
-