yaks 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +22 -35
- data/lib/yaks/breaking_changes.rb +15 -2
- data/lib/yaks/collection_mapper.rb +3 -1
- data/lib/yaks/collection_resource.rb +6 -11
- data/lib/yaks/config.rb +5 -1
- data/lib/yaks/format.rb +1 -1
- data/lib/yaks/format/collection_json.rb +1 -1
- data/lib/yaks/format/hal.rb +9 -7
- data/lib/yaks/format/json_api.rb +8 -8
- data/lib/yaks/mapper.rb +1 -1
- data/lib/yaks/mapper/association_mapper.rb +2 -2
- data/lib/yaks/null_resource.rb +16 -4
- data/lib/yaks/primitivize.rb +1 -1
- data/lib/yaks/resource.rb +46 -12
- data/lib/yaks/runner.rb +16 -3
- data/lib/yaks/serializer.rb +1 -1
- data/lib/yaks/version.rb +1 -1
- data/spec/acceptance/json_shared_examples.rb +1 -1
- data/spec/integration/map_to_resource_spec.rb +48 -9
- data/spec/support/shared_contexts.rb +1 -1
- data/spec/unit/yaks/collection_mapper_spec.rb +10 -10
- data/spec/unit/yaks/collection_resource_spec.rb +5 -5
- data/spec/unit/yaks/mapper/association_mapper_spec.rb +3 -5
- data/spec/unit/yaks/mapper/association_spec.rb +5 -3
- data/spec/unit/yaks/mapper/has_many_spec.rb +7 -7
- data/spec/unit/yaks/mapper/has_one_spec.rb +3 -3
- data/spec/unit/yaks/mapper_spec.rb +11 -11
- data/spec/unit/yaks/null_resource_spec.rb +3 -3
- data/spec/unit/yaks/resource_spec.rb +10 -19
- data/spec/unit/yaks/runner_spec.rb +4 -4
- data/spec/unit/yaks/serializer_spec.rb +1 -1
- metadata +14 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b4ec03c9b3f3c19ac3eaca461185e507d6f7edb3
|
4
|
+
data.tar.gz: 01e2fb005d69f7add878d814e23cc76029a49969
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8833c0d2122ad0b3cce1791ac7f4b982110595e31cb33c54db8740085404ba98cf2e4eda60cd941f77f7f82e263f415f56f80bfae82e6baeff6660ea92b6f792
|
7
|
+
data.tar.gz: 9b93205060524ffa7aa2b7a8a1d3c59491c208df0c77fd8942baa27498c17757c4117a3cf555ce2d019ce45be0a546c2de4de7b365823501fba830482579dd26
|
data/README.md
CHANGED
@@ -8,28 +8,25 @@
|
|
8
8
|
[gemnasium]: https://gemnasium.com/plexus/yaks
|
9
9
|
[codeclimate]: https://codeclimate.com/github/plexus/yaks
|
10
10
|
|
11
|
-
#
|
11
|
+
# Yaks
|
12
12
|
|
13
|
-
|
13
|
+
The library that understands hypermedia.
|
14
14
|
|
15
|
-
|
15
|
+
Yaks consists of a resource representation that is independent of any
|
16
|
+
output type. A Yaks mapper transforms an object into a resource, which
|
17
|
+
can then be serialized into whichever output format the client
|
18
|
+
requested. These formats are presently supported:
|
16
19
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
*
|
22
|
-
* Embedded resources
|
23
|
-
* Links to related resources
|
24
|
-
* Outbound links that have a specific relation to the resource
|
25
|
-
|
26
|
-
They might also contain extra control data to specify possible future interactions, not unlike HTML forms.
|
27
|
-
|
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).
|
20
|
+
* HAL
|
21
|
+
* JSON API
|
22
|
+
* Collection+JSON
|
23
|
+
* HTML
|
24
|
+
* HALO
|
29
25
|
|
30
26
|
## Concepts
|
31
27
|
|
32
|
-
Yaks is a processing pipeline, you create and configure the pipeline,
|
28
|
+
Yaks is a processing pipeline, you create and configure the pipeline,
|
29
|
+
then feed data through it.
|
33
30
|
|
34
31
|
``` ruby
|
35
32
|
yaks = Yaks.new do
|
@@ -42,7 +39,7 @@ yaks = Yaks.new do
|
|
42
39
|
end
|
43
40
|
end
|
44
41
|
|
45
|
-
yaks.call(data) # =>
|
42
|
+
yaks.call(data) # => '{"foo": "bar", "_links": { ... }}'
|
46
43
|
```
|
47
44
|
|
48
45
|
Yaks performs this serialization in three steps
|
@@ -386,10 +383,6 @@ default_format :collection_json
|
|
386
383
|
|
387
384
|
Subresources aren't mapped because Collection+JSON doesn't really have that concept, and the other way around templates and queries don't exist (yet) in Yaks.
|
388
385
|
|
389
|
-
### More formats
|
390
|
-
|
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.
|
392
|
-
|
393
386
|
## Hooks
|
394
387
|
|
395
388
|
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.
|
@@ -481,25 +474,19 @@ Yaks by default "primitivizes" symbols (as strings), and classes that include En
|
|
481
474
|
|
482
475
|
Yaks is used in production by [Ticketsolve](http://www.ticketsolve.com/). You can find an example API endpoint [here](http://leicestersquaretheatre.ticketsolve.com/api).
|
483
476
|
|
484
|
-
Get in touch if you like to see your name and API here.
|
485
|
-
|
486
477
|
## Demo
|
487
478
|
|
488
|
-
You can find an example app at [Yakports](https://github.com/plexus/yakports), or browse the HAL api directly using the [HAL browser](http://yaks-airports.herokuapp.com/browser.html).
|
489
|
-
|
490
|
-
## Acknowledgment
|
491
|
-
|
492
|
-
The mapper syntax is largely borrowed from ActiveModel::Serializers, which in turn closely mimics the syntax of ActiveRecord models. It's a great concise syntax that still offers plenty of flexibility, so to not reinvent the wheel I've stuck to the existing syntax as far as practical, although there are several extensions and deviations.
|
493
|
-
|
494
|
-
## Lightweight
|
495
|
-
|
496
|
-
Yaks is a lean library. It only depends on a few other tiny libraries (inflection, concord, uri_template). It has no core extensions (monkey patches). There is deliberately no built-in "integration" with existing frameworks, since the API is simply enough. You just call it.
|
479
|
+
You can find an outdated example app at [Yakports](https://github.com/plexus/yakports), or browse the HAL api directly using the [HAL browser](http://yaks-airports.herokuapp.com/browser.html).
|
497
480
|
|
498
|
-
|
481
|
+
## Standards Based
|
499
482
|
|
500
|
-
|
483
|
+
Yaks is based on internet standards, including
|
501
484
|
|
502
|
-
[
|
485
|
+
* [RFC4288 Media types](http://tools.ietf.org/html/rfc4288)
|
486
|
+
* [RFC5988 Web Linking](http://tools.ietf.org/html/rfc5988)
|
487
|
+
* [RFC6906 The "profile" link relation](http://tools.ietf.org/search/rfc6906)
|
488
|
+
* [RFC6570 URI Templates](http://tools.ietf.org/html/rfc6570)
|
489
|
+
* [RFC4229 HTTP Header Field Registrations](http://tools.ietf.org/html/rfc4229).
|
503
490
|
|
504
491
|
## How to contribute
|
505
492
|
|
@@ -5,7 +5,20 @@ module Yaks
|
|
5
5
|
|
6
6
|
BreakingChanges = {
|
7
7
|
|
8
|
-
'0.
|
8
|
+
'0.7.0' => %q~
|
9
|
+
Breaking Changes in Yaks 0.7.0
|
10
|
+
==============================
|
11
|
+
Yaks::Resource#subresources is now an array, not a hash. The rel is
|
12
|
+
stored on the resource itself as Yaks::Resource#rels (an array). This
|
13
|
+
should only be of concern if you implement custom output formats
|
14
|
+
|
15
|
+
The general signature of all processing steps (mapper, formatter,
|
16
|
+
hooks) has changed to incldue a second parameter, the rack env. If you
|
17
|
+
have custom implementations of any of these, or hooks that are not
|
18
|
+
specified as ruby blocks, you will need to take this into account
|
19
|
+
~,
|
20
|
+
|
21
|
+
'0.5.0' => %q~
|
9
22
|
|
10
23
|
Breaking Changes in Yaks 0.5.0
|
11
24
|
==============================
|
@@ -26,7 +39,7 @@ for full documentation.
|
|
26
39
|
|
27
40
|
~,
|
28
41
|
|
29
|
-
'0.4.3' => %q~
|
42
|
+
'0.4.3' => %q~
|
30
43
|
|
31
44
|
Breaking Changes in Yaks 0.4.3
|
32
45
|
==============================
|
@@ -5,25 +5,20 @@ module Yaks
|
|
5
5
|
# A collection can be the top-level result of an API call, like all posts to
|
6
6
|
# a blog, or a subresource collection, like the comments on a post result.
|
7
7
|
#
|
8
|
-
# Some formats treat everything like a collection, and a single resource as a
|
9
|
-
# collection of one. Others treat every top level response as singular, e.g.
|
10
|
-
# a single "collection of orders". Because of this Resource and
|
11
|
-
# CollectionResource can both be iterated with #each, for the "everything is
|
12
|
-
# a collection crowd", and they both respond to `links`, `attributes` and
|
13
|
-
# `subresources`, so they can both be approached like a singular resource.
|
14
|
-
#
|
15
|
-
# In the second case a collection has a single "subresource", being its
|
16
|
-
# members.
|
17
8
|
class CollectionResource < Resource
|
18
|
-
include attributes.add(members: []
|
9
|
+
include attributes.add(members: [])
|
19
10
|
|
20
11
|
extend Forwardable
|
21
|
-
def_delegators :members, :each
|
12
|
+
def_delegators :members, :each, :map, :each_with_object
|
22
13
|
|
23
14
|
# @return [Boolean]
|
24
15
|
def collection?
|
25
16
|
true
|
26
17
|
end
|
27
18
|
|
19
|
+
def seq
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
28
23
|
end
|
29
24
|
end
|
data/lib/yaks/config.rb
CHANGED
@@ -47,6 +47,10 @@ module Yaks
|
|
47
47
|
@policy ||= @policy_class.new(@policy_options)
|
48
48
|
end
|
49
49
|
|
50
|
+
def runner(object, options)
|
51
|
+
Runner.new(config: self, object: object, options: options)
|
52
|
+
end
|
53
|
+
|
50
54
|
# Main entry point into yaks
|
51
55
|
#
|
52
56
|
# @param object [Object] The object to serialize
|
@@ -58,7 +62,7 @@ module Yaks
|
|
58
62
|
# @option item_mapper [Class] Mapper class to use for items in a top-level collection
|
59
63
|
#
|
60
64
|
def call(object, options = {})
|
61
|
-
|
65
|
+
runner(object, options).call
|
62
66
|
end
|
63
67
|
alias serialize call
|
64
68
|
end
|
data/lib/yaks/format.rb
CHANGED
data/lib/yaks/format/hal.rb
CHANGED
@@ -40,7 +40,7 @@ module Yaks
|
|
40
40
|
|
41
41
|
if resource.collection?
|
42
42
|
result = result.merge(_embedded:
|
43
|
-
serialize_embedded(resource
|
43
|
+
serialize_embedded([resource]))
|
44
44
|
elsif resource.subresources.any?
|
45
45
|
result = result.merge(_embedded:
|
46
46
|
serialize_embedded(resource.subresources))
|
@@ -79,12 +79,14 @@ module Yaks
|
|
79
79
|
# @param [Array] subresources
|
80
80
|
# @return [Hash]
|
81
81
|
def serialize_embedded(subresources)
|
82
|
-
subresources.each_with_object({}) do |
|
83
|
-
memo[
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
82
|
+
subresources.each_with_object({}) do |sub, memo|
|
83
|
+
memo[sub.rels.first] = if sub.collection?
|
84
|
+
sub.map( &method(:serialize_resource) )
|
85
|
+
elsif sub.null_resource?
|
86
|
+
nil
|
87
|
+
else
|
88
|
+
serialize_resource(sub)
|
89
|
+
end
|
88
90
|
end
|
89
91
|
end
|
90
92
|
|
data/lib/yaks/format/json_api.rb
CHANGED
@@ -9,11 +9,11 @@ module Yaks
|
|
9
9
|
|
10
10
|
# @param [Yaks::Resource] resource
|
11
11
|
# @return [Hash]
|
12
|
-
def call(resource)
|
13
|
-
main_collection = resource.map(&method(:serialize_resource))
|
12
|
+
def call(resource, env = {})
|
13
|
+
main_collection = resource.seq.map(&method(:serialize_resource))
|
14
14
|
|
15
15
|
{ pluralize(resource.type) => main_collection }.tap do |serialized|
|
16
|
-
linked = resource.each_with_object({}) do |res, hsh|
|
16
|
+
linked = resource.seq.each_with_object({}) do |res, hsh|
|
17
17
|
serialize_linked_subresources(res.subresources, hsh)
|
18
18
|
end
|
19
19
|
serialized.merge!(linked: linked) unless linked.empty?
|
@@ -39,8 +39,8 @@ module Yaks
|
|
39
39
|
# @param [Yaks::Resource] subresource
|
40
40
|
# @return [Hash]
|
41
41
|
def serialize_links(subresources)
|
42
|
-
subresources.each_with_object({}) do |
|
43
|
-
next if resource.
|
42
|
+
subresources.each_with_object({}) do |resource, hsh|
|
43
|
+
next if resource.null_resource?
|
44
44
|
key = resource.collection? ? pluralize(resource.type) : resource.type
|
45
45
|
hsh[key] = serialize_link(resource)
|
46
46
|
end
|
@@ -56,7 +56,7 @@ module Yaks
|
|
56
56
|
# @param [Hash] hsh
|
57
57
|
# @return [Hash]
|
58
58
|
def serialize_linked_subresources(subresources, hsh)
|
59
|
-
subresources.
|
59
|
+
subresources.each do |resources|
|
60
60
|
serialize_linked_resources(resources, hsh)
|
61
61
|
end
|
62
62
|
end
|
@@ -64,8 +64,8 @@ module Yaks
|
|
64
64
|
# @param [Array] resources
|
65
65
|
# @param [Hash] linked
|
66
66
|
# @return [Hash]
|
67
|
-
def serialize_linked_resources(
|
68
|
-
|
67
|
+
def serialize_linked_resources(subresource, linked)
|
68
|
+
subresource.seq.each_with_object(linked) do |resource, memo|
|
69
69
|
serialize_subresource(resource, memo)
|
70
70
|
end
|
71
71
|
end
|
data/lib/yaks/mapper.rb
CHANGED
@@ -34,8 +34,8 @@ module Yaks
|
|
34
34
|
|
35
35
|
def add_subresource(resource)
|
36
36
|
object = parent_mapper.load_association(association.name)
|
37
|
-
subresource = association.map_resource(object, context)
|
38
|
-
resource.add_subresource(
|
37
|
+
subresource = association.map_resource(object, context).add_rel(rel)
|
38
|
+
resource.add_subresource(subresource)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/yaks/null_resource.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
module Yaks
|
2
2
|
class NullResource < Resource
|
3
|
-
include
|
3
|
+
include attributes.add(collection: false),
|
4
|
+
Equalizer.new(:rels, :collection)
|
4
5
|
|
5
6
|
def initialize(opts = {})
|
6
|
-
|
7
|
-
|
7
|
+
_opts = {}
|
8
|
+
_opts[:rels] = opts[:rels] if opts.key?(:rels)
|
9
|
+
_opts[:collection] = opts[:collection] if opts.key?(:collection)
|
10
|
+
super(_opts)
|
8
11
|
end
|
9
12
|
|
10
13
|
def each
|
@@ -19,6 +22,15 @@ module Yaks
|
|
19
22
|
true
|
20
23
|
end
|
21
24
|
|
25
|
+
def seq
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
|
29
|
+
def map
|
30
|
+
return [] if collection?
|
31
|
+
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
32
|
+
end
|
33
|
+
|
22
34
|
def update_attributes(_new_attrs)
|
23
35
|
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
24
36
|
end
|
@@ -31,7 +43,7 @@ module Yaks
|
|
31
43
|
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
32
44
|
end
|
33
45
|
|
34
|
-
def add_subresource(
|
46
|
+
def add_subresource(_subresource)
|
35
47
|
raise UnsupportedOperationError, "Operation #{__method__} not supported on #{self.class}"
|
36
48
|
end
|
37
49
|
end
|
data/lib/yaks/primitivize.rb
CHANGED
data/lib/yaks/resource.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
1
|
module Yaks
|
2
2
|
class Resource
|
3
3
|
include Attributes.new(
|
4
|
-
type: nil,
|
5
|
-
|
6
|
-
|
4
|
+
type: nil,
|
5
|
+
rels: [],
|
6
|
+
links: [],
|
7
|
+
attributes: {},
|
8
|
+
subresources: [],
|
9
|
+
controls: []
|
10
|
+
)
|
11
|
+
|
12
|
+
def initialize(attrs = {})
|
13
|
+
raise attrs.inspect if attrs.key?(:subresources) && !attrs[:subresources].instance_of?(Array)
|
14
|
+
super
|
15
|
+
end
|
7
16
|
|
8
17
|
def [](attr)
|
9
18
|
attributes[attr]
|
10
19
|
end
|
11
20
|
|
12
|
-
def
|
13
|
-
|
14
|
-
yield self
|
21
|
+
def seq
|
22
|
+
[self]
|
15
23
|
end
|
16
24
|
|
17
25
|
def self_link
|
@@ -23,23 +31,27 @@ module Yaks
|
|
23
31
|
def collection?
|
24
32
|
false
|
25
33
|
end
|
34
|
+
alias collection collection?
|
26
35
|
|
27
36
|
def null_resource?
|
28
37
|
false
|
29
38
|
end
|
30
39
|
|
31
|
-
def collection_rel
|
32
|
-
raise UnsupportedOperationError, "Only Yaks::CollectionResource has a collection_rel"
|
33
|
-
end
|
34
|
-
|
35
40
|
def members
|
36
41
|
raise UnsupportedOperationError, "Only Yaks::CollectionResource has members"
|
37
42
|
end
|
43
|
+
alias each members
|
44
|
+
alias map members
|
45
|
+
alias each_with_object members
|
38
46
|
|
39
47
|
def update_attributes(new_attrs)
|
40
48
|
update(attributes: @attributes.merge(new_attrs))
|
41
49
|
end
|
42
50
|
|
51
|
+
def add_rel(rel)
|
52
|
+
append_to(:rels, rel)
|
53
|
+
end
|
54
|
+
|
43
55
|
def add_link(link)
|
44
56
|
append_to(:links, link)
|
45
57
|
end
|
@@ -48,8 +60,30 @@ module Yaks
|
|
48
60
|
append_to(:controls, control)
|
49
61
|
end
|
50
62
|
|
51
|
-
def add_subresource(
|
52
|
-
|
63
|
+
def add_subresource(subresource)
|
64
|
+
append_to(:subresources, subresource)
|
65
|
+
end
|
66
|
+
|
67
|
+
def pp
|
68
|
+
indent = ->(str) { str.lines.map {|l| " #{l}"}.join }
|
69
|
+
format = ->(val) { val.respond_to?(:pp) ? val.pp : val.inspect }
|
70
|
+
|
71
|
+
fmt_attrs = self.class.attributes.attributes.map do |attr|
|
72
|
+
value = public_send(attr)
|
73
|
+
fmt_val = case value
|
74
|
+
when Array
|
75
|
+
if value.inspect.length < 50
|
76
|
+
value.inspect
|
77
|
+
else
|
78
|
+
"[\n#{indent[value.map(&format).join(",\n")]}\n]"
|
79
|
+
end
|
80
|
+
else
|
81
|
+
format[value]
|
82
|
+
end
|
83
|
+
"#{attr}=#{fmt_val}"
|
84
|
+
end.join("\n")
|
85
|
+
|
86
|
+
"#<#{self.class.name}\n#{indent[fmt_attrs]}\n>"
|
53
87
|
end
|
54
88
|
end
|
55
89
|
end
|
data/lib/yaks/runner.rb
CHANGED
@@ -5,11 +5,12 @@ module Yaks
|
|
5
5
|
include Adamantium::Flat
|
6
6
|
extend Forwardable
|
7
7
|
|
8
|
-
def_delegators :config, :policy, :default_format, :format_options, :primitivize, :serializers
|
8
|
+
def_delegators :config, :policy, :default_format, :format_options, :primitivize, :serializers
|
9
9
|
|
10
10
|
def call
|
11
|
-
steps.inject(object) {|memo, (_, step)| step.call(memo) }
|
11
|
+
steps.inject(object) {|memo, (_, step)| step.call(memo, env) }
|
12
12
|
end
|
13
|
+
alias result call
|
13
14
|
|
14
15
|
def context
|
15
16
|
{
|
@@ -33,6 +34,14 @@ module Yaks
|
|
33
34
|
end
|
34
35
|
memoize :format_class
|
35
36
|
|
37
|
+
def media_type
|
38
|
+
format_class.media_type
|
39
|
+
end
|
40
|
+
|
41
|
+
def format
|
42
|
+
format_class.format_name
|
43
|
+
end
|
44
|
+
|
36
45
|
def steps
|
37
46
|
insert_hooks(
|
38
47
|
[[ :map, mapper ],
|
@@ -55,7 +64,7 @@ module Yaks
|
|
55
64
|
memoize :formatter
|
56
65
|
|
57
66
|
def primitivizer
|
58
|
-
|
67
|
+
proc do |input|
|
59
68
|
if format_class.serializer.equal? :json
|
60
69
|
primitivize.call(input)
|
61
70
|
else
|
@@ -77,6 +86,10 @@ module Yaks
|
|
77
86
|
end
|
78
87
|
memoize :serializer
|
79
88
|
|
89
|
+
def hooks
|
90
|
+
config.hooks + options.fetch(:hooks, [])
|
91
|
+
end
|
92
|
+
|
80
93
|
def insert_hooks(steps)
|
81
94
|
hooks.inject(steps) do |steps, (type, target_step, name, hook)|
|
82
95
|
steps.flat_map do |step_name, callable|
|
data/lib/yaks/serializer.rb
CHANGED
data/lib/yaks/version.rb
CHANGED
@@ -2,7 +2,7 @@ RSpec.shared_examples_for 'JSON output format' do |yaks, format, name|
|
|
2
2
|
let(:input) { load_yaml_fixture(name) }
|
3
3
|
let(:output) { load_json_fixture("#{name}.#{format}") }
|
4
4
|
|
5
|
-
subject { yaks.
|
5
|
+
subject { yaks.call(input) }
|
6
6
|
|
7
7
|
it { should deep_eql output }
|
8
8
|
end
|
@@ -12,17 +12,56 @@ RSpec.describe 'Mapping domain models to Resource objects' do
|
|
12
12
|
its(:attributes) { should eql(id: 1, name: 'john') }
|
13
13
|
its(:links) { should eql [ Yaks::Resource::Link.new(rel: :copyright, uri: '/api/copyright/2024') ] }
|
14
14
|
|
15
|
+
specify {
|
16
|
+
|
17
|
+
subject.subresources == [
|
18
|
+
Yaks::Resource.new(
|
19
|
+
type:'pet_peeve',
|
20
|
+
rels: ['rel:pet_peeve'],
|
21
|
+
attributes: {id: 4, type: 'parsing with regexps'}
|
22
|
+
),
|
23
|
+
Yaks::CollectionResource.new(
|
24
|
+
type: 'pet',
|
25
|
+
rels: ['rel:pets'],
|
26
|
+
members: [
|
27
|
+
Yaks::Resource.new(
|
28
|
+
type: 'pet',
|
29
|
+
attributes: {:id => 2, :species => 'dog', :name => 'boingboing'}
|
30
|
+
),
|
31
|
+
Yaks::Resource.new(
|
32
|
+
type: 'pet',
|
33
|
+
attributes: {:id => 3, :species => 'cat', :name => 'wassup'}
|
34
|
+
)
|
35
|
+
]
|
36
|
+
)
|
37
|
+
]
|
38
|
+
|
39
|
+
|
40
|
+
}
|
41
|
+
|
15
42
|
its(:subresources) {
|
16
43
|
should eq(
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
44
|
+
[
|
45
|
+
Yaks::Resource.new(
|
46
|
+
type:'pet_peeve',
|
47
|
+
rels: ['rel:pet_peeve'],
|
48
|
+
attributes: {id: 4, type: 'parsing with regexps'}
|
49
|
+
),
|
50
|
+
Yaks::CollectionResource.new(
|
51
|
+
type: 'pet',
|
52
|
+
rels: ['rel:pets'],
|
53
|
+
members: [
|
54
|
+
Yaks::Resource.new(
|
55
|
+
type: 'pet',
|
56
|
+
attributes: {:id => 2, :species => 'dog', :name => 'boingboing'}
|
57
|
+
),
|
58
|
+
Yaks::Resource.new(
|
59
|
+
type: 'pet',
|
60
|
+
attributes: {:id => 3, :species => 'cat', :name => 'wassup'}
|
61
|
+
)
|
62
|
+
]
|
63
|
+
)
|
64
|
+
]
|
26
65
|
)
|
27
66
|
}
|
28
67
|
end
|
@@ -23,7 +23,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
23
23
|
links: [],
|
24
24
|
attributes: {},
|
25
25
|
members: [],
|
26
|
-
|
26
|
+
rels: ['rel:the_types']
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
@@ -44,7 +44,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
44
44
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
|
45
45
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
|
46
46
|
],
|
47
|
-
|
47
|
+
rels: ['rel:pets']
|
48
48
|
)
|
49
49
|
end
|
50
50
|
end
|
@@ -68,7 +68,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
68
68
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 2, :species => "dog", :name => "boingboing"}),
|
69
69
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
|
70
70
|
],
|
71
|
-
|
71
|
+
rels: ['rel:pets']
|
72
72
|
)
|
73
73
|
end
|
74
74
|
end
|
@@ -93,7 +93,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
93
93
|
links: [],
|
94
94
|
attributes: { foo: 123, bar: 'pi la~~~' },
|
95
95
|
members: [],
|
96
|
-
|
96
|
+
rels: ['rel:the_types']
|
97
97
|
)
|
98
98
|
end
|
99
99
|
end
|
@@ -111,7 +111,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
111
111
|
links: [ Yaks::Resource::Link.new(rel: :self, uri: 'http://api.example.com/orders') ],
|
112
112
|
attributes: { },
|
113
113
|
members: [],
|
114
|
-
|
114
|
+
rels: ['rel:the_types']
|
115
115
|
)
|
116
116
|
end
|
117
117
|
end
|
@@ -138,7 +138,7 @@ RSpec.describe Yaks::CollectionMapper do
|
|
138
138
|
members: [
|
139
139
|
Yaks::Resource.new(type: 'pet', attributes: {:id => 3, :species => "cat", :name => "wassup"})
|
140
140
|
],
|
141
|
-
|
141
|
+
rels: ['rel:pets']
|
142
142
|
)
|
143
143
|
end
|
144
144
|
end
|
@@ -146,18 +146,18 @@ RSpec.describe Yaks::CollectionMapper do
|
|
146
146
|
context 'with an empty collection' do
|
147
147
|
|
148
148
|
context 'without an item_mapper specified' do
|
149
|
-
let(:context) { Yaks::Util.slice_hash(super(), :policy, :env) }
|
149
|
+
let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :mapper_stack) }
|
150
150
|
|
151
151
|
it 'should use a rel of "collection"' do
|
152
|
-
expect(mapper.([]).
|
152
|
+
expect(mapper.([]).rels).to eq ['collection']
|
153
153
|
end
|
154
154
|
end
|
155
155
|
|
156
156
|
context 'with an item_mapper specified' do
|
157
|
-
let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :item_mapper) }
|
157
|
+
let(:context) { Yaks::Util.slice_hash(super(), :policy, :env, :mapper_stack, :item_mapper) }
|
158
158
|
|
159
159
|
it 'should derive the collection rel from the item mapper' do
|
160
|
-
expect(mapper.([]).
|
160
|
+
expect(mapper.([]).rels).to eq ['rel:the_types']
|
161
161
|
end
|
162
162
|
end
|
163
163
|
|
@@ -12,8 +12,8 @@ RSpec.describe Yaks::CollectionResource do
|
|
12
12
|
its(:links) { should eql [] }
|
13
13
|
its(:attributes) { should eql({}) }
|
14
14
|
its(:members) { should eql [] }
|
15
|
-
its(:subresources) { should eql
|
16
|
-
its(:
|
15
|
+
its(:subresources) { should eql [] }
|
16
|
+
its(:rels) { should eql [] }
|
17
17
|
end
|
18
18
|
|
19
19
|
context 'with a full constructor' do
|
@@ -32,7 +32,7 @@ RSpec.describe Yaks::CollectionResource do
|
|
32
32
|
attributes: { customer: 'John Doe', price: 10.00 }
|
33
33
|
)
|
34
34
|
],
|
35
|
-
|
35
|
+
rels: ['http://api.example.org/rels/orders']
|
36
36
|
}
|
37
37
|
}
|
38
38
|
|
@@ -51,9 +51,9 @@ RSpec.describe Yaks::CollectionResource do
|
|
51
51
|
)
|
52
52
|
]
|
53
53
|
}
|
54
|
-
its(:
|
54
|
+
its(:rels) { should eq ['http://api.example.org/rels/orders'] }
|
55
55
|
|
56
|
-
its(:subresources) { should eql
|
56
|
+
its(:subresources) { should eql [] }
|
57
57
|
|
58
58
|
end
|
59
59
|
end
|
@@ -39,16 +39,14 @@ RSpec.describe Yaks::Mapper::AssociationMapper do
|
|
39
39
|
stub(association).render_as_link?(parent_mapper) { false }
|
40
40
|
stub(association).map_rel(policy) { 'rels:the_rel' }
|
41
41
|
stub(association).name { :the_name }
|
42
|
-
stub(association).map_resource(:the_object, association_mapper.context) {
|
42
|
+
stub(association).map_resource(:the_object, association_mapper.context) { Yaks::Resource.new }
|
43
43
|
|
44
44
|
stub(parent_mapper).load_association(:the_name) { :the_object }
|
45
45
|
end
|
46
46
|
|
47
|
-
it 'should render a
|
47
|
+
it 'should render a subresource' do
|
48
48
|
expect(association_mapper.call(Yaks::Resource.new)).to eql Yaks::Resource.new(
|
49
|
-
subresources:
|
50
|
-
'rels:the_rel' => :the_resource
|
51
|
-
}
|
49
|
+
subresources: [ Yaks::Resource.new(rels: ['rels:the_rel']) ]
|
52
50
|
)
|
53
51
|
end
|
54
52
|
|
@@ -19,14 +19,14 @@ RSpec.describe Yaks::Mapper::Association do
|
|
19
19
|
let(:href) { Yaks::Undefined }
|
20
20
|
let(:link_if) { Yaks::Undefined }
|
21
21
|
|
22
|
-
its(:name)
|
22
|
+
its(:name) { should equal :shoes }
|
23
23
|
its(:item_mapper) { should equal Yaks::Mapper }
|
24
24
|
|
25
25
|
context 'with a minimal constructor' do
|
26
26
|
subject(:association) { described_class.new(name: :foo) }
|
27
27
|
|
28
28
|
its(:name) { should be :foo }
|
29
|
-
its(:item_mapper)
|
29
|
+
its(:item_mapper) { should be Yaks::Undefined }
|
30
30
|
its(:rel) { should be Yaks::Undefined }
|
31
31
|
its(:href) { should be Yaks::Undefined }
|
32
32
|
its(:link_if) { should be Yaks::Undefined }
|
@@ -38,12 +38,14 @@ RSpec.describe Yaks::Mapper::Association do
|
|
38
38
|
describe '#add_to_resource' do
|
39
39
|
let(:object) { fake(:shoes => []) }
|
40
40
|
let(:rel) { 'rel:shoes' }
|
41
|
+
|
41
42
|
before do
|
42
43
|
parent_mapper.call(object)
|
44
|
+
stub(association).map_resource(any_args) { Yaks::Resource.new }
|
43
45
|
end
|
44
46
|
|
45
47
|
it 'should delegate to AssociationMapper' do
|
46
|
-
expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context)).to eql Yaks::Resource.new(subresources:
|
48
|
+
expect(association.add_to_resource(Yaks::Resource.new, parent_mapper, yaks_context)).to eql Yaks::Resource.new(subresources: [Yaks::Resource.new(rels: ['rel:shoes'])])
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
@@ -28,22 +28,22 @@ RSpec.describe Yaks::Mapper::HasMany do
|
|
28
28
|
}
|
29
29
|
|
30
30
|
it 'should map the subresources' do
|
31
|
-
expect(closet_mapper.call(closet).subresources).to eql(
|
32
|
-
|
31
|
+
expect(closet_mapper.call(closet).subresources).to eql([
|
32
|
+
Yaks::CollectionResource.new(
|
33
33
|
type: 'shoe',
|
34
34
|
members: [
|
35
35
|
Yaks::Resource.new(type: 'shoe', attributes: {:size => 9, :color => :blue}),
|
36
36
|
Yaks::Resource.new(type: 'shoe', attributes: {:size => 11.5, :color => :red})
|
37
37
|
],
|
38
|
-
|
38
|
+
rels: ['http://foo/shoes']
|
39
39
|
)
|
40
|
-
)
|
40
|
+
])
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'should map nil to a NullResource collection' do
|
44
|
-
expect(closet_mapper.call(fake(shoes: nil)).subresources).to eql(
|
45
|
-
|
46
|
-
)
|
44
|
+
expect(closet_mapper.call(fake(shoes: nil)).subresources).to eql([
|
45
|
+
Yaks::NullResource.new(collection: true, rels: ['http://foo/shoes'])
|
46
|
+
])
|
47
47
|
end
|
48
48
|
|
49
49
|
context 'without an explicit mapper' do
|
@@ -40,9 +40,9 @@ RSpec.describe Yaks::Mapper::HasOne do
|
|
40
40
|
it 'should derive one based on policy' do
|
41
41
|
expect(subresource).to eql(
|
42
42
|
Yaks::Resource.new(
|
43
|
-
subresources:
|
44
|
-
|
45
|
-
|
43
|
+
subresources: [
|
44
|
+
Yaks::Resource.new(type: 'author', attributes: {name: name}, rels: ['http://rel'])
|
45
|
+
]
|
46
46
|
)
|
47
47
|
)
|
48
48
|
end
|
@@ -98,14 +98,14 @@ RSpec.describe Yaks::Mapper do
|
|
98
98
|
|
99
99
|
|
100
100
|
it 'should have the subresource in the resource' do
|
101
|
-
expect(resource.subresources).to eq(
|
101
|
+
expect(resource.subresources).to eq([Yaks::Resource.new(type: 'widget', attributes: {:type => 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])])
|
102
102
|
end
|
103
103
|
|
104
104
|
context 'with explicit mapper and rel' do
|
105
105
|
it 'should delegate to the given mapper' do
|
106
|
-
expect(resource.subresources).to eq(
|
107
|
-
|
108
|
-
)
|
106
|
+
expect(resource.subresources).to eq([
|
107
|
+
Yaks::Resource.new(type: 'widget', attributes: {:type => 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])
|
108
|
+
])
|
109
109
|
end
|
110
110
|
end
|
111
111
|
|
@@ -121,9 +121,9 @@ RSpec.describe Yaks::Mapper do
|
|
121
121
|
end
|
122
122
|
|
123
123
|
it 'should derive the mapper based on policy' do
|
124
|
-
expect(resource.subresources).to eq(
|
125
|
-
|
126
|
-
)
|
124
|
+
expect(resource.subresources).to eq([
|
125
|
+
Yaks::Resource.new(type: 'widget', attributes: {:type => 'super_widget'}, rels: ['http://foo.bar/rels/widgets'])
|
126
|
+
])
|
127
127
|
end
|
128
128
|
end
|
129
129
|
|
@@ -139,9 +139,9 @@ RSpec.describe Yaks::Mapper do
|
|
139
139
|
end
|
140
140
|
|
141
141
|
it 'should derive the rel based on policy' do
|
142
|
-
expect(resource.subresources).to eq(
|
143
|
-
|
144
|
-
)
|
142
|
+
expect(resource.subresources).to eq([
|
143
|
+
Yaks::Resource.new(type: 'widget', attributes: {:type => 'super_widget'}, rels: ['http://rel/rel'])
|
144
|
+
])
|
145
145
|
end
|
146
146
|
end
|
147
147
|
|
@@ -155,7 +155,7 @@ RSpec.describe Yaks::Mapper do
|
|
155
155
|
end
|
156
156
|
|
157
157
|
it 'should not map the resource' do
|
158
|
-
expect(resource.subresources).to eq(
|
158
|
+
expect(resource.subresources).to eq([])
|
159
159
|
end
|
160
160
|
end
|
161
161
|
end
|
@@ -3,9 +3,9 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe Yaks::NullResource do
|
4
4
|
subject(:null_resource) { described_class.new }
|
5
5
|
|
6
|
-
its(:attributes) { should eq
|
6
|
+
its(:attributes) { should eq({}) }
|
7
7
|
its(:links) { should eq [] }
|
8
|
-
its(:subresources) { should eq
|
8
|
+
its(:subresources) { should eq [] }
|
9
9
|
its(:collection?) { should be false }
|
10
10
|
its(:null_resource?) { should be true }
|
11
11
|
|
@@ -49,7 +49,7 @@ RSpec.describe Yaks::NullResource do
|
|
49
49
|
end
|
50
50
|
|
51
51
|
it 'should not allow adding subresources' do
|
52
|
-
expect { null_resource.add_subresource(nil
|
52
|
+
expect { null_resource.add_subresource(nil) }.to raise_error(
|
53
53
|
Yaks::UnsupportedOperationError, "Operation add_subresource not supported on Yaks::NullResource"
|
54
54
|
)
|
55
55
|
end
|
@@ -10,7 +10,7 @@ RSpec.describe Yaks::Resource do
|
|
10
10
|
its(:type) { should be_nil }
|
11
11
|
its(:attributes) { should eql({}) }
|
12
12
|
its(:links) { should eql [] }
|
13
|
-
its(:subresources) { should eql
|
13
|
+
its(:subresources) { should eql [] }
|
14
14
|
its(:self_link) { should be_nil }
|
15
15
|
its(:null_resource?) { should be false }
|
16
16
|
its(:collection?) { should be false }
|
@@ -48,17 +48,16 @@ RSpec.describe Yaks::Resource do
|
|
48
48
|
end
|
49
49
|
|
50
50
|
context 'with subresources' do
|
51
|
-
let(:init_opts) { { subresources:
|
52
|
-
its(:subresources) { should eql
|
51
|
+
let(:init_opts) { { subresources: [ Yaks::Resource.new(type: 'comment', rels: ['comments']) ] } }
|
52
|
+
its(:subresources) { should eql [Yaks::Resource.new(type: 'comment', rels: ['comments'])] }
|
53
53
|
|
54
|
-
it 'should return an
|
55
|
-
expect(resource.each.with_index.to_a).to eq [ [resource, 0] ]
|
54
|
+
it 'should return an enumerable for #seq' do
|
55
|
+
expect(resource.seq.each.with_index.to_a).to eq [ [resource, 0] ]
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
59
|
-
|
60
59
|
it 'should act as a collection of one' do
|
61
|
-
expect(resource.each.to_a).to eql [resource]
|
60
|
+
expect(resource.seq.each.to_a).to eql [resource]
|
62
61
|
end
|
63
62
|
|
64
63
|
describe 'persistent updates' do
|
@@ -66,7 +65,7 @@ RSpec.describe Yaks::Resource do
|
|
66
65
|
Yaks::Resource.new(
|
67
66
|
attributes: {x: :y},
|
68
67
|
links: [:one],
|
69
|
-
subresources:
|
68
|
+
subresources: [ :subres ]
|
70
69
|
)
|
71
70
|
}
|
72
71
|
|
@@ -75,18 +74,18 @@ RSpec.describe Yaks::Resource do
|
|
75
74
|
resource
|
76
75
|
.update_attributes(foo: :bar)
|
77
76
|
.add_link(:a_link)
|
78
|
-
.add_subresource(:
|
77
|
+
.add_subresource(:a_subresource)
|
79
78
|
.update_attributes(foo: :baz)
|
80
79
|
).to eq Yaks::Resource.new(
|
81
80
|
attributes: {x: :y, foo: :baz},
|
82
81
|
links: [:one, :a_link],
|
83
|
-
subresources:
|
82
|
+
subresources: [:subres, :a_subresource]
|
84
83
|
)
|
85
84
|
|
86
85
|
expect(resource).to eq Yaks::Resource.new(
|
87
86
|
attributes: {x: :y},
|
88
87
|
links: [:one],
|
89
|
-
subresources:
|
88
|
+
subresources: [:subres]
|
90
89
|
)
|
91
90
|
end
|
92
91
|
end
|
@@ -113,14 +112,6 @@ RSpec.describe Yaks::Resource do
|
|
113
112
|
end
|
114
113
|
end
|
115
114
|
|
116
|
-
describe '#collection_rel' do
|
117
|
-
it 'should raise unsupported operation error' do
|
118
|
-
expect { resource.collection_rel }.to raise_error(
|
119
|
-
Yaks::UnsupportedOperationError, "Only Yaks::CollectionResource has a collection_rel"
|
120
|
-
)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
115
|
describe '#members' do
|
125
116
|
it 'should raise unsupported operation error' do
|
126
117
|
expect { resource.members }.to raise_error(
|
@@ -13,8 +13,8 @@ RSpec.describe Yaks::Runner do
|
|
13
13
|
let(:runner) {
|
14
14
|
Class.new(described_class) do
|
15
15
|
def steps
|
16
|
-
[ [:step1,
|
17
|
-
[:step2,
|
16
|
+
[ [:step1, proc { |x| x + 35 }],
|
17
|
+
[:step2, proc { |x| "#{x} #{x}" }] ]
|
18
18
|
end
|
19
19
|
end.new(object: object, config: config, options: options)
|
20
20
|
}
|
@@ -219,12 +219,12 @@ RSpec.describe Yaks::Runner do
|
|
219
219
|
}
|
220
220
|
|
221
221
|
it 'should try to find an explicitly configured serializer' do
|
222
|
-
expect(runner.serializer.call('42')).to eql 'serialized 42'
|
222
|
+
expect(runner.serializer.call('42', {})).to eql 'serialized 42'
|
223
223
|
end
|
224
224
|
end
|
225
225
|
|
226
226
|
it 'should fall back to the policy' do
|
227
|
-
expect(runner.serializer.call([1,2,3])).to eql "[\n 1,\n 2,\n 3\n]"
|
227
|
+
expect(runner.serializer.call([1,2,3], {})).to eql "[\n 1,\n 2,\n 3\n]"
|
228
228
|
end
|
229
229
|
end
|
230
230
|
|
@@ -11,7 +11,7 @@ RSpec.describe Yaks::Serializer do
|
|
11
11
|
end
|
12
12
|
|
13
13
|
it 'should by default have a serializer for JSON' do
|
14
|
-
expect(Yaks::Serializer.all[:json].call([1,2,3])).to eql "[\n 1,\n 2,\n 3\n]"
|
14
|
+
expect(Yaks::Serializer.all[:json].call([1,2,3], {})).to eql "[\n 1,\n 2,\n 3\n]"
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should warn when registering a key again' do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yaks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arne Brasseur
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-11-
|
11
|
+
date: 2014-11-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: inflection
|
@@ -346,7 +346,18 @@ homepage: https://github.com/plexus/yaks
|
|
346
346
|
licenses:
|
347
347
|
- MIT
|
348
348
|
metadata: {}
|
349
|
-
post_install_message:
|
349
|
+
post_install_message: |2
|
350
|
+
|
351
|
+
Breaking Changes in Yaks 0.7.0
|
352
|
+
==============================
|
353
|
+
Yaks::Resource#subresources is now an array, not a hash. The rel is
|
354
|
+
stored on the resource itself as Yaks::Resource#rels (an array). This
|
355
|
+
should only be of concern if you implement custom output formats
|
356
|
+
|
357
|
+
The general signature of all processing steps (mapper, formatter,
|
358
|
+
hooks) has changed to incldue a second parameter, the rack env. If you
|
359
|
+
have custom implementations of any of these, or hooks that are not
|
360
|
+
specified as ruby blocks, you will need to take this into account
|
350
361
|
rdoc_options: []
|
351
362
|
require_paths:
|
352
363
|
- lib
|