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 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
- { :counter => 42 }
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
- { :counter => 42 }
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({ :bind => [ User, current_user.id ] }) do
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
- # apply cache strategies
55
- cache_options = cache_options(context)
56
- CACHE_STRATEGIES.each do |strategy|
57
- cache_options = strategy.apply(cache_options)
58
- end
59
- key = key(binding, key_context(context))
60
- result = Garner.config.cache.fetch(key, cache_options) do
61
- object = yield
62
- reset_cache_metadata(key, object)
63
- object
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 cache_options(context)
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
@@ -1,3 +1,3 @@
1
1
  module Garner
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
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.2.1
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-22 00:00:00.000000000 Z
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: 889347935123245080
282
+ hash: 4499796191639143111
267
283
  required_rubygems_version: !ruby/object:Gem::Requirement
268
284
  none: false
269
285
  requirements: