garner 0.3.3 → 0.4.0

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