knuckles 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +168 -0
- data/lib/knuckles.rb +5 -1
- data/lib/knuckles/active/hydrator.rb +1 -1
- data/lib/knuckles/pipeline.rb +1 -1
- data/lib/knuckles/stages/combiner.rb +40 -1
- data/lib/knuckles/stages/dumper.rb +14 -0
- data/lib/knuckles/stages/enhancer.rb +29 -0
- data/lib/knuckles/stages/fetcher.rb +40 -4
- data/lib/knuckles/stages/hydrator.rb +24 -0
- data/lib/knuckles/stages/renderer.rb +25 -0
- data/lib/knuckles/stages/writer.rb +14 -1
- data/lib/knuckles/version.rb +2 -1
- data/lib/knuckles/view.rb +2 -2
- data/spec/knuckles/stages/fetcher_spec.rb +9 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 96d10e0c441bc44adc48654b19878d2a7283cd7b
|
4
|
+
data.tar.gz: 4a9df62148abaec7d7fb161b37bf1bab7b511327
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 828e4d98d6f655aee4ed20fb0792af61920d9304775b23693b0f3cd87b7a6c4d4d68ceddc20a3895408eca23f88df1220239b74f705d415cf0b3f8b4525862fe
|
7
|
+
data.tar.gz: 4cb0da0daf47140f5ff953214f739d79de15de96f59134dadb803c02887a70c2cc6bf35b0719f5caf1493614e2685794ac58d236917866010cd0b32d54e674a0
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## v0.5.0 - 2016-07-08
|
2
|
+
|
3
|
+
* Added: Accept a `proc` or any callable object as the `keygen`. This simplifies
|
4
|
+
overriding the cache key on a per-instance basis.
|
5
|
+
* Added: Lots of documentation! All code has inline documentation now.
|
6
|
+
|
1
7
|
## v0.4.0 - 2016-05-11
|
2
8
|
|
3
9
|
* Added: `Knuckles::Active::Hydrator`, a hydrator specifically designed to work
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
[![Build Status](https://travis-ci.org/sorentwo/knuckles.svg?branch=master)](https://travis-ci.org/sorentwo/knuckles)
|
2
2
|
[![Coverage Status](https://coveralls.io/repos/github/sorentwo/knuckles/badge.svg?branch=master)](https://coveralls.io/github/sorentwo/knuckles?branch=master)
|
3
3
|
[![Code Climate](https://codeclimate.com/github/sorentwo/knuckles/badges/gpa.svg)](https://codeclimate.com/github/sorentwo/knuckles)
|
4
|
+
[![Inline Docs](http://inch-ci.org/github/sorentwo/knuckles.svg?branch=master)](http://inch-ci.org/github/sorentwo/knuckles)
|
4
5
|
|
5
6
|
# Knuckles (Because Sonic was Taken)
|
6
7
|
|
@@ -69,6 +70,144 @@ end
|
|
69
70
|
With the top level module configured it is simple to jump right into rendering,
|
70
71
|
but we'll look at configuring the pipeline first.
|
71
72
|
|
73
|
+
## Understanding and Using Pipelines
|
74
|
+
|
75
|
+
Knuckles renders and serializes data through a series of stages composed into a
|
76
|
+
pipeline. Stages can easily be added or removed to control how data is
|
77
|
+
transformed. Here is a breakdown of the default stages and what their role is
|
78
|
+
within the pipeline.
|
79
|
+
|
80
|
+
#### Fetcher
|
81
|
+
|
82
|
+
The fetcher is responsible for bulk retrieval of data from the cache. Fetching
|
83
|
+
is done using a single `read_multi` operation, which is multiplexed in caches
|
84
|
+
like Redis or MemCached.
|
85
|
+
|
86
|
+
```ruby
|
87
|
+
pipeline = Knuckles::Pipeline.new
|
88
|
+
|
89
|
+
pipeline.call(posts)
|
90
|
+
```
|
91
|
+
|
92
|
+
#### Hydrator
|
93
|
+
|
94
|
+
Models that couldn't be retrieved from the cache will then be hydrated, a
|
95
|
+
process where the stripped down model that was given for fetching is replaced
|
96
|
+
with a full model with preloaded associations. The behavior of the hydrator
|
97
|
+
stage is entirely controlled by passing a Proc as the `hydrate` option. If the
|
98
|
+
`hydrate` proc is omitted hydration will be skipped. Skipping hydration is
|
99
|
+
useful if you want a simplified pipeline where full models and their
|
100
|
+
associations are preloaded before starting serialization.
|
101
|
+
|
102
|
+
See `Knuckles::Active::Hydrator` for an alternative `ActiveRecord` specific
|
103
|
+
hydrator. If you are using Knuckles within a Rais app, this is probably the
|
104
|
+
hydration stage you want to use.
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
# Using the standard hydrator
|
108
|
+
pipeline.call(posts, hydrator: -> (model) { model.fetch })
|
109
|
+
|
110
|
+
# Using active hydrator with a relation that has a `prepared` scope
|
111
|
+
pipeline.call(posts, relation: posts.prepared)
|
112
|
+
```
|
113
|
+
|
114
|
+
#### Renderer
|
115
|
+
|
116
|
+
After un-cached models have been hydrated they can be rendered. Rendering is
|
117
|
+
synonymous with converting a model to a hash, like calling `as_json` on an
|
118
|
+
`ActiveRecord` model. Knuckles provides a minimal (but fast) view module that
|
119
|
+
can be used with the rendering step. Alternatively, if you're migrating from
|
120
|
+
`ActiveModelSerializers` you can pass in an AMS class instead.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
# Using Knuckles::View
|
124
|
+
pipeline.call(models, view: PostView)
|
125
|
+
|
126
|
+
# Using ActiveModelSerializer
|
127
|
+
pipeline.call(models, view: PostSerializer)
|
128
|
+
```
|
129
|
+
|
130
|
+
#### Writer
|
131
|
+
|
132
|
+
After un-cached models have been serialized they are ready to be cached for
|
133
|
+
future retrieval. Each fully serialized model is written to the cache in a
|
134
|
+
single `write_multi` operation if available (using Readthis, for example). Only
|
135
|
+
previously un-cached data will be written to the cache, making the writer a
|
136
|
+
no-op when all of the data was cached initially.
|
137
|
+
|
138
|
+
#### Enhancer
|
139
|
+
|
140
|
+
The enhancer modifies rendered data using proc passed through options. The
|
141
|
+
enhancer stage is critical to customizing the final output. For example, if
|
142
|
+
staff should have confidential data that regular users can't see you can enhance
|
143
|
+
the final values. Another use of enhancers is personalizing an otherwise generic
|
144
|
+
response.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
# Removing staff only content from the rendered data
|
148
|
+
pipeline.call(posts,
|
149
|
+
scope: current_user,
|
150
|
+
enhancer: lambda do |result, options|
|
151
|
+
scope = options[:scope]
|
152
|
+
|
153
|
+
unless scope.staff?
|
154
|
+
result.delete_if { |key, _| key == "confidential" }
|
155
|
+
end
|
156
|
+
|
157
|
+
result
|
158
|
+
end
|
159
|
+
)
|
160
|
+
```
|
161
|
+
|
162
|
+
#### Combiner
|
163
|
+
|
164
|
+
The combiner stage merges all of the individually rendered results into a single
|
165
|
+
hash. The output of this stage is a single object, ready to be serialized.
|
166
|
+
|
167
|
+
#### Dumper
|
168
|
+
|
169
|
+
The dumping process combines de-duplication and actual serialization. For every
|
170
|
+
top level key that is an array all of the children will have uniqueness
|
171
|
+
enforced. For example, if you had rendered a collection of posts that shared the
|
172
|
+
same author, you will only have a single author object serialized. Be aware that
|
173
|
+
the uniqueness check relies on the presence of an `id` key rather than full
|
174
|
+
object comparisons.
|
175
|
+
|
176
|
+
Dumping is the final stage of the pipeline. At this point you have a single
|
177
|
+
serialized payload in the format of your choice (JSON by default), ready to send
|
178
|
+
back as a response.
|
179
|
+
|
180
|
+
## Customizing Pipelines
|
181
|
+
|
182
|
+
Pipelines stages can be removed, swapped out or otherwise tuned. An array of
|
183
|
+
stages can be passed when building a new pipeline. Here is an example of
|
184
|
+
creating a customized pipeline without any caching, hydration, or enhancing:
|
185
|
+
|
186
|
+
```ruby
|
187
|
+
Knuckles::Pipeline.new(stages: [
|
188
|
+
Knuckles::Stages::Renderer,
|
189
|
+
Knuckles::Stages::Combiner,
|
190
|
+
Knuckles::Stages::Dumper
|
191
|
+
])
|
192
|
+
```
|
193
|
+
|
194
|
+
Or, perhaps you want to use the active hydrator instead:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
Knuckles::Pipeline.new(stages: [
|
198
|
+
Knuckles::Stages::Fetcher,
|
199
|
+
Knuckles::Active::Hydrator,
|
200
|
+
Knuckles::Stages::Renderer,
|
201
|
+
Knuckles::Stages::Writer,
|
202
|
+
Knuckles::Stages::Enhancer,
|
203
|
+
Knuckles::Stages::Combiner,
|
204
|
+
Knuckles::Stages::Dumper
|
205
|
+
])
|
206
|
+
```
|
207
|
+
|
208
|
+
Note that once the pipeline is initialized the stages are frozen to prevent
|
209
|
+
modification.
|
210
|
+
|
72
211
|
## Defining Views for Rendering
|
73
212
|
|
74
213
|
While you can use Knuckles with other serializers, you can also use the provided
|
@@ -95,6 +234,35 @@ end
|
|
95
234
|
|
96
235
|
See `Knuckles::View` for more usage details.
|
97
236
|
|
237
|
+
## Rendering in Rails
|
238
|
+
|
239
|
+
One driving factor of Knuckles is that code should be explicit. As a result
|
240
|
+
there isn't a default Railtie that will integrate Knuckles into the
|
241
|
+
`ActiveController` rendering process for you. Luckily there isn't much to
|
242
|
+
setting up a new pipeline for rendering. Add this to your
|
243
|
+
`ApplicationController` or an API specific controller:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
def knuckles_render(relation, options)
|
247
|
+
Knuckles::Pipeline.new.call(relation, options)
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
Now you can easily render responses:
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
def index
|
255
|
+
posts = posts.published.paginate(pagination_params)
|
256
|
+
|
257
|
+
render json: knuckles_render(
|
258
|
+
posts.select(:id, :updated_at),
|
259
|
+
relation: posts.prepared,
|
260
|
+
view: PostView,
|
261
|
+
scope: current_user,
|
262
|
+
)
|
263
|
+
end
|
264
|
+
```
|
265
|
+
|
98
266
|
## Contributing
|
99
267
|
|
100
268
|
1. Fork it ( https://github.com/sorentwo/knuckles/fork )
|
data/lib/knuckles.rb
CHANGED
@@ -33,10 +33,13 @@ module Knuckles
|
|
33
33
|
autoload :Pipeline, "knuckles/pipeline"
|
34
34
|
autoload :View, "knuckles/view"
|
35
35
|
|
36
|
+
# Top level wrapper for stages that are expected to interact with `Active*`
|
37
|
+
# libraries, such as `ActiveModel`.
|
36
38
|
module Active
|
37
39
|
autoload :Hydrator, "knuckles/active/hydrator"
|
38
40
|
end
|
39
41
|
|
42
|
+
# Top level wrapper for standard pipleline stages.
|
40
43
|
module Stages
|
41
44
|
autoload :Combiner, "knuckles/stages/combiner"
|
42
45
|
autoload :Dumper, "knuckles/stages/dumper"
|
@@ -104,7 +107,8 @@ module Knuckles
|
|
104
107
|
yield self
|
105
108
|
end
|
106
109
|
|
107
|
-
#
|
110
|
+
# Reset all configuration values back to `nil`, restoring them to the
|
111
|
+
# defaults. This is useful for testing because configuration is global.
|
108
112
|
def reset!
|
109
113
|
@cache = nil
|
110
114
|
@keygen = nil
|
@@ -24,8 +24,8 @@ module Knuckles
|
|
24
24
|
#
|
25
25
|
# @example Hydrating missing objects
|
26
26
|
#
|
27
|
-
# prepared = [Post.new(1), Post.new(2)]
|
28
27
|
# relation = Post.all.preload(:author, :comments)
|
28
|
+
# prepared = relation.select(:id, :updated_at)
|
29
29
|
#
|
30
30
|
# Knuckles::Active::Hydrator.call(prepared, relation: relation) #=>
|
31
31
|
# # [{object: #Post<1>, cached?: false, ...
|
data/lib/knuckles/pipeline.rb
CHANGED
@@ -2,10 +2,47 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# The combiner stage merges all of the individually rendered results into a
|
6
|
+
# single hash. The output of this stage is a single object with string keys
|
7
|
+
# and array values, ready to be serialized.
|
5
8
|
module Combiner
|
6
9
|
extend self
|
7
10
|
|
8
|
-
|
11
|
+
# Merge all of the rendered data into a single hash. Each
|
12
|
+
# resulting value will be an array, even if there was only one
|
13
|
+
# value in the original rendered results.
|
14
|
+
#
|
15
|
+
# @param [Enumerable] prepared The prepared collection to be combined
|
16
|
+
# @param [Hash] _options Options aren't used, but are accepted
|
17
|
+
# to maintain a consistent interface
|
18
|
+
#
|
19
|
+
# @example Combining rendered data
|
20
|
+
#
|
21
|
+
# prepared = [
|
22
|
+
# {
|
23
|
+
# result: {
|
24
|
+
# author: {id: 1, name: "Michael"},
|
25
|
+
# posts: [{id: 1, title: "hello"}],
|
26
|
+
# }
|
27
|
+
# }, {
|
28
|
+
# result: {
|
29
|
+
# author: {id: 1, name: "Michael"},
|
30
|
+
# posts: [{id: 2, title: "there"}],
|
31
|
+
# }
|
32
|
+
# }
|
33
|
+
# ]
|
34
|
+
#
|
35
|
+
# Knuckles::Stage::Combiner.call(prepared, {}) #=> {
|
36
|
+
# "author" => [
|
37
|
+
# {id: 1, name: "Michael"}
|
38
|
+
# ],
|
39
|
+
# "posts" => [
|
40
|
+
# {id: 1, title: "hello"},
|
41
|
+
# {id: 2, title: "there"}
|
42
|
+
# ]
|
43
|
+
# }
|
44
|
+
#
|
45
|
+
def call(prepared, _options)
|
9
46
|
prepared.each_with_object(array_backed_hash) do |hash, memo|
|
10
47
|
hash[:result].each do |root, values|
|
11
48
|
case values
|
@@ -16,6 +53,8 @@ module Knuckles
|
|
16
53
|
end
|
17
54
|
end
|
18
55
|
|
56
|
+
private
|
57
|
+
|
19
58
|
def array_backed_hash
|
20
59
|
Hash.new { |hash, key| hash[key] = [] }
|
21
60
|
end
|
@@ -2,9 +2,23 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# The dumping process combines de-duplication and actual serialization. For
|
6
|
+
# every top level key that is an array all of the children will have
|
7
|
+
# uniqueness enforced. For example, if you had rendered a collection of
|
8
|
+
# posts that shared the same author, you will only have a single author
|
9
|
+
# object serialized. Be aware that the uniqueness check relies on the
|
10
|
+
# presence of an `id` key rather than full object comparisons.
|
5
11
|
module Dumper
|
6
12
|
extend self
|
7
13
|
|
14
|
+
# De-duplicate values in all keys and merge them into a single hash.
|
15
|
+
# Afterwards the complete hash is serialized using the serializer
|
16
|
+
# configured at `Knuckles.serializer`.
|
17
|
+
#
|
18
|
+
# @param [Enumerable<Hash>] objects A collection of hashes to be dumped
|
19
|
+
# @param [Hash] _options Options aren't used, but are accepted
|
20
|
+
# to maintain a consistent interface
|
21
|
+
#
|
8
22
|
def call(objects, _options)
|
9
23
|
Knuckles.serializer.dump(keys_to_arrays(objects))
|
10
24
|
end
|
@@ -2,9 +2,38 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# The enhancer modifies rendered data using proc passed through options.
|
6
|
+
# The enhancer stage is critical to customizing the final output. For
|
7
|
+
# example, if staff should have confidential data that regular users can't
|
8
|
+
# see you can enhance the final values. Another use of enhancers is
|
9
|
+
# personalizing an otherwise generic response.
|
5
10
|
module Enhancer
|
6
11
|
extend self
|
7
12
|
|
13
|
+
# Modify all results using an `enhancer` proc.
|
14
|
+
#
|
15
|
+
# @param [Enumerable] prepared The prepared collection to be enhanced
|
16
|
+
# @option [Proc] :enhancer A `proc`, `lambda`, or any object that responds
|
17
|
+
# to `call`. Every complete `result` in the prepared collection will be
|
18
|
+
# passed to the enhancer.
|
19
|
+
#
|
20
|
+
# @example Removing tags unless the scope is staff
|
21
|
+
#
|
22
|
+
# enhancer = lambda do |result, options|
|
23
|
+
# scope = options[:scope]
|
24
|
+
#
|
25
|
+
# unless scope.staff?
|
26
|
+
# result.delete_if { |key, _| key == "tags" }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# result
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# prepared = [{result: {"posts" => [], "tags" => []}}]
|
33
|
+
#
|
34
|
+
# Knuckles::Stages::Enhancer.call(prepared, enhancer: enhancer) #=>
|
35
|
+
# # [{result: {"posts" => []}}]
|
36
|
+
#
|
8
37
|
def call(prepared, options)
|
9
38
|
enhancer = options[:enhancer]
|
10
39
|
|
@@ -2,16 +2,44 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# The fetcher is responsible for bulk retrieval of data from the cache.
|
6
|
+
# Fetching is done using a single `read_multi` operation, which is
|
7
|
+
# multiplexed in caches like Redis or MemCached.
|
8
|
+
#
|
9
|
+
# The underlying cache *must* support `read_multi` for the stage to work.
|
5
10
|
module Fetcher
|
6
11
|
extend self
|
7
12
|
|
13
|
+
# Fetch all previously cached objects from the configured store.
|
14
|
+
#
|
15
|
+
# @param [Enumerable] prepared The prepared collection to fetch
|
16
|
+
# @option [Module] :keygen (Knuckles.keygen) The cache key generator used
|
17
|
+
# to construct an entries cache_key. It can be any object that responds
|
18
|
+
# to `expand_key`
|
19
|
+
#
|
20
|
+
# @example Provide a custom keygen
|
21
|
+
#
|
22
|
+
# keygen = Module.new do
|
23
|
+
# def self.expand_key(object)
|
24
|
+
# object.name
|
25
|
+
# end
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# Knuckles::Stages::Fetcher.call(prepared, keygen: keygen)
|
29
|
+
#
|
30
|
+
# @example Use a lambda as a keygen
|
31
|
+
#
|
32
|
+
# Knuckles::Stages::Fetcher.call(
|
33
|
+
# prepared,
|
34
|
+
# keygen: -> (object) { object.name }
|
35
|
+
# )
|
36
|
+
#
|
8
37
|
def call(prepared, options)
|
9
38
|
results = get_cached(prepared, options)
|
10
39
|
|
11
40
|
prepared.each do |hash|
|
12
|
-
result = results[hash[:key]]
|
13
|
-
hash[:cached?] = !result.nil?
|
14
|
-
hash[:result] = result
|
41
|
+
hash[:result] = results[hash[:key]]
|
42
|
+
hash[:cached?] = !hash[:result].nil?
|
15
43
|
end
|
16
44
|
end
|
17
45
|
|
@@ -20,11 +48,19 @@ module Knuckles
|
|
20
48
|
def get_cached(prepared, options)
|
21
49
|
kgen = options.fetch(:keygen, Knuckles.keygen)
|
22
50
|
keys = prepared.map do |hash|
|
23
|
-
hash[:key] =
|
51
|
+
hash[:key] = expand_key(kgen, hash[:object])
|
24
52
|
end
|
25
53
|
|
26
54
|
Knuckles.cache.read_multi(*keys)
|
27
55
|
end
|
56
|
+
|
57
|
+
def expand_key(keygen, object)
|
58
|
+
if keygen.respond_to?(:call)
|
59
|
+
keygen.call(object)
|
60
|
+
else
|
61
|
+
keygen.expand_key(object)
|
62
|
+
end
|
63
|
+
end
|
28
64
|
end
|
29
65
|
end
|
30
66
|
end
|
@@ -2,9 +2,33 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# The hydrator converts minimal objects in a prepared collection into fully
|
6
|
+
# "hydrated" versions of the same record. For example, the initial `model`
|
7
|
+
# may only have the `id` and `updated_at` timestamp selected, which is
|
8
|
+
# ideal for fetching from the cache. If the object wasn't in the cache then
|
9
|
+
# all of the fields are needed for a complete rendering, so the hydration
|
10
|
+
# call will use the passed relation to fetch the full model and any
|
11
|
+
# associations.
|
12
|
+
#
|
13
|
+
# This is a generic hydrator suitable for any type of collection. If you
|
14
|
+
# are working with `ActiveRecord` you'll want to use the
|
15
|
+
# `Knuckles::Active::Hydrator` module instead.
|
5
16
|
module Hydrator
|
6
17
|
extend self
|
7
18
|
|
19
|
+
# Convert all uncached objects into their full representation.
|
20
|
+
#
|
21
|
+
# @param [Enumerable] prepared The prepared collection for processing
|
22
|
+
# @option [Proc, #call] :hydrate A proc used to load missing data for
|
23
|
+
# uncached objects
|
24
|
+
#
|
25
|
+
# @example Hydrating missing objects
|
26
|
+
#
|
27
|
+
# Knuckles::Hydrator.call(
|
28
|
+
# prepared,
|
29
|
+
# hydrate: -> (objects) { objects.each(&:fetch!) }
|
30
|
+
# )
|
31
|
+
#
|
8
32
|
def call(prepared, options)
|
9
33
|
hydrate = options[:hydrate]
|
10
34
|
|
@@ -2,9 +2,34 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# After un-cached models have been hydrated they can be rendered. Rendering
|
6
|
+
# is synonymous with converting a model to a hash, like calling `as_json`
|
7
|
+
# on an `ActiveRecord` model. Knuckles provides a minimal (but fast) view
|
8
|
+
# module that can be used with the rendering step. Alternatively, if you're
|
9
|
+
# migrating from `ActiveModelSerializers` you can pass in an AMS class
|
10
|
+
# instead.
|
5
11
|
module Renderer
|
6
12
|
extend self
|
7
13
|
|
14
|
+
# Serialize all un-cached objects into hashes.
|
15
|
+
#
|
16
|
+
# @param [Enumerable] objects The prepared collection to be rendered
|
17
|
+
# @option [Module] :view A `Knuckles::View` compliant module,
|
18
|
+
# it will be passed the object and any options. Alternately,
|
19
|
+
# a class compatible with the `ActiveModelSerializers` API.
|
20
|
+
#
|
21
|
+
# @example Using a Knuckles::View
|
22
|
+
#
|
23
|
+
# module PostView
|
24
|
+
# extend Knuckles::View
|
25
|
+
#
|
26
|
+
# def self.data(post, _options)
|
27
|
+
# {id: post.id, name: post.name}
|
28
|
+
# end
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# pipeline.call(models, view: PostView)
|
32
|
+
#
|
8
33
|
def call(objects, options)
|
9
34
|
view = options.fetch(:view)
|
10
35
|
|
@@ -2,10 +2,23 @@
|
|
2
2
|
|
3
3
|
module Knuckles
|
4
4
|
module Stages
|
5
|
+
# After un-cached models have been serialized they are ready to be cached
|
6
|
+
# for future retrieval. Each fully serialized model is written to the cache
|
7
|
+
# in a single `write_multi` operation if available (using Readthis, for
|
8
|
+
# example). Only previously un-cached data will be written to the cache,
|
9
|
+
# making the writer a no-op when all of the data was cached initially.
|
5
10
|
module Writer
|
6
11
|
extend self
|
7
12
|
|
8
|
-
|
13
|
+
# Write all serialized, but previously un-cached, data to the cache.
|
14
|
+
#
|
15
|
+
# @param [Enumerable] objects A collection of hashes to be serialized,
|
16
|
+
# each hash must have they keys `:key`, `:result`, and `:cached?`.
|
17
|
+
# @param [Hash] _options Options aren't used, but are accepted
|
18
|
+
# to maintain a consistent interface
|
19
|
+
# @return The original enumerable is returned unchanged
|
20
|
+
#
|
21
|
+
def call(objects, _options)
|
9
22
|
if cache.respond_to?(:write_multi)
|
10
23
|
write_multi(objects)
|
11
24
|
else
|
data/lib/knuckles/version.rb
CHANGED
data/lib/knuckles/view.rb
CHANGED
@@ -46,8 +46,8 @@ module Knuckles
|
|
46
46
|
# Convenience for combining the results of data and relations
|
47
47
|
# into a single object.
|
48
48
|
#
|
49
|
-
# @param [Object]
|
50
|
-
# @param [Hash]
|
49
|
+
# @param [Object] object The object for serializing.
|
50
|
+
# @param [Hash] options The options to be used during serialization, i.e.
|
51
51
|
# `:scope`
|
52
52
|
#
|
53
53
|
# @return [Hash] A hash representing the serialized object and relations.
|
@@ -24,6 +24,15 @@ RSpec.describe Knuckles::Stages::Fetcher do
|
|
24
24
|
|
25
25
|
expect(pluck(results, :key)).to eq(["alpha"])
|
26
26
|
end
|
27
|
+
|
28
|
+
it "allows using a lambda as a keygen" do
|
29
|
+
results = Knuckles::Stages::Fetcher.call(
|
30
|
+
prepare([Tag.new(1, "alpha")]),
|
31
|
+
keygen: -> (object) { object.name }
|
32
|
+
)
|
33
|
+
|
34
|
+
expect(pluck(results, :key)).to eq(["alpha"])
|
35
|
+
end
|
27
36
|
end
|
28
37
|
|
29
38
|
def pluck(enum, key)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knuckles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Parker Selbert
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|