yaks 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +13 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +23 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +32 -6
  6. data/README.md +182 -80
  7. data/examples/hal01.rb +126 -0
  8. data/examples/jsonapi01.rb +68 -0
  9. data/examples/jsonapi02.rb +62 -0
  10. data/examples/jsonapi03.rb +86 -0
  11. data/lib/yaks.rb +55 -25
  12. data/lib/yaks/collection_mapper.rb +33 -0
  13. data/lib/yaks/collection_resource.rb +65 -0
  14. data/lib/yaks/default_policy.rb +13 -0
  15. data/lib/yaks/hal_serializer.rb +59 -0
  16. data/lib/yaks/json_api_serializer.rb +59 -0
  17. data/lib/yaks/link_lookup.rb +23 -0
  18. data/lib/yaks/mapper.rb +59 -0
  19. data/lib/yaks/mapper/association.rb +43 -0
  20. data/lib/yaks/mapper/class_methods.rb +36 -0
  21. data/lib/yaks/mapper/config.rb +79 -0
  22. data/lib/yaks/mapper/has_many.rb +15 -0
  23. data/lib/yaks/mapper/has_one.rb +10 -0
  24. data/lib/yaks/mapper/link.rb +74 -0
  25. data/lib/yaks/mapper/lookup.rb +19 -0
  26. data/lib/yaks/mapper/map_links.rb +17 -0
  27. data/lib/yaks/null_resource.rb +28 -0
  28. data/lib/yaks/primitivize.rb +34 -15
  29. data/lib/yaks/profile_registry.rb +60 -0
  30. data/lib/yaks/rel_registry.rb +20 -0
  31. data/lib/yaks/resource.rb +28 -0
  32. data/lib/yaks/resource/link.rb +21 -0
  33. data/lib/yaks/serializer.rb +11 -53
  34. data/lib/yaks/shared_options.rb +15 -0
  35. data/lib/yaks/util.rb +76 -5
  36. data/lib/yaks/version.rb +1 -1
  37. data/spec/integration/map_to_resource_spec.rb +30 -0
  38. data/spec/json/hal_plant_collection.json +34 -0
  39. data/spec/spec_helper.rb +10 -1
  40. data/spec/support/friends_mapper.rb +29 -0
  41. data/spec/support/pet_mapper.rb +5 -0
  42. data/spec/support/pet_peeve_mapper.rb +3 -0
  43. data/spec/support/serializers.rb +11 -11
  44. data/spec/support/shared_contexts.rb +47 -0
  45. data/spec/support/shorthands.rb +22 -0
  46. data/spec/yaks/collection_resource_spec.rb +9 -0
  47. data/spec/yaks/hal_serializer_spec.rb +9 -0
  48. data/spec/yaks/mapper/association_spec.rb +21 -0
  49. data/spec/yaks/mapper/class_methods_spec.rb +28 -0
  50. data/spec/yaks/mapper/config_spec.rb +77 -0
  51. data/spec/yaks/mapper/has_one_spec.rb +16 -0
  52. data/spec/yaks/mapper/link_spec.rb +42 -0
  53. data/spec/yaks/mapper/map_links_spec.rb +46 -0
  54. data/spec/yaks/mapper_spec.rb +47 -0
  55. data/spec/yaks/resource_spec.rb +23 -0
  56. data/yaks.gemspec +6 -3
  57. metadata +115 -27
  58. data/lib/yaks/dumper.rb +0 -23
  59. data/lib/yaks/fold_ams_compat.rb +0 -33
  60. data/lib/yaks/fold_json_api.rb +0 -61
  61. data/lib/yaks/serializable_association.rb +0 -21
  62. data/lib/yaks/serializable_collection.rb +0 -10
  63. data/lib/yaks/serializable_object.rb +0 -18
  64. data/lib/yaks/serializer/class_methods.rb +0 -76
  65. data/spec/integration_spec.rb +0 -57
  66. data/spec/yaks/json_api_folder_spec.rb +0 -63
  67. data/spec/yaks/serializer_spec.rb +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 44bddcd54510d2f8a9fbd3a928874218eb12f946
4
- data.tar.gz: 3d6578acf4bb8b7bb22e6b81e84e747321b6a456
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NTZhMTliZGU4NTNkZTNmYjYyOGRhYzFjZjAwNTIwYTI4YTdjNDJjNA==
5
+ data.tar.gz: !binary |-
6
+ MmVjN2NjZDI0ZDhjNjliYTczZjk3ODE0OTJmNGQzNDk3ZmE3ZDk0OQ==
5
7
  SHA512:
6
- metadata.gz: 48980782c7367fef882d2bc46cde48df3d13280fd19e196246461c774e7f81f2d300800a3c3f7c6f1560fd0ecf8b60afc9628f787110768f372b7c250766269d
7
- data.tar.gz: 9733522792a868b261d48e429db1416104b62b2b8c80689a33b9c8dc8ede9d70d5173f16cded562b01adfa7aa5827cb0e59b1fac84ed0722538f9c409470420e
8
+ metadata.gz: !binary |-
9
+ M2E1ZWY2MzIyMzkyMmIxMDM3ZTFhZGRkODhhNzhiMjM1NTZiMTkxMDVjZjE2
10
+ MTc5NTAwYWI4NGJiOWJjMjVkNGI5MjdmZmM3OWE3ODIwYWYwMDNmM2FkNGMy
11
+ ZTcxNTRkNGIzMjgxZWU4OTdlYzMyMmUwYmEwYjRlM2FhODA1YTc=
12
+ data.tar.gz: !binary |-
13
+ NjJhM2ExODNhNGM2OTAzMzMzZDc3MjlkNTM0NWVmM2I1N2VlMjdkZmYxZWU2
14
+ Mzg2MmQxZmYyNmZkZTJjODZlMDFiYjViZjA5OGVjMjI3ZDEyYzI3ZGEwZWUw
15
+ MDc3NWU0MDBiYTk5OWQ3ZGU3OTJkMTVlMzQ1MmUwNjllM2ExZjU=
data/.gitignore CHANGED
@@ -1 +1,2 @@
1
1
  .bundle
2
+ coverage
data/.travis.yml ADDED
@@ -0,0 +1,23 @@
1
+ language: ruby
2
+ before_install: gem install bundler
3
+ bundler_args: --without yard guard benchmarks
4
+ script: "bundle exec rspec"
5
+ rvm:
6
+ - 1.9.2
7
+ - 1.9.3
8
+ - 2.0.0
9
+ - 2.1.0
10
+ - ruby-head
11
+ - rbx-19mode
12
+ matrix:
13
+ include:
14
+ - rvm: jruby-19mode
15
+ env: JRUBY_OPTS="$JRUBY_OPTS --debug"
16
+ - rvm: jruby-head
17
+ env: JRUBY_OPTS="$JRUBY_OPTS --debug"
18
+ notifications:
19
+ email:
20
+ recipients:
21
+ - arne@arnebrasseur.net
22
+ on_success: never
23
+ on_failure: change
data/Gemfile CHANGED
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'hamster', path: '/home/arne/github/hamster'
5
+ gem 'hamster', github: 'harukizaemon/hamster'
data/Gemfile.lock CHANGED
@@ -1,15 +1,17 @@
1
+ GIT
2
+ remote: git://github.com/harukizaemon/hamster.git
3
+ revision: aaf874b20bd31c2d984ae131dbf58d9a9bc2b379
4
+ specs:
5
+ hamster (0.4.3)
6
+
1
7
  PATH
2
8
  remote: .
3
9
  specs:
4
- yaks (0.0.0)
10
+ yaks (0.1.0)
5
11
  concord (~> 0.1.4)
6
12
  hamster (~> 0.4.3)
7
13
  inflection (~> 1.0.0)
8
-
9
- PATH
10
- remote: /home/arne/github/hamster
11
- specs:
12
- hamster (0.4.3)
14
+ uri_template (~> 0.6.0)
13
15
 
14
16
  GEM
15
17
  remote: https://rubygems.org/
@@ -28,11 +30,23 @@ GEM
28
30
  concord (0.1.4)
29
31
  adamantium (~> 0.1)
30
32
  equalizer (~> 0.0.7)
33
+ coveralls (0.7.0)
34
+ multi_json (~> 1.3)
35
+ rest-client
36
+ simplecov (>= 0.7)
37
+ term-ansicolor
38
+ thor
31
39
  descendants_tracker (0.0.3)
32
40
  diff-lcs (1.2.5)
41
+ docile (1.1.1)
33
42
  equalizer (0.0.8)
34
43
  ice_nine (0.10.0)
35
44
  inflection (1.0.0)
45
+ mime-types (2.0)
46
+ multi_json (1.8.2)
47
+ rake (10.1.1)
48
+ rest-client (1.6.7)
49
+ mime-types (>= 1.16)
36
50
  rspec (2.14.1)
37
51
  rspec-core (~> 2.14.0)
38
52
  rspec-expectations (~> 2.14.0)
@@ -41,8 +55,18 @@ GEM
41
55
  rspec-expectations (2.14.4)
42
56
  diff-lcs (>= 1.1.3, < 2.0)
43
57
  rspec-mocks (2.14.4)
58
+ simplecov (0.8.2)
59
+ docile (~> 1.1.0)
60
+ multi_json
61
+ simplecov-html (~> 0.8.0)
62
+ simplecov-html (0.8.0)
63
+ term-ansicolor (1.2.2)
64
+ tins (~> 0.8)
65
+ thor (0.18.1)
44
66
  thread_safe (0.1.3)
45
67
  atomic
68
+ tins (0.13.1)
69
+ uri_template (0.6.0)
46
70
  virtus (1.0.0)
47
71
  axiom-types (~> 0.0.5)
48
72
  coercible (~> 0.2)
@@ -53,7 +77,9 @@ PLATFORMS
53
77
  ruby
54
78
 
55
79
  DEPENDENCIES
80
+ coveralls
56
81
  hamster!
82
+ rake
57
83
  rspec
58
84
  virtus
59
85
  yaks!
data/README.md CHANGED
@@ -1,105 +1,207 @@
1
- # Yankee Alpha Kilo Serializers
1
+ [![Gem Version](https://badge.fury.io/rb/yaks.png)][gem]
2
+ [![Build Status](https://secure.travis-ci.org/plexus/yaks.png?branch=master)][travis]
3
+ [![Dependency Status](https://gemnasium.com/plexus/yaks.png)][gemnasium]
4
+ [![Code Climate](https://codeclimate.com/github/plexus/yaks.png)][codeclimate]
5
+ [![Coverage Status](https://coveralls.io/repos/plexus/yaks/badge.png?branch=master)][coveralls]
6
+
7
+ [gem]: https://rubygems.org/gems/yaks
8
+ [travis]: https://travis-ci.org/plexus/yaks
9
+ [gemnasium]: https://gemnasium.com/plexus/yaks
10
+ [codeclimate]: https://codeclimate.com/github/plexus/yaks
11
+ [coveralls]: https://coveralls.io/r/plexus/yaks
12
+
13
+ # Yaks
14
+
15
+ ### One Stop Hypermedia Shopping ###
16
+
17
+ Yaks is a tool for turning your domain models into Hypermedia resources.
18
+
19
+ There are at the moment a number of competing media types for building Hypermedia APIs. These all add a layer of semantics on top of a low level serialization format such as JSON or XML. Even though they each have their own design goals, the core features mostly overlap. They typically provide a way to represent resources (entities), and resource collections, consisting of
20
+
21
+ * Data in key-value format, possibly with composite values
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).
29
+
30
+ ## Yaks Resources
31
+
32
+ At the core of Yaks is the concept of a Resource, consisting of key-value attributes, RFC5988 style links, and embedded sub-resources.
33
+
34
+ To build an API you create 'mappers' that turn your domain models into resources. Then you pick a media type 'serializer', which can turn the resource into a hypermedia message.
35
+
36
+ The web linking standard which defines link relations like 'self', 'next' or 'alternate' is embraced as far as practically possible, for instance to find the URI that uniquely defines a resource, we look at the 'self' link. To distinguish different types of resources we use the 'profile' link.
37
+
38
+ ## Mappers
39
+
40
+ To turn your domain models into resources, you define mappers, for example :
2
41
 
3
42
  ```ruby
4
- class ShowSerializer < Yaks::Serialize
5
- attributes :id, :name, :description, :dates
43
+ class PostMapper < Yaks::Mapper
44
+ link :self, '/api/posts/{id}'
45
+
46
+ attributes :id, :title
47
+
48
+ has_one :author
49
+ has_many :comments
50
+ end
51
+ ```
52
+
53
+ Now you can use this to create a Resource
6
54
 
7
- has_many :events
8
- has_one :event_category
55
+ ```ruby
56
+ resource = PostMapper.new(post).to_resource
57
+ ```
9
58
 
10
- def description
11
- object.description(:long)
59
+ ### Attributes
60
+
61
+ Use the `attributes` DSL method to specify which attributes of your model you want to expose, as in the example above. You can override the `load_attribute` method to change how attributes are fetched from the model.
62
+
63
+ For example, if you are representing data that is stored in a Hash, you could do
64
+
65
+ ```ruby
66
+ class PostHashMapper < Yaks::Mapper
67
+ attributes :id, :body
68
+
69
+ def load_attribute(name)
70
+ object[name]
12
71
  end
72
+ end
73
+ ```
74
+
75
+ The default implementation will first try to find a matching method for an attribute on the mapper itself, and will then fall back to calling the actual model. So you can add extra 'virtual' attributes like so :
76
+
77
+ ```ruby
78
+ class CommentMapper < Yaks::Mapper
79
+ attributes :id, :body, :date
13
80
 
14
- def dates
15
- events.map(&:day)
81
+ def date
82
+ object.created_at.strftime("at %I:%M%p")
16
83
  end
17
84
  end
85
+ ```
86
+
87
+ #### Filtering
88
+
89
+ Implement `filter(attrs)` to filter out specific attributes, e.g. based on options.
18
90
 
19
- class EventSerializer < Yaks::Serializer
20
- attributes :id, :name
91
+ ```ruby
92
+ def filter(attrs)
93
+ attrs.reject{|attr| options[:exclude].include? attr
21
94
  end
95
+ ```
22
96
 
23
- class EventCategorySerializer < Yaks::Serializer
24
- attributes :id, :name
97
+ ### Links
98
+
99
+ You can specify link templates that will be expanded with model attributes. The link relation name should be a registered [IANA link relation](http://www.iana.org/assignments/link-relations/link-relations.xhtml) or a URL.
100
+
101
+ ```ruby
102
+ class FooMapper < Yaks::Mapper
103
+ link :self, '/api/foo/{id}'
104
+ link 'http://api.foo.com/rels/comments', '/api/foo/{id}/comments'
25
105
  end
106
+ ```
107
+
108
+ To prevent a link to be expanded, add `expand: false` as an option. Now the actual template will be rendered in the result, so clients can use it to generate links from.
26
109
 
27
- json = JSON.dump(
28
- Yaks::Dumper.new(format: :json_api).dump('shows', Show.upcoming)
29
- )
110
+ ### Associations
111
+
112
+ Use `has_one` for an association that returns a single object, or `has_many` for embedding a collection.
113
+
114
+ Options
115
+
116
+ * `:as` : use a different name for the association in the result
117
+ * `:mapper` : Use a specific for each instance, will be derived from the class name if omitted (see Policy vs Configuration)
118
+ * `:collection_mapper` : For mapping the collection as a whole, this defaults to Yaks::CollectionMapper, but you can subclass it for example to add links or attributes on the collection itself
119
+ * `:policy` : supply an alternative Policy object
120
+
121
+ ## Serializers
122
+
123
+ A resource can be turned in to a specific media type representation, for example [HAL](http://stateless.co/hal_specification.html) using a Serializer
124
+
125
+ ```ruby
126
+ hal = Yaks::HalSerializer.new(resource).serialize
127
+ puts JSON.dump(hal)
30
128
  ```
31
129
 
130
+ This will give you back a composite types consisting of primitives that have a mapping to JSON, so you can use your favorite JSON encoder to turn this into a character stream.
131
+
132
+ ### Yaks::HalSerializer
133
+
134
+ Serializes to HAL. In HAL one decides when building an API which links can only be singular (e.g. self), and which are always represented as an array. So the HalSerializer understand the `:singular_links` option.
135
+
136
+ ```ruby
137
+ hal = Yaks::HalSerializer.new(resource, singular_links: [:self, :"ea:find", :"ea:basket"])
138
+ ```
139
+
140
+ CURIEs are not explicitly supported, but it's possible to use them with some effort, see `examples/hal01.rb` for an example.
141
+
142
+ The line between a singular resource and a collection is fuzzy in HAL. To stick close to the spec you're best to create your own singular types that represent collections, rather than rendering a top level CollectionResource.
143
+
144
+ ### Yaks::JsonApiSerializer
145
+
146
+ JSON-API has no concept of outbound links, so these will not be rendered, but the profile link information will be used to derive the root key.
147
+
148
+ * `embed: :resources` : Embed resources in a `{"linked":` section, referenced by id
149
+ * `embed: :links` : Use URL style JSON-API
150
+
151
+ ## Policy over Configuration
152
+
153
+ It's an old adagio 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.
154
+
155
+ This saves a lot of typing, but for the uninitiated it can also create confusion, the implicitness makes it hard to follow what's going on.
156
+
157
+ What's worse, is that often the Configuration part is skimmed over, making it very hard to deviate from the Golden Standard.
158
+
159
+ There is another old adagio, "Policy vs Mechanism". Implement the mechanisms, but don't dictate the policy.
160
+
161
+ In Yaks whenever missing values need to be inferred, like finding an unspecified mapper for a relation, this is handled by a policy object. The default is `Yaks::DefaultPolicy`, you can go there to find all the rules of inference. Subclass it and override to fit your needs, then pass it in to each mapper/serializer, they will pass it on to whatever objects they call.
162
+
163
+ ```ruby
164
+ PostMapper.new(post, policy: MyPolicy.new)
165
+ ```
166
+
167
+ ## ProfileRegistry , RelationRegistry
168
+
169
+ ...
170
+
171
+ ## Future plans
172
+
173
+ * Collection+JSON
174
+ * Siren
175
+ * Examples on how to integrate with web frameworks
176
+
177
+ ## Acknowledgment
178
+
179
+ 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 we stick to the existing syntax as far as practical, although there are several extensions and deviations.
180
+
181
+ ## How to contribute
182
+
183
+ Run the tests, the examples, try it with your own stuff and leave your impressions in the issues. Or discuss on API-craft.
184
+
185
+ To fix a bug
186
+
187
+ 1. Fork the repo
188
+ 2. Fix the bug, add tests for it
189
+ 3. Push it to a named branch
190
+ 4. Add a PR
191
+
192
+ To add a feature
193
+
194
+ 1. Open an issue as soon as possible to gather feedback
195
+ 2. Same as above, fork, push to named branch, make a pull-request
196
+
32
197
  ## Non-Features
33
198
 
34
199
  * No core extensions
35
200
  * Minimal dependencies
36
201
  * Only serializes what explicitly has a Serializer, will never call to_json/as_json
37
202
  * Adding extra output formats does not require altering existing code
203
+ * Has no opinion on what to use for final JSON encoding (json, multi_json, yajl, oj, etc.)
38
204
 
39
- ## Formats
40
-
41
- * :json_api
42
-
43
- ```json
44
- {
45
- "shows" : [
46
- {
47
- "dates" : [ "next Sunday" ],
48
- "name" : "Piglet gets his groove back",
49
- "id" : 5,
50
- "description" : "Once in a lifetime...",
51
- "links" : {
52
- "event_category" : 2,
53
- "events" : [ 7 ]
54
- }
55
- }
56
- ],
57
- "linked" : {
58
- "event_categories" : [
59
- {
60
- "name" : "Drama",
61
- "id" : 2
62
- }
63
- ],
64
- "events" : [
65
- {
66
- "name" : "Sneak preview",
67
- "id" : 7
68
- }
69
- ]
70
- }
71
- }
72
- ```
73
-
74
- * :ams_compat
75
-
76
- ```json
77
- {
78
- "shows" : [
79
- {
80
- "dates" : [ "next Sunday" ],
81
- "name" : "Piglet gets his groove back",
82
- "id" : 5,
83
- "description" : null,
84
- "event_ids" : [ 7 ],
85
- "event_category_id" : 2
86
- }
87
- ],
88
- "event_categories" : [{
89
- "name" : "Drama",
90
- "id" : 2
91
- }],
92
- "events" : [{
93
- "Name" : "Sneak preview",
94
- "id" : 7
95
- }]
96
- }
97
-
98
- ## Maturity
99
-
100
- Infantile. Crazy what these kids get up to, though.
101
-
102
- requires current master of Hamster.
103
205
 
104
206
  ## License
105
207
 
data/examples/hal01.rb ADDED
@@ -0,0 +1,126 @@
1
+ # http://stateless.co/hal_specification.html
2
+
3
+ # There are still some affordances missing to support all of HAL, in particular
4
+ # for this example to ability to generate links based on composite content
5
+ # (ea:admin) is not implemented, neither is explicit support for CURIEs (compact
6
+ # URI shorthand syntax), although this examples works around that by manually
7
+ # encoding the CURIE prefixes.
8
+
9
+ # The 'next' link from the example below is also ommitted, pagination is an aspect
10
+ # that will be implemented generically
11
+
12
+ # Example from the specification, approximated with Yaks below
13
+
14
+ # {
15
+ # "_links": {
16
+ # "self": { "href": "/orders" },
17
+ # "curies": [{ "name": "ea", "href": "http://example.com/docs/rels/{rel}", "templated": true }],
18
+ # "next": { "href": "/orders?page=2" },
19
+ # "ea:find": { "href": "/orders{?id}", "templated": true },
20
+ # "ea:admin": [{
21
+ # "href": "/admins/2",
22
+ # "title": "Fred"
23
+ # }, {
24
+ # "href": "/admins/5",
25
+ # "title": "Kate"
26
+ # }]
27
+ # },
28
+ # "currentlyProcessing": 14,
29
+ # "shippedToday": 20,
30
+ # "_embedded": {
31
+ # "ea:order": [{
32
+ # "_links": {
33
+ # "self": { "href": "/orders/123" },
34
+ # "ea:basket": { "href": "/baskets/98712" },
35
+ # "ea:customer": { "href": "/customers/7809" }
36
+ # },
37
+ # "total": 30.00,
38
+ # "currency": "USD",
39
+ # "status": "shipped"
40
+ # }, {
41
+ # "_links": {
42
+ # "self": { "href": "/orders/124" },
43
+ # "ea:basket": { "href": "/baskets/97213" },
44
+ # "ea:customer": { "href": "/customers/12369" }
45
+ # },
46
+ # "total": 20.00,
47
+ # "currency": "USD",
48
+ # "status": "processing"
49
+ # }]
50
+ # }
51
+ # }
52
+
53
+ require 'virtus'
54
+ require 'yaks'
55
+ require 'json'
56
+
57
+ class Order
58
+ include Virtus.model
59
+ attribute :id, Integer
60
+ attribute :basket_id, Integer
61
+ attribute :customer_id, Integer
62
+ attribute :total, Numeric
63
+ attribute :currency, String
64
+ attribute :status, String
65
+ end
66
+
67
+ class OrderSet
68
+ include Virtus.model
69
+ attribute :currently_processing, Integer
70
+ attribute :shipped_today, Integer
71
+ attribute :orders, Array[Order]
72
+ end
73
+
74
+ class OrderMapper < Yaks::Mapper
75
+ link :self, '/orders/{id}'
76
+ link :"ea:basket", '/baskets/{basket_id}'
77
+ link :"ea:customer", '/customers/{customer_id}'
78
+
79
+ attributes :total, :currency, :status
80
+ end
81
+
82
+ class OrderSetMapper < Yaks::Mapper
83
+ link :self, '/orders'
84
+ link :curies, 'http://example.com/docs/rels/{rel}', name: "ea", expand: false
85
+ link :"ea:find", "/orders{?id}", expand: false
86
+
87
+ attributes :currentlyProcessing, :shippedToday
88
+
89
+ has_many :orders, as: :"ea:order", mapper: OrderMapper
90
+
91
+ # Having the attributes be encoded in CamelCase is such a common
92
+ # use case we might have to make this a setting
93
+
94
+ def load_attribute(name)
95
+ super(Yaks::Util.underscore(name.to_s))
96
+ end
97
+ end
98
+
99
+ order_set = OrderSet.new(
100
+ currently_processing: 14,
101
+ shipped_today: 20,
102
+ orders: [
103
+ Order.new(
104
+ id: 123,
105
+ basket_id: 98712,
106
+ customer_id: 7809,
107
+ total: 30.00,
108
+ currency: "USD",
109
+ status: "shipped"
110
+ ),
111
+ Order.new(
112
+ id: 124,
113
+ basket_id: 97213,
114
+ customer_id: 12369,
115
+ total: 20.00,
116
+ currency: "USD",
117
+ status: "processing"
118
+ )
119
+ ]
120
+ )
121
+
122
+ resource = OrderSetMapper.new(order_set).to_resource
123
+
124
+ hal = Yaks::HalSerializer.new(resource, singular_links: [:self, :"ea:find", :"ea:basket", :"ea:customer"]).to_hal
125
+
126
+ puts JSON.dump(hal)