yaks 0.4.4 → 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/.gitignore +2 -0
- data/CHANGELOG.md +44 -3
- data/README.md +90 -33
- data/Rakefile +10 -0
- data/bench/bench.rb +0 -1
- data/bench/bench_1000.rb +60 -0
- data/lib/yaks/breaking_changes.rb +22 -0
- data/lib/yaks/config/dsl.rb +114 -27
- data/lib/yaks/config.rb +39 -54
- data/lib/yaks/default_policy.rb +32 -14
- data/lib/yaks/format/collection_json.rb +4 -4
- data/lib/yaks/format/hal.rb +20 -3
- data/lib/yaks/format/json_api.rb +3 -3
- data/lib/yaks/format.rb +54 -9
- data/lib/yaks/fp/callable.rb +9 -0
- data/lib/yaks/fp/hash_updatable.rb +2 -0
- data/lib/yaks/fp/updatable.rb +2 -0
- data/lib/yaks/fp.rb +8 -0
- data/lib/yaks/mapper/link.rb +2 -2
- data/lib/yaks/mapper.rb +6 -6
- data/lib/yaks/primitivize.rb +2 -2
- data/lib/yaks/resource/link.rb +0 -4
- data/lib/yaks/runner.rb +90 -0
- data/lib/yaks/util.rb +4 -0
- data/lib/yaks/version.rb +1 -1
- data/lib/yaks.rb +3 -0
- data/spec/acceptance/acceptance_spec.rb +6 -1
- data/spec/json/confucius.collection.json +5 -16
- data/spec/json/plant_collection.collection.json +32 -0
- data/spec/spec_helper.rb +2 -1
- data/spec/support/deep_eql.rb +14 -7
- data/spec/support/pet_mapper.rb +0 -2
- data/spec/unit/yaks/collection_mapper_spec.rb +24 -2
- data/spec/unit/yaks/config/dsl_spec.rb +6 -10
- data/spec/unit/yaks/config_spec.rb +40 -99
- data/spec/unit/yaks/default_policy_spec.rb +20 -0
- data/spec/unit/yaks/format/collection_json_spec.rb +41 -0
- data/spec/unit/yaks/format/hal_spec.rb +38 -3
- data/spec/unit/yaks/format/json_api_spec.rb +2 -2
- data/spec/unit/yaks/format_spec.rb +28 -3
- data/spec/unit/yaks/fp/callable_spec.rb +13 -0
- data/spec/unit/yaks/mapper_spec.rb +226 -126
- data/spec/unit/yaks/resource/link_spec.rb +2 -3
- data/spec/unit/yaks/resource_spec.rb +15 -0
- data/spec/unit/yaks/runner_spec.rb +260 -0
- data/spec/unit/yaks/util_spec.rb +7 -1
- data/yaks.gemspec +4 -1
- metadata +72 -15
- /data/spec/json/{hal_plant_collection.json → plant_collection.hal.json} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 35548188d34c9c00d48ed2c575a2be98d424f313
|
4
|
+
data.tar.gz: 203ad17187b3063c9cf04592f026234d6474bc4e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc6e520c6d8d5d878578481f3ab373867af02a90c3c1ccdf247dba7f3203049b0ba88b4681c6296c726eac52d5d85f30706449be9ae68193680a56ea407050fa
|
7
|
+
data.tar.gz: 465fbab2456ff24a8b1d948eb9cae76133a8d3fdbe2e71a6a54c246fe926693e58296626276560a8e55b99a45e4540345fdaf5b797745791af0ee97f53d4905c
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,48 @@
|
|
1
1
|
### Development
|
2
|
-
[full changelog](http://github.com/plexus/yaks/compare/v0.
|
2
|
+
[full changelog](http://github.com/plexus/yaks/compare/v0.5.0...master)
|
3
3
|
|
4
|
-
|
4
|
+
### 0.5.0
|
5
|
+
|
6
|
+
* Yaks now serializes (returns a string), instead of returning a data structure. This is a preparatory step for supporting non-JSON formats. To get the old behavior back, do this
|
7
|
+
|
8
|
+
``` ruby
|
9
|
+
yaks = Yaks.new do
|
10
|
+
skip :serialize
|
11
|
+
end
|
12
|
+
```
|
13
|
+
|
14
|
+
* The old `after` hook has been removed, instead there are now generic hooks for all steps: `before`, `after`, `around`, `skip`; `:map`, `:format`, `:primitivize`, `:serialize`.
|
15
|
+
|
16
|
+
* By default Yaks uses `JSON.pretty_generate` as a JSON unparser. To use something else, for example `Oj.dump`, do this
|
17
|
+
|
18
|
+
``` ruby
|
19
|
+
yaks = Yaks.new do
|
20
|
+
json_serializer &Oj.method(:dump)
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
* Mapping a non-empty collection will try to infer the type, and hence rel of the nested items, based on the first object in the collection. This is only relevant for formats like HAL that don't have a top-level collection representation, and only matters when mapping a collection at the top level, not when mapping a collection from an association.
|
25
|
+
|
26
|
+
* When registering a custom format (Yaks::Format subclass), the signature has changed
|
27
|
+
|
28
|
+
``` ruby
|
29
|
+
|
30
|
+
* Collection+JSON uses a link's "title" attribute to output a link's "name", to better correspond with other formats
|
31
|
+
|
32
|
+
# 0.4.3
|
33
|
+
Format.register self, :collection_json, 'application/vnd.collection+json'
|
34
|
+
|
35
|
+
# 0.5.0
|
36
|
+
register :collection_json, :json, 'application/vnd.collection+json'
|
37
|
+
```
|
38
|
+
|
39
|
+
* `yaks.call` is now the preferred interface, rather than `yaks.serialize`, although there are no plans yet to remove the alias.
|
40
|
+
|
41
|
+
* The result of a call to `Yaks.new` now responds to `to_proc`, so you can treat it as a Proc/Symbol, e.g. `some_method &yaks`
|
42
|
+
|
43
|
+
* Improved YARD documentation
|
44
|
+
|
45
|
+
* 100% mutation coverage :trumpet: :tada:
|
5
46
|
|
6
47
|
### 0.4.3
|
7
48
|
|
@@ -106,4 +147,4 @@ end
|
|
106
147
|
|
107
148
|
## v0.2.0
|
108
149
|
|
109
|
-
* links can now take a simple for a template to compute a link just like an attribute
|
150
|
+
* links can now take a simple for a template to compute a link just like an attribute
|
data/README.md
CHANGED
@@ -27,17 +27,43 @@ They might also contain extra control data to specify possible future interactio
|
|
27
27
|
|
28
28
|
These different media types for Hypermedia clients and servers base themselves on the same set of internet standards, such as [RFC4288 Media types](http://tools.ietf.org/html/rfc4288), [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988), [RFC6906 The "profile" link relation](http://tools.ietf.org/search/rfc6906) and [RFC6570 URI Templates](http://tools.ietf.org/html/rfc6570).
|
29
29
|
|
30
|
-
##
|
30
|
+
## Concepts
|
31
31
|
|
32
|
-
|
32
|
+
Yaks is a processing pipeline, you create and configure the pipeline, then feed data through it.
|
33
33
|
|
34
|
-
|
34
|
+
``` ruby
|
35
|
+
yaks = Yaks.new do
|
36
|
+
default_format :hal
|
37
|
+
rel_template 'http://api.example.com/rels/{rel}'
|
38
|
+
format_options(:hal, plural_links: [:copyright])
|
39
|
+
namespace ::MyAPI
|
40
|
+
json_serializer do |data|
|
41
|
+
MultiJson.dump(data)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
yaks.call(data) # => JSON
|
46
|
+
```
|
47
|
+
|
48
|
+
Yaks performs this serialization in three steps
|
35
49
|
|
36
|
-
|
50
|
+
* It *maps* your data to a `Yaks::Resource`
|
51
|
+
* It *formats* the resource to a syntax tree representation
|
52
|
+
* It *serializes* to get the final output
|
53
|
+
|
54
|
+
For JSON types, the "syntax tree" is just a combination of Ruby primitives, nested arrays and hashes with strings, numbers, booleans, nils.
|
55
|
+
|
56
|
+
A Resource is an abstraction shared by all output formats. It can contain key-value attributes, RFC5988 style links, and embedded sub-resources.
|
57
|
+
|
58
|
+
To build an API you create a "mapper" for each type of object you want to represent. Yaks takes care of the rest.
|
59
|
+
|
60
|
+
For all configuration options see [Yaks::Config::DSL](http://rdoc.info/gems/yaks/frames/Yaks/Config/DSL).
|
61
|
+
|
62
|
+
See also the [API Docs on rdoc.info](http://rdoc.info/gems/yaks/frames/file/README.md)
|
37
63
|
|
38
64
|
## Mappers
|
39
65
|
|
40
|
-
|
66
|
+
Say your app has a `Post` object for blog posts. To serve posts over your API, define a `PostMapper`
|
41
67
|
|
42
68
|
```ruby
|
43
69
|
class PostMapper < Yaks::Mapper
|
@@ -54,7 +80,7 @@ Configure a Yaks instance and start serializing!
|
|
54
80
|
|
55
81
|
```ruby
|
56
82
|
yaks = Yaks.new
|
57
|
-
yaks.
|
83
|
+
yaks.call(post)
|
58
84
|
```
|
59
85
|
|
60
86
|
or a bit more elaborate
|
@@ -66,7 +92,7 @@ yaks = Yaks.new do
|
|
66
92
|
format_options(:hal, plural_links: [:copyright])
|
67
93
|
end
|
68
94
|
|
69
|
-
yaks.
|
95
|
+
yaks.call(post, mapper: PostMapper, format: :hal)
|
70
96
|
```
|
71
97
|
|
72
98
|
### Attributes
|
@@ -100,7 +126,7 @@ end
|
|
100
126
|
|
101
127
|
#### Filtering
|
102
128
|
|
103
|
-
|
129
|
+
You can override `#attributes`, or `#associations`.
|
104
130
|
|
105
131
|
```ruby
|
106
132
|
class SongMapper
|
@@ -186,6 +212,40 @@ class ShowMapper < Yaks::Mapper
|
|
186
212
|
end
|
187
213
|
```
|
188
214
|
|
215
|
+
## Calling Yaks
|
216
|
+
|
217
|
+
Once you have a Yaks instance, you can call it with `call`
|
218
|
+
(`serialize` also works but might be deprecated in the future.) Pass
|
219
|
+
it the data to be serialized, plus options.
|
220
|
+
|
221
|
+
* `:env` a Rack environment, see next section
|
222
|
+
* `:format` the format to be used, e.g. `:json_api`. Note that if the Rack env contains an `Accept` header which resolves to a recognized format, then the header takes precedence
|
223
|
+
* `:mapper` the mapper to be used. Will be inferred if omitted
|
224
|
+
* `:item_mapper` When rendering a collection, the mapper to be used for each item in the collection. Will be inferred from the class of the first item in the collection if omitted.
|
225
|
+
|
226
|
+
### Rack env
|
227
|
+
|
228
|
+
When serializing, Yaks lets you pass in an `env` hash, which will be made available to all mappers.
|
229
|
+
|
230
|
+
```ruby
|
231
|
+
yaks = Yaks.new
|
232
|
+
yaks.call(foo, env: my_env)
|
233
|
+
|
234
|
+
class FooMapper
|
235
|
+
attributes :bar
|
236
|
+
|
237
|
+
def bar
|
238
|
+
if env['something']
|
239
|
+
#...
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
```
|
244
|
+
|
245
|
+
The env hash will be available to all mappers, so you can use this to pass around context. In particular context related to the current HTTP request, e.g. the current logged in user, which is why the recommended use is to pass in the Rack environment.
|
246
|
+
|
247
|
+
If `env` contains a `HTTP_ACCEPT` key (Rack's way of representing the `Accept` header), Yaks will return the format that most closely matches what was requested.
|
248
|
+
|
189
249
|
## Namespace
|
190
250
|
|
191
251
|
Yaks by default will find your mappers for you if they follow the naming convention of appending 'Mapper' to the model class name. This (and all other "conventions") can be easily redefined though, see below. If you have your mappers inside a module, use `namespace`.
|
@@ -237,28 +297,6 @@ end
|
|
237
297
|
|
238
298
|
Yaks will automatically detect and use this collection when serializing an array of `LineItem` objects.
|
239
299
|
|
240
|
-
## Rack env
|
241
|
-
|
242
|
-
When serializing, Yaks lets you pass in an `env` hash, which will be made available to all mappers.
|
243
|
-
|
244
|
-
```ruby
|
245
|
-
yaks = Yaks.new
|
246
|
-
yaks.serialize(foo, env: my_env)
|
247
|
-
|
248
|
-
class FooMapper
|
249
|
-
attributes :bar
|
250
|
-
|
251
|
-
def bar
|
252
|
-
if env['something']
|
253
|
-
#...
|
254
|
-
end
|
255
|
-
end
|
256
|
-
end
|
257
|
-
```
|
258
|
-
|
259
|
-
You can use this to pass around context, in particular context related to the current HTTP request, e.g. the current logged in user, which is why the recommended use is to pass in the Rack environment.
|
260
|
-
|
261
|
-
If `env` contains a `HTTP_ACCEPT` key (Rack's way of representing the `Accept` header), Yaks will return the format that most closely matches what was requested.
|
262
300
|
|
263
301
|
## Custom attribute/link/subresource handling
|
264
302
|
|
@@ -296,7 +334,7 @@ Since version 0.4 the recommended API is through `Yaks.new {...}.serialize`. Thi
|
|
296
334
|
|
297
335
|
```ruby
|
298
336
|
my_yaks = Yaks.new
|
299
|
-
hal = my_yaks.
|
337
|
+
hal = my_yaks.call(model)
|
300
338
|
puts JSON.dump(hal)
|
301
339
|
```
|
302
340
|
|
@@ -352,6 +390,23 @@ Subresources aren't mapped because Collection+JSON doesn't really have that conc
|
|
352
390
|
|
353
391
|
Are planned... at the moment HAL is the only format I actually use, so it's the one that's best supported. Adding formats that follow the resource=(attributes, links, subresources) structure or a subset thereof is straightforward. More features, e.g. forms/actions such as used in Mason might be added in the future.
|
354
392
|
|
393
|
+
## Hooks
|
394
|
+
|
395
|
+
It is possible to hook into the Yaks pipeline to perform extra processing steps before, after, or around each step. It also possible to skip a step.
|
396
|
+
|
397
|
+
``` ruby
|
398
|
+
yaks = Yaks.new do
|
399
|
+
# Automatically give every resource a self link
|
400
|
+
after :map, :add_self_link do |resource|
|
401
|
+
resource.add_link(Yaks::Resource::Link.new(:self, "/#{resource.type}/#{resource.attributes[:id]}"))
|
402
|
+
end
|
403
|
+
|
404
|
+
# Skip serialization, so Ruby primitives come back instead of JSON
|
405
|
+
# This was the default before versions < 0.5.0
|
406
|
+
skip :serialize
|
407
|
+
end
|
408
|
+
```
|
409
|
+
|
355
410
|
## Policy over Configuration
|
356
411
|
|
357
412
|
It's an old adage in the Ruby/Rails world to have "Convention over Configuration", mostly to derive values that were not given explicitly. Typically based on things having similar names and a 1-1 derivable relationship.
|
@@ -398,7 +453,7 @@ end
|
|
398
453
|
|
399
454
|
## Primitives
|
400
455
|
|
401
|
-
For JSON based formats,
|
456
|
+
For JSON based formats, the "syntax tree" is merely a structure of Ruby primitives that have a JSON equivalent. If your mappers return non-primitive attribute values, you can define how they should be converted. For example, JSON has no notion of dates. If your mappers return these types as attributes, then Yaks needs to know how to turn these into primitives. To add extra types, use `map_to_primitive`
|
402
457
|
|
403
458
|
```ruby
|
404
459
|
Yaks.new do
|
@@ -420,7 +475,9 @@ Yaks.new do
|
|
420
475
|
end
|
421
476
|
```
|
422
477
|
|
423
|
-
|
478
|
+
Yaks by default "primitivizes" symbols (as strings), and classes that include Enumerable (as arrays).
|
479
|
+
|
480
|
+
## Real World Usage
|
424
481
|
|
425
482
|
Yaks is used in production by [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).
|
426
483
|
|
data/Rakefile
CHANGED
data/bench/bench.rb
CHANGED
data/bench/bench_1000.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'benchmark/ips'
|
4
|
+
require 'yaks'
|
5
|
+
require 'ruby-prof'
|
6
|
+
|
7
|
+
SIZE=20
|
8
|
+
$timestamp = Time.now.utc.iso8601.gsub('-', '').gsub(':', '')
|
9
|
+
$yaks = Yaks.new
|
10
|
+
|
11
|
+
FlatModel = Struct.new(:field1, :field2)
|
12
|
+
DeepModel = Struct.new(:field, :next)
|
13
|
+
|
14
|
+
flat = SIZE.times.map do |i|
|
15
|
+
FlatModel.new(i, 'x' * (i % 50))
|
16
|
+
end
|
17
|
+
|
18
|
+
deep = nil
|
19
|
+
SIZE.times do |i|
|
20
|
+
deep = DeepModel.new(i, deep)
|
21
|
+
end
|
22
|
+
|
23
|
+
class FlatMapper < Yaks::Mapper
|
24
|
+
attributes :field1, :field2
|
25
|
+
link :self, '/model/{field1}'
|
26
|
+
end
|
27
|
+
|
28
|
+
class DeepMapper < Yaks::Mapper
|
29
|
+
attributes :field
|
30
|
+
link :self, '/model/{field}'
|
31
|
+
has_one :next, mapper: DeepMapper
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def profile!(name)
|
36
|
+
RubyProf.start
|
37
|
+
yield
|
38
|
+
results = RubyProf.stop
|
39
|
+
File.open "/tmp/#{name}-#{$timestamp}.out.#{$$}", 'w' do |file|
|
40
|
+
RubyProf::CallTreePrinter.new(results).print(file)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
do_flat = ->(format) { -> { $yaks.serialize(flat, item_mapper: FlatMapper, format: format) } }
|
45
|
+
do_deep = ->(format) { -> { $yaks.serialize(deep, mapper: DeepMapper, format: format) } }
|
46
|
+
|
47
|
+
10.times { do_flat[:hal][] }
|
48
|
+
10.times { do_deep[:hal][] }
|
49
|
+
|
50
|
+
profile!('flat', &do_flat.(:hal))
|
51
|
+
profile!('deep', &do_deep.(:hal))
|
52
|
+
exit
|
53
|
+
|
54
|
+
Benchmark.ips(10) do |job|
|
55
|
+
Yaks::Format.names.each do |format|
|
56
|
+
|
57
|
+
job.report "#{format} ; #{SIZE} objects in a list ; no nesting", &do_flat.(format)
|
58
|
+
job.report "#{format} ; #{SIZE} objects nested", &do_deep.(format)
|
59
|
+
end
|
60
|
+
end
|
@@ -4,6 +4,28 @@ module Yaks
|
|
4
4
|
# gem to aid upgraiding
|
5
5
|
|
6
6
|
BreakingChanges = {
|
7
|
+
|
8
|
+
'0.5.0' => %q~
|
9
|
+
|
10
|
+
Breaking Changes in Yaks 0.5.0
|
11
|
+
==============================
|
12
|
+
|
13
|
+
Yaks now serializes its output, you no longer have to convert to JSON
|
14
|
+
yourself. Use `skip :serialize' to get the old behavior, or
|
15
|
+
`json_serializer` to use a different JSON implementation.
|
16
|
+
|
17
|
+
The single `after' hook has been replaced with a set of `before',
|
18
|
+
`after', `around' and `skip' hooks.
|
19
|
+
|
20
|
+
If you've created your own subclass of `Yaks::Format' (previously:
|
21
|
+
`Yaks::Serializer'), then you need to update the call to
|
22
|
+
`Format.register'.
|
23
|
+
|
24
|
+
These are potentially breaking changes. See the CHANGELOG and README
|
25
|
+
for full documentation.
|
26
|
+
|
27
|
+
~,
|
28
|
+
|
7
29
|
'0.4.3' => %q~
|
8
30
|
|
9
31
|
Breaking Changes in Yaks 0.4.3
|
data/lib/yaks/config/dsl.rb
CHANGED
@@ -6,70 +6,157 @@ module Yaks
|
|
6
6
|
attr_reader :config
|
7
7
|
|
8
8
|
# @param [Yaks::Config] config
|
9
|
-
# @param [Proc]
|
10
|
-
|
11
|
-
def initialize(config, &blk)
|
9
|
+
# @param [Proc] block
|
10
|
+
def initialize(config, &block)
|
12
11
|
@config = config
|
13
12
|
@policy_class = Class.new(DefaultPolicy)
|
14
13
|
@policies = []
|
15
|
-
instance_eval(&
|
16
|
-
@policies.each do |
|
17
|
-
@policy_class.class_eval &
|
14
|
+
instance_eval(&block) if block
|
15
|
+
@policies.each do |policy_block|
|
16
|
+
@policy_class.class_eval &policy_block
|
18
17
|
end
|
19
18
|
config.policy_class = @policy_class
|
20
19
|
end
|
21
20
|
|
21
|
+
# Set the options for a format
|
22
|
+
#
|
22
23
|
# @param [Symbol] format
|
23
|
-
# @
|
24
|
+
# @param [Hash] options
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
#
|
28
|
+
# yaks = Yaks.new do
|
29
|
+
# format_options :hal, {plural_links: [:related_content]}
|
30
|
+
# end
|
31
|
+
#
|
24
32
|
def format_options(format, options)
|
25
33
|
config.format_options[format] = options
|
26
34
|
end
|
27
35
|
|
36
|
+
# Set the default format
|
37
|
+
#
|
38
|
+
# Defaults to +:hal+
|
39
|
+
#
|
28
40
|
# @param [Symbol] format
|
29
|
-
#
|
41
|
+
# Format identifier, one of +Yaks::Format.names+
|
42
|
+
#
|
43
|
+
# @example
|
44
|
+
#
|
45
|
+
# yaks = Yaks.new do
|
46
|
+
# default_fomat :json_api
|
47
|
+
# end
|
48
|
+
#
|
30
49
|
def default_format(format)
|
31
50
|
config.default_format = format
|
32
51
|
end
|
33
52
|
|
34
|
-
#
|
35
|
-
#
|
53
|
+
# Configure JSON serializer
|
54
|
+
#
|
55
|
+
# Defaults to JSON.pretty_generate
|
56
|
+
#
|
57
|
+
# @example
|
58
|
+
#
|
59
|
+
# yaks = Yaks.new do
|
60
|
+
# json_serializer &Oj.method(:dump)
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# @param [Proc] block
|
64
|
+
# Serialization procedure
|
65
|
+
#
|
66
|
+
def json_serializer(&block)
|
67
|
+
config.serializers[:json] = block
|
68
|
+
end
|
69
|
+
|
70
|
+
%w[before after around skip].map(&:intern).each do |hook_type|
|
71
|
+
define_method hook_type do |step, name = :"#{hook_type}_#{step}", &block|
|
72
|
+
config.hooks << [hook_type, step, name, block]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Set a different policy implementation
|
77
|
+
#
|
78
|
+
# By default Yaks uses +Yaks::DefaultPolicy+ to derive missing
|
79
|
+
# information. You can swap in a class with a compatible
|
80
|
+
# interface to change the default behavior
|
81
|
+
#
|
82
|
+
# To override a single policy method, simply call a method with
|
83
|
+
# the same name as part of your Yaks configuration, passing a
|
84
|
+
# block to define the new behavior.
|
85
|
+
#
|
86
|
+
# @example
|
87
|
+
#
|
88
|
+
# yaks = Yaks.new do
|
89
|
+
# derive_type_from_mapper_class do |mapper_class|
|
90
|
+
# mapper_class.name.sub(/Mapper^/,'')
|
91
|
+
# end
|
92
|
+
# end
|
93
|
+
#
|
94
|
+
# @param [Class] klass
|
95
|
+
# Policy class
|
96
|
+
#
|
36
97
|
def policy(klass)
|
37
98
|
@policy_class = klass
|
38
99
|
end
|
39
100
|
|
40
|
-
#
|
41
|
-
#
|
42
|
-
|
43
|
-
|
101
|
+
# Set the template for deriving rels
|
102
|
+
#
|
103
|
+
# Used to derive rels for links and subresources.
|
104
|
+
#
|
105
|
+
# @example
|
106
|
+
#
|
107
|
+
# yaks = Yaks.new do
|
108
|
+
# rel_template 'http://api.example.com/rels/{rel}'
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# @param [String] template
|
112
|
+
# A valid URI template containing +{rel}+
|
113
|
+
#
|
114
|
+
def rel_template(template)
|
115
|
+
config.policy_options[:rel_template] = template
|
44
116
|
end
|
45
117
|
|
46
|
-
#
|
47
|
-
#
|
118
|
+
# Set the namespace (Ruby module) that contains your mappers
|
119
|
+
#
|
120
|
+
# When your mappers don't live at the top-level, then set this
|
121
|
+
# so Yaks can correctly infer the mapper class from the model
|
122
|
+
# class.
|
123
|
+
#
|
124
|
+
# @example
|
125
|
+
#
|
126
|
+
# yaks = Yaks.new do
|
127
|
+
# mapper_namespace API::Mappers
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# module API::Mappers
|
131
|
+
# class FruitMapper < Yaks::Mapper
|
132
|
+
# ...
|
133
|
+
# end
|
134
|
+
# end
|
135
|
+
#
|
136
|
+
# class Fruit < BaseModel
|
137
|
+
# ...
|
138
|
+
# end
|
139
|
+
#
|
140
|
+
# @param [Module] namespace
|
141
|
+
#
|
48
142
|
def mapper_namespace(namespace)
|
49
143
|
config.policy_options[:namespace] = namespace
|
50
144
|
end
|
51
145
|
alias namespace mapper_namespace
|
52
146
|
|
53
147
|
# @param [Array] args
|
54
|
-
# @param [Proc] blk
|
55
|
-
# @return [Array]
|
56
|
-
def map_to_primitive(*args, &blk)
|
57
|
-
config.primitivize.map(*args, &blk)
|
58
|
-
end
|
59
|
-
|
60
148
|
# @param [Proc] block
|
61
|
-
|
62
|
-
|
63
|
-
config.steps << block
|
149
|
+
def map_to_primitive(*args, &block)
|
150
|
+
config.primitivize.map(*args, &block)
|
64
151
|
end
|
65
152
|
|
66
153
|
# Will define each method available in the DefaultPolicy upon the DSL
|
67
154
|
# and then make it available to apply to any Class taking on the
|
68
155
|
# `@policies` Array.
|
69
156
|
DefaultPolicy.public_instance_methods(false).each do |method|
|
70
|
-
define_method method do |&
|
157
|
+
define_method method do |&block|
|
71
158
|
@policies << proc {
|
72
|
-
define_method method, &
|
159
|
+
define_method method, &block
|
73
160
|
}
|
74
161
|
end
|
75
162
|
end
|
data/lib/yaks/config.rb
CHANGED
@@ -1,79 +1,64 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Config
|
3
|
+
include Yaks::FP::Callable
|
4
|
+
|
5
|
+
# @!attribute [r] format_options
|
6
|
+
# @return [Hash<Symbol,Hash>]
|
7
|
+
attr_reader :format_options
|
3
8
|
|
4
|
-
# @!attribute [rw] format_options
|
5
|
-
# @return [Hash]
|
6
9
|
# @!attribute [rw] default_format
|
7
10
|
# @return [Symbol]
|
11
|
+
attr_accessor :default_format
|
12
|
+
|
8
13
|
# @!attribute [rw] policy_class
|
9
|
-
# @return [
|
10
|
-
|
14
|
+
# @return [Class]
|
15
|
+
attr_accessor :policy_class
|
16
|
+
|
17
|
+
# @!attribute [r] policy_options
|
11
18
|
# @return [Hash]
|
19
|
+
attr_reader :policy_options
|
20
|
+
|
12
21
|
# @!attribute [rw] primitivize
|
13
|
-
# @return [
|
14
|
-
|
22
|
+
# @return [Primitivize]
|
23
|
+
attr_accessor :primitivize
|
24
|
+
|
25
|
+
# @!attribute [r] serializers
|
26
|
+
# @return [Hash<Symbol,#call>]
|
27
|
+
attr_reader :serializers
|
28
|
+
|
29
|
+
# @!attribute [r] hooks
|
15
30
|
# @return [Array]
|
16
|
-
|
31
|
+
attr_reader :hooks
|
17
32
|
|
18
|
-
# @param [Proc]
|
19
|
-
# @return [Yaks::Config]
|
33
|
+
# @param blk [Proc] Configuration block
|
20
34
|
def initialize(&blk)
|
21
35
|
@format_options = Hash.new({})
|
22
36
|
@default_format = :hal
|
23
37
|
@policy_options = {}
|
24
38
|
@primitivize = Primitivize.create
|
25
|
-
@
|
26
|
-
|
27
|
-
|
39
|
+
@serializers = {}
|
40
|
+
@hooks = []
|
41
|
+
|
28
42
|
DSL.new(self, &blk)
|
29
43
|
end
|
30
44
|
|
31
|
-
# @return [Yaks::DefaultPolicy
|
45
|
+
# @return [Yaks::DefaultPolicy]
|
32
46
|
def policy
|
33
|
-
@policy_class.new(@policy_options)
|
34
|
-
end
|
35
|
-
|
36
|
-
# @param [Hash] opts
|
37
|
-
# @param [Hash] env
|
38
|
-
# @return [Class]
|
39
|
-
def format_class(opts, env)
|
40
|
-
accept = Rack::Accept::MediaType.new(env['HTTP_ACCEPT'])
|
41
|
-
mime_type = accept.best_of([nil] + Format.mime_types.values)
|
42
|
-
return Format.by_mime_type(mime_type) if mime_type
|
43
|
-
Format.by_name(opts.fetch(:format) { @default_format })
|
44
|
-
end
|
45
|
-
|
46
|
-
# @param [Hash] opts
|
47
|
-
# @return [String]
|
48
|
-
def format_name(opts)
|
49
|
-
opts.fetch(:format) { @default_format }
|
50
|
-
end
|
51
|
-
|
52
|
-
# @param [Symbol] format
|
53
|
-
# @return [Object]
|
54
|
-
def options_for_format(format)
|
55
|
-
format_options[format]
|
47
|
+
@policy ||= @policy_class.new(@policy_options)
|
56
48
|
end
|
57
49
|
|
58
|
-
#
|
59
|
-
# Yaks::Resource => serialized structure
|
60
|
-
# serialized structure => serialized flat
|
50
|
+
# Main entry point into yaks
|
61
51
|
#
|
62
|
-
# @param [Object] object
|
63
|
-
# @param [Hash]
|
64
|
-
#
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
mapper = opts.fetch(:mapper) { policy.derive_mapper_from_object(object) }.new(context)
|
74
|
-
format = format_class(opts, env).new(format_options[format_name(opts)])
|
75
|
-
|
76
|
-
[ mapper, format, *steps ].inject(object) {|memo, step| step.call(memo) }
|
52
|
+
# @param object [Object] The object to serialize
|
53
|
+
# @param options [Hash<Symbol,Object>] Serialization options
|
54
|
+
#
|
55
|
+
# @option env [Hash] The rack environment
|
56
|
+
# @option format [Symbol] The target format, default :hal
|
57
|
+
# @option mapper [Class] Mapper class to use
|
58
|
+
# @option item_mapper [Class] Mapper class to use for items in a top-level collection
|
59
|
+
#
|
60
|
+
def call(object, options = {})
|
61
|
+
Runner.new(config: self, object: object, options: options).call
|
77
62
|
end
|
78
63
|
alias serialize call
|
79
64
|
end
|