oat 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/LICENSE.txt +22 -0
- data/README.md +510 -0
- data/Rakefile +6 -0
- data/lib/oat/adapter.rb +36 -0
- data/lib/oat/adapters/hal.rb +29 -0
- data/lib/oat/adapters/json_api.rb +62 -0
- data/lib/oat/adapters/siren.rb +40 -0
- data/lib/oat/props.rb +21 -0
- data/lib/oat/serializer.rb +50 -0
- data/lib/oat/version.rb +3 -0
- data/lib/oat.rb +6 -0
- data/oat.gemspec +25 -0
- data/spec/adapters/hal_spec.rb +39 -0
- data/spec/adapters/json_api_spec.rb +43 -0
- data/spec/adapters/siren_spec.rb +45 -0
- data/spec/fixtures.rb +42 -0
- data/spec/serializer_spec.rb +69 -0
- data/spec/spec_helper.rb +3 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 265248fecbfa3adf9fae15b6c9124a452fe8f285
|
4
|
+
data.tar.gz: aac3cc104b933d261d8f8ca858e038e5e7cef835
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29f80fbffde75401c81cf69de1f59a5f1091d173abddf404c902ce90ada9c8060b950cfd56a3cc713daf5e961cac417376eb8e68d8ebeae169bfa5bfcbb9a2e9
|
7
|
+
data.tar.gz: fa5ecb844b738eed0344bc513ae083f2928e40eef0099c3e8bd282d0b466e1727cd23598577eb080977a06050e3d6c58764b387d2b5be0565683fea9d51a9985
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2013 Ismael Celis
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Ismael Celis
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,510 @@
|
|
1
|
+
# Oat [](https://travis-ci.org/ismasan/oat)
|
2
|
+
|
3
|
+
Adapters-based API serializers with Hypermedia support for Ruby apps.
|
4
|
+
|
5
|
+
## What
|
6
|
+
|
7
|
+
Oat lets you design your API payloads succinctly while conforming to your *media type* of choice (hypermedia or not).
|
8
|
+
The details of the media type are dealt with by pluggable adapters.
|
9
|
+
|
10
|
+
Oat ships with adapters for HAL, Siren and JsonAPI, and it's easy to write your own.
|
11
|
+
|
12
|
+
## Serializers
|
13
|
+
|
14
|
+
A serializer describes one or more of your API's *entities*.
|
15
|
+
|
16
|
+
You extend from [Oat::Serializer](https://github.com/ismasan/oat/blob/master/lib/oat/serializer.rb) to define your own serializers.
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
require 'oat/adapters/hal'
|
20
|
+
class ProductSerializer < Oat::Serializer
|
21
|
+
adapter Oat::Adapters::HAL
|
22
|
+
|
23
|
+
schema do
|
24
|
+
type "product"
|
25
|
+
link :self, href: product_url(item)
|
26
|
+
properties do |props|
|
27
|
+
props.title item.title
|
28
|
+
props.price item.price
|
29
|
+
props.description item.blurb
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
Then in your app (for example a Rails controller)
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
product = Product.find(params[:id])
|
40
|
+
render json: ProductSerializer.new(product)
|
41
|
+
```
|
42
|
+
|
43
|
+
Serializers require a single object as argument, which can be a model instance, a presenter or any other domain object.
|
44
|
+
|
45
|
+
The full serializer signature is `item`, `context`, `adapter_class`.
|
46
|
+
|
47
|
+
* `item` a model or presenter instance. It is available in your serializer's schema as `item`.
|
48
|
+
* `context` (optional) a context object or hash that is passed to the serializer and sub-serializers as the `context` variable. Useful if you need to pass request-specific data.
|
49
|
+
* `adapter_class` (optional) A serializer's adapter can be configured at class-level or passed here to the initializer. Useful if you want to switch adapters based on request data. More on this below.
|
50
|
+
|
51
|
+
## Adapters
|
52
|
+
|
53
|
+
Using the included [HAL](http://stateless.co/hal_specification.html) adapter, the `ProductSerializer` above would render the following JSON:
|
54
|
+
|
55
|
+
```json
|
56
|
+
{
|
57
|
+
"_links": {
|
58
|
+
"self": {"href": "http://example.com/products/1"}
|
59
|
+
},
|
60
|
+
"title": "Some product",
|
61
|
+
"price": 1000,
|
62
|
+
"description": "..."
|
63
|
+
}
|
64
|
+
```
|
65
|
+
|
66
|
+
You can easily swap adapters. The same `ProductSerializer`, this time using the [Siren](https://github.com/kevinswiber/siren) adapter:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
adapter Oat::Adapters::Siren
|
70
|
+
```
|
71
|
+
|
72
|
+
... Renders this JSON:
|
73
|
+
|
74
|
+
```json
|
75
|
+
{
|
76
|
+
"class": ["product"],
|
77
|
+
"links": [
|
78
|
+
{ "rel": [ "self" ], "href": "http://example.com/products/1" }
|
79
|
+
],
|
80
|
+
"properties": {
|
81
|
+
"title": "Some product",
|
82
|
+
"price": 1000,
|
83
|
+
"description": "..."
|
84
|
+
}
|
85
|
+
}
|
86
|
+
```
|
87
|
+
At the moment Oat ships with adapters for [HAL](http://stateless.co/hal_specification.html), [Siren](https://github.com/kevinswiber/siren) and [JsonAPI](http://jsonapi.org/), but it's easy to write your own.
|
88
|
+
|
89
|
+
Note: Oat adapters are not *required* by default. Your code should explicitely require the ones it needs:
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
# HAL
|
93
|
+
require 'oat/adapters/hal'
|
94
|
+
# Siren
|
95
|
+
require 'oat/adapters/siren'
|
96
|
+
# JsonAPI
|
97
|
+
require 'oat/adapters/json_api'
|
98
|
+
```
|
99
|
+
|
100
|
+
## Switching adapters dynamically
|
101
|
+
|
102
|
+
Adapters can also be passed as an argument to serializer instances.
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
ProductSerializer.new(product, nil, Oat::Adapters::HAL)
|
106
|
+
```
|
107
|
+
|
108
|
+
That means that your app could switch adapters on run time depending, for example, on the request's `Accept` header or anything you need.
|
109
|
+
|
110
|
+
Note: a different library could be written to make adapter-switching auto-magical for different frameworks, for example using [Responders](http://api.rubyonrails.org/classes/ActionController/Responder.html) in Rails.
|
111
|
+
|
112
|
+
## Nested serializers
|
113
|
+
|
114
|
+
It's common for a media type to include "embedded" entities within a payload. For example an `account` entity may have many `users`. An Oat serializer can inline such relationships:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
class AccountSerializer < Oat::Serializer
|
118
|
+
adapter Oat::Adapters::HAL
|
119
|
+
|
120
|
+
schema do
|
121
|
+
property :id, item.id
|
122
|
+
property :status, item.status
|
123
|
+
# user entities
|
124
|
+
entities :users, item.users do |user, user_serializer|
|
125
|
+
user_serializer.name user.name
|
126
|
+
user_serializer.email user.email
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
Another, more reusable option is to use a nested serializer. Instead of a block, you pass another serializer class that will handle serializing `user` entities.
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
class AccountSerializer < Oat::Serializer
|
136
|
+
adapter Oat::Adapters::HAL
|
137
|
+
|
138
|
+
schema do
|
139
|
+
property :id, item.id
|
140
|
+
property :status, item.status
|
141
|
+
# user entities
|
142
|
+
entities :users, item.users, UserSerializer
|
143
|
+
end
|
144
|
+
end
|
145
|
+
```
|
146
|
+
|
147
|
+
And the `UserSerializer` may look like this:
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
class UserSerializer < Oat::Serializer
|
151
|
+
adapter Oat::Adapters::HAL
|
152
|
+
|
153
|
+
schema do
|
154
|
+
property :name, item.name
|
155
|
+
property :email, item.name
|
156
|
+
end
|
157
|
+
end
|
158
|
+
```
|
159
|
+
|
160
|
+
In the user serializer, `item` refers to the user instance being wrapped by the serializer.
|
161
|
+
|
162
|
+
The bundled hypermedia adapters ship with an `entities` method to add arrays of entities, and an `entity` method to add a single entity.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
# single entity
|
166
|
+
entity :child, item.child do |child, s|
|
167
|
+
s.name child.name
|
168
|
+
s.id child.id
|
169
|
+
end
|
170
|
+
|
171
|
+
# list of entities
|
172
|
+
entities :children, item.children do |child, s|
|
173
|
+
s.name child.name
|
174
|
+
s.id child.id
|
175
|
+
end
|
176
|
+
```
|
177
|
+
|
178
|
+
Both can be expressed using a separate serializer:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# single entity
|
182
|
+
entity :child, item.child, ChildSerializer
|
183
|
+
|
184
|
+
# list of entities
|
185
|
+
entities :children, item.children, ChildSerializer
|
186
|
+
```
|
187
|
+
|
188
|
+
The way sub-entities are rendered in the final payload is up to the adapter. In HAL the example above would be:
|
189
|
+
|
190
|
+
```json
|
191
|
+
{
|
192
|
+
...,
|
193
|
+
"_embedded": {
|
194
|
+
"child": {"name": "child's name", "id": 1},
|
195
|
+
"children": [
|
196
|
+
{"name": "child 2 name", "id": 2},
|
197
|
+
{"name": "child 3 name", "id": 3},
|
198
|
+
...
|
199
|
+
]
|
200
|
+
}
|
201
|
+
}
|
202
|
+
```
|
203
|
+
|
204
|
+
## Subclassing
|
205
|
+
|
206
|
+
Serializers can be subclassed, for example if you want all your serializers to share the same adapter or add shared helper methods.
|
207
|
+
|
208
|
+
```ruby
|
209
|
+
class MyAppSerializer < Oat::Serializer
|
210
|
+
adapter Oat::Adapters::HAL
|
211
|
+
|
212
|
+
protected
|
213
|
+
|
214
|
+
def format_price(price)
|
215
|
+
Money.new(price, 'GBP').format
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class ProductSerializer < MyAppSerializer
|
222
|
+
schema do
|
223
|
+
property :title, item.title
|
224
|
+
property :price, format_price(item.price)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
This is useful if you want your serializers to better express your app's domain. For example, a serializer for a social app:
|
230
|
+
|
231
|
+
```ruby
|
232
|
+
class UserSerializer < SocialSerializer
|
233
|
+
schema do
|
234
|
+
name item.name
|
235
|
+
email item.email
|
236
|
+
# friend entities
|
237
|
+
friends item.friends
|
238
|
+
end
|
239
|
+
end
|
240
|
+
```
|
241
|
+
|
242
|
+
The superclass defines the methods `name`, `email` and `friends`, which in turn delegate to the adapter's setters.
|
243
|
+
|
244
|
+
```ruby
|
245
|
+
class SocialSerializer < Oat::Serializer
|
246
|
+
adapter Oat::Adapters::HAL # or whatever
|
247
|
+
|
248
|
+
# friendly setters
|
249
|
+
protected
|
250
|
+
|
251
|
+
def name(value)
|
252
|
+
property :name, value
|
253
|
+
end
|
254
|
+
|
255
|
+
def email(value)
|
256
|
+
property :email, value
|
257
|
+
end
|
258
|
+
|
259
|
+
def friends(objects)
|
260
|
+
entities :friends, objects, FriendSerializer
|
261
|
+
end
|
262
|
+
end
|
263
|
+
```
|
264
|
+
|
265
|
+
## URLs
|
266
|
+
|
267
|
+
Hypermedia is all about the URLs linking your resources together. Oat adapters can have methods to declare links in your entity schemae but it's up to your code/framework how to create those links.
|
268
|
+
A simple stand-alone implementation could be:
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
class ProductSerializer < Oat::Serializer
|
272
|
+
adapter Oat::Adapters::HAL
|
273
|
+
|
274
|
+
schema do
|
275
|
+
link :self, href: product_url(item.id)
|
276
|
+
...
|
277
|
+
end
|
278
|
+
|
279
|
+
protected
|
280
|
+
|
281
|
+
# helper URL method
|
282
|
+
def product_url(id)
|
283
|
+
"https://api.com/products/#{id}"
|
284
|
+
end
|
285
|
+
end
|
286
|
+
```
|
287
|
+
|
288
|
+
In frameworks like Rails, you'll probably want to use the URL helpers created by the `routes.rb` file. Two options:
|
289
|
+
|
290
|
+
### Pass a context object to serializers
|
291
|
+
|
292
|
+
You can pass a context object as second argument to serializers. This object will be passed to nested serializers too. For example, you can pass the controller instance itself.
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
# users_controller.rb
|
296
|
+
|
297
|
+
def show
|
298
|
+
user = User.find(params[:id])
|
299
|
+
render json: UserSerializer.new(user, self)
|
300
|
+
end
|
301
|
+
```
|
302
|
+
|
303
|
+
Then, in the `UserSerializer`:
|
304
|
+
```ruby
|
305
|
+
class ProductSerializer < Oat::Serializer
|
306
|
+
adapter Oat::Adapters::HAL
|
307
|
+
|
308
|
+
schema do
|
309
|
+
# `context` is the controller, which responds to URL helpers.
|
310
|
+
link :self, href: context.product_url(item)
|
311
|
+
...
|
312
|
+
end
|
313
|
+
end
|
314
|
+
```
|
315
|
+
|
316
|
+
### Mixin Rails' routing module
|
317
|
+
|
318
|
+
Alternatively, you can mix in Rails routing helpers directly into your serializers.
|
319
|
+
|
320
|
+
```ruby
|
321
|
+
class MyAppParentSerializer < Oat::Serializer
|
322
|
+
include ActionDispatch::Routing::UrlFor
|
323
|
+
include Rails.application.routes.url_helpers
|
324
|
+
def self.default_url_options
|
325
|
+
Rails.application.routes.default_url_options
|
326
|
+
end
|
327
|
+
|
328
|
+
adapter Oat::Adapters::HAL
|
329
|
+
end
|
330
|
+
```
|
331
|
+
|
332
|
+
Then your serializer sub-classes can just use the URL helpers
|
333
|
+
|
334
|
+
```ruby
|
335
|
+
class ProductSerializer < MyAppParentSerializer
|
336
|
+
schema do
|
337
|
+
# `product_url` is mixed in from Rails' routing system.
|
338
|
+
link :self, href: product_url(item)
|
339
|
+
...
|
340
|
+
end
|
341
|
+
end
|
342
|
+
```
|
343
|
+
|
344
|
+
However, since serializers don't have access to the current request, for this to work you must configure each environment's base host. In `config/environments/production.rb`:
|
345
|
+
|
346
|
+
```ruby
|
347
|
+
config.after_initialize do
|
348
|
+
Rails.application.routes.default_url_options[:host] = 'api.com'
|
349
|
+
end
|
350
|
+
```
|
351
|
+
|
352
|
+
NOTE: Rails URL helpers could be handled by a separate oat-rails gem.
|
353
|
+
|
354
|
+
## Custom adapters.
|
355
|
+
|
356
|
+
An adapter's primary concern is to abstract away the details of specific media types.
|
357
|
+
|
358
|
+
Methods defined in an adapter are exposed as `schema` setters in your serializers.
|
359
|
+
Ideally different adapters should expose the same methods so your serializers can switch adapters without loosing compatibility. For example all bundled adapters expose the following methods:
|
360
|
+
|
361
|
+
* `type` The type of the entity. Renders as "class" in Siren, root node name in JsonAPI, not used in HAL.
|
362
|
+
* `link` Add a link with `rel` and `href`. Renders inside "_links" in HAL, "links" in Siren and JsonAP.
|
363
|
+
* `property` Add a property to the entity. Top level attributes in HAL and JsonAPI, "properties" node in Siren.
|
364
|
+
* `properties` Yield a properties object to set many properties at once.
|
365
|
+
* `entity` Add a single sub-entity. "_embedded" node in HAL, "entities" in Siren, "linked" in JsonAPI.
|
366
|
+
* `entities` Add a collection of sub-entities.
|
367
|
+
|
368
|
+
You can define these in your own custom adapters if you're using your own media type or need to implement a different spec.
|
369
|
+
|
370
|
+
```ruby
|
371
|
+
class CustomAdapter < Oat::Adapter
|
372
|
+
|
373
|
+
def type(*types)
|
374
|
+
data[:rel] = types
|
375
|
+
end
|
376
|
+
|
377
|
+
def property(name, value)
|
378
|
+
data[:attr][name] = value
|
379
|
+
end
|
380
|
+
|
381
|
+
def entity(name, obj, serializer_class = nil, &block)
|
382
|
+
data[:nested_documents] = serializer_from_block_or_class(obj, serializer_class, &block)
|
383
|
+
end
|
384
|
+
|
385
|
+
... etc
|
386
|
+
end
|
387
|
+
```
|
388
|
+
|
389
|
+
An adapter class provides a `data` object (just a Hash) that stores your data in the structure you want. An adapter's public methods are exposed to your serializers.
|
390
|
+
|
391
|
+
## Unconventional or domain specific adapters
|
392
|
+
|
393
|
+
Although adapters should in general comply with a common interface, you can still create your own domain-specific adapters if you need to.
|
394
|
+
|
395
|
+
Let's say you're working on a media-type specification specializing in describing social networks and want your payload definitions to express the concept of "friendship". You want your serializers to look like:
|
396
|
+
|
397
|
+
```ruby
|
398
|
+
class UserSerializer < Oat::Serializer
|
399
|
+
adapter SocialAdapter
|
400
|
+
|
401
|
+
schema do
|
402
|
+
name item.name
|
403
|
+
email item.email
|
404
|
+
|
405
|
+
# Friend entity
|
406
|
+
friends item.friends do |friend, friend_serializer|
|
407
|
+
friend_serializer.name friend.name
|
408
|
+
friend_serializer.email friend.email
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
```
|
413
|
+
|
414
|
+
A custom media type could return JSON looking looking like this:
|
415
|
+
|
416
|
+
```json
|
417
|
+
{
|
418
|
+
"name": "Joe",
|
419
|
+
"email": "joe@email.com",
|
420
|
+
"friends": [
|
421
|
+
{"name": "Jane", "email":"jane@email.com"},
|
422
|
+
...
|
423
|
+
]
|
424
|
+
}
|
425
|
+
```
|
426
|
+
|
427
|
+
The adapter for that would be:
|
428
|
+
|
429
|
+
```ruby
|
430
|
+
class SocialAdapter < Oat::Adapter
|
431
|
+
|
432
|
+
def name(value)
|
433
|
+
data[:name] = value
|
434
|
+
end
|
435
|
+
|
436
|
+
def email(value)
|
437
|
+
data[:email] = value
|
438
|
+
end
|
439
|
+
|
440
|
+
def friends(friend_list, serializer_class = nil, &block)
|
441
|
+
data[:friends] = friend_list.map do |obj|
|
442
|
+
serializer_from_block_or_class(obj, serializer_class, &block)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
```
|
447
|
+
|
448
|
+
But you can easily write an adapter that turns your domain-specific serializers into HAL-compliant JSON.
|
449
|
+
|
450
|
+
```ruby
|
451
|
+
class SocialHalAdapter < Oat::Adapters::HAL
|
452
|
+
|
453
|
+
def name(value)
|
454
|
+
property :name, value
|
455
|
+
end
|
456
|
+
|
457
|
+
def email(value)
|
458
|
+
property :email, value
|
459
|
+
end
|
460
|
+
|
461
|
+
def friends(friend_list, serializer_class = nil, &block)
|
462
|
+
entities :friends, friend_list, serializer_class, &block
|
463
|
+
end
|
464
|
+
end
|
465
|
+
```
|
466
|
+
|
467
|
+
The result for the SocialHalAdapter is:
|
468
|
+
|
469
|
+
```json
|
470
|
+
{
|
471
|
+
"name": "Joe",
|
472
|
+
"email": "joe@email.com",
|
473
|
+
"_embedded": {
|
474
|
+
"friends": [
|
475
|
+
{"name": "Jane", "email":"jane@email.com"},
|
476
|
+
...
|
477
|
+
]
|
478
|
+
}
|
479
|
+
}
|
480
|
+
```
|
481
|
+
|
482
|
+
You can take a look at [the built-in Hypermedia adapters](https://github.com/ismasan/oat/tree/master/lib/oat/adapters) for guidance.
|
483
|
+
|
484
|
+
## Installation
|
485
|
+
|
486
|
+
Add this line to your application's Gemfile:
|
487
|
+
|
488
|
+
gem 'oat'
|
489
|
+
|
490
|
+
And then execute:
|
491
|
+
|
492
|
+
$ bundle
|
493
|
+
|
494
|
+
Or install it yourself as:
|
495
|
+
|
496
|
+
$ gem install oat
|
497
|
+
|
498
|
+
## TODO / contributions welcome
|
499
|
+
|
500
|
+
* Siren actions.
|
501
|
+
* JsonAPI URL and ID modes, top-level links
|
502
|
+
* testing module that can be used for testing spec-compliance in user apps?
|
503
|
+
|
504
|
+
## Contributing
|
505
|
+
|
506
|
+
1. Fork it
|
507
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
508
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
509
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
510
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/lib/oat/adapter.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'oat/props'
|
2
|
+
module Oat
|
3
|
+
class Adapter
|
4
|
+
|
5
|
+
def initialize(serializer)
|
6
|
+
@serializer = serializer
|
7
|
+
@data = Hash.new{|h,k| h[k] = {}}
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
data
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
attr_reader :data, :serializer
|
17
|
+
|
18
|
+
def yield_props(&block)
|
19
|
+
props = Props.new
|
20
|
+
serializer.instance_exec(props, &block)
|
21
|
+
props.to_hash
|
22
|
+
end
|
23
|
+
|
24
|
+
def serializer_from_block_or_class(obj, serializer_class = nil, &block)
|
25
|
+
if block_given?
|
26
|
+
serializer_class = Class.new(serializer.class)
|
27
|
+
serializer_class.adapter self.class
|
28
|
+
s = serializer_class.new(obj, serializer.context, serializer.adapter_class, serializer.top)
|
29
|
+
serializer.top.instance_exec(obj, s, &block)
|
30
|
+
s.to_hash
|
31
|
+
else
|
32
|
+
serializer_class.new(obj, serializer.context, serializer.adapter_class).to_hash
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# http://stateless.co/hal_specification.html
|
2
|
+
module Oat
|
3
|
+
module Adapters
|
4
|
+
class HAL < Oat::Adapter
|
5
|
+
def link(rel, opts = {})
|
6
|
+
data[:_links][rel] = opts
|
7
|
+
end
|
8
|
+
|
9
|
+
def properties(&block)
|
10
|
+
data.merge! yield_props(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def property(key, value)
|
14
|
+
data[key] = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def entity(name, obj, serializer_class = nil, &block)
|
18
|
+
data[:_embedded][name] = serializer_from_block_or_class(obj, serializer_class, &block)
|
19
|
+
end
|
20
|
+
|
21
|
+
def entities(name, collection, serializer_class = nil, &block)
|
22
|
+
data[:_embedded][name] = collection.map do |obj|
|
23
|
+
serializer_from_block_or_class(obj, serializer_class, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# http://jsonapi.org/format/#url-based-json-api
|
2
|
+
require 'active_support/core_ext/string/inflections'
|
3
|
+
module Oat
|
4
|
+
module Adapters
|
5
|
+
class JsonAPI < Oat::Adapter
|
6
|
+
|
7
|
+
def initialize(*args)
|
8
|
+
super
|
9
|
+
@entities = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def type(*types)
|
13
|
+
@root_name = types.first.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def link(rel, opts = {})
|
17
|
+
data[:links][rel] = opts[:href]
|
18
|
+
end
|
19
|
+
|
20
|
+
def properties(&block)
|
21
|
+
data.merge! yield_props(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def property(key, value)
|
25
|
+
data[key] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def entity(name, obj, serializer_class = nil, &block)
|
29
|
+
ent = entity_without_root(obj, serializer_class, &block)
|
30
|
+
link name, href: ent[:id]
|
31
|
+
(@entities[name.to_s.pluralize.to_sym] ||= []) << ent
|
32
|
+
end
|
33
|
+
|
34
|
+
def entities(name, collection, serializer_class = nil, &block)
|
35
|
+
link_name = name.to_s.pluralize.to_sym
|
36
|
+
data[:links][link_name] = []
|
37
|
+
|
38
|
+
collection.each do |obj|
|
39
|
+
ent = entity_without_root(obj, serializer_class, &block)
|
40
|
+
data[:links][link_name] << ent[:id]
|
41
|
+
(@entities[link_name] ||= []) << ent
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
raise "JSON API entities MUST define a type. Use type 'user' in your serializers" unless root_name
|
47
|
+
h = {root_name.pluralize.to_sym => [data]}
|
48
|
+
h[:linked] = @entities if @entities.keys.any?
|
49
|
+
h
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
attr_reader :root_name
|
55
|
+
|
56
|
+
def entity_without_root(obj, serializer_class = nil, &block)
|
57
|
+
serializer_from_block_or_class(obj, serializer_class, &block).values.first.first
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# https://github.com/kevinswiber/siren
|
2
|
+
module Oat
|
3
|
+
module Adapters
|
4
|
+
class Siren < Oat::Adapter
|
5
|
+
|
6
|
+
def initialize(*args)
|
7
|
+
super
|
8
|
+
data[:links] = []
|
9
|
+
data[:entities] = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def type(*types)
|
13
|
+
data[:class] = types
|
14
|
+
end
|
15
|
+
|
16
|
+
def link(rel, opts = {})
|
17
|
+
data[:links] << {rel: [rel]}.merge(opts)
|
18
|
+
end
|
19
|
+
|
20
|
+
def properties(&block)
|
21
|
+
data[:properties].merge! yield_props(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def property(key, value)
|
25
|
+
data[:properties][key] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def entity(name, obj, serializer_class = nil, &block)
|
29
|
+
data[:entities] << serializer_from_block_or_class(obj, serializer_class, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
def entities(name, collection, serializer_class = nil, &block)
|
33
|
+
collection.each do |obj|
|
34
|
+
entity name, obj, serializer_class, &block
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/oat/props.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module Oat
|
2
|
+
class Props < BasicObject
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@attributes = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def _from(data)
|
9
|
+
@attributes = data.to_hash
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, value)
|
13
|
+
@attributes[name] = value
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
@attributes
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
module Oat
|
3
|
+
class Serializer
|
4
|
+
|
5
|
+
class_attribute :_adapter, :logger
|
6
|
+
|
7
|
+
def self.schema(&block)
|
8
|
+
@schema = block if block_given?
|
9
|
+
@schema || Proc.new{}
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.adapter(adapter_class = nil)
|
13
|
+
self._adapter = adapter_class if adapter_class
|
14
|
+
self._adapter
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.warn(msg)
|
18
|
+
logger ? logger.warning(msg) : puts(msg)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :item, :context, :adapter_class, :adapter
|
22
|
+
|
23
|
+
def initialize(item, context = nil, _adapter_class = nil, parent_serializer = nil)
|
24
|
+
@item, @context = item, context
|
25
|
+
@parent_serializer = parent_serializer
|
26
|
+
@adapter_class = _adapter_class || self.class.adapter
|
27
|
+
@adapter = @adapter_class.new(self)
|
28
|
+
end
|
29
|
+
|
30
|
+
def top
|
31
|
+
@top ||= @parent_serializer || self
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(name, *args, &block)
|
35
|
+
if adapter.respond_to?(name)
|
36
|
+
adapter.send(name, *args, &block)
|
37
|
+
else
|
38
|
+
self.class.warn "[#{adapter.class}] does not implement ##{name}. Called with #{args}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_hash
|
43
|
+
@to_hash ||= (
|
44
|
+
self.instance_eval &self.class.schema
|
45
|
+
adapter.to_hash
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
data/lib/oat/version.rb
ADDED
data/lib/oat.rb
ADDED
data/oat.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'oat/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "oat"
|
8
|
+
spec.version = Oat::VERSION
|
9
|
+
spec.authors = ["Ismael Celis"]
|
10
|
+
spec.email = ["ismaelct@gmail.com"]
|
11
|
+
spec.description = %q{Oat helps you separate your API schema definitions from the underlying media type. Media types can be plugged or swapped on demand globally or on the content-negotiation phase}
|
12
|
+
spec.summary = %q{Adapters-based serializers with Hypermedia support}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "activesupport", ">= 4.0"
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'oat/adapters/hal'
|
3
|
+
|
4
|
+
describe Oat::Adapters::HAL do
|
5
|
+
|
6
|
+
include Fixtures
|
7
|
+
|
8
|
+
subject{ serializer_class.new(user, {name: 'some_controller'}, Oat::Adapters::HAL) }
|
9
|
+
|
10
|
+
describe '#to_hash' do
|
11
|
+
it 'produces a HAL-compliant hash' do
|
12
|
+
subject.to_hash.tap do |h|
|
13
|
+
# properties
|
14
|
+
h[:id].should == user.id
|
15
|
+
h[:name].should == user.name
|
16
|
+
h[:age].should == user.age
|
17
|
+
h[:controller_name].should == 'some_controller'
|
18
|
+
# links
|
19
|
+
h[:_links][:self][:href].should == "http://foo.bar.com/#{user.id}"
|
20
|
+
# embedded manager
|
21
|
+
h[:_embedded][:manager].tap do |m|
|
22
|
+
m[:id].should == manager.id
|
23
|
+
m[:name].should == manager.name
|
24
|
+
m[:age].should == manager.age
|
25
|
+
m[:_links][:self][:href].should == "http://foo.bar.com/#{manager.id}"
|
26
|
+
end
|
27
|
+
# embedded friends
|
28
|
+
h[:_embedded][:friends].size.should == 1
|
29
|
+
h[:_embedded][:friends][0].tap do |f|
|
30
|
+
f[:id].should == friend.id
|
31
|
+
f[:name].should == friend.name
|
32
|
+
f[:age].should == friend.age
|
33
|
+
f[:controller_name].should == 'some_controller'
|
34
|
+
f[:_links][:self][:href].should == "http://foo.bar.com/#{friend.id}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'oat/adapters/json_api'
|
3
|
+
|
4
|
+
describe Oat::Adapters::JsonAPI do
|
5
|
+
|
6
|
+
include Fixtures
|
7
|
+
|
8
|
+
subject{ serializer_class.new(user, {name: 'some_controller'}, Oat::Adapters::JsonAPI) }
|
9
|
+
|
10
|
+
describe '#to_hash' do
|
11
|
+
it 'produces a JSON-API compliant hash' do
|
12
|
+
payload = subject.to_hash
|
13
|
+
# embedded friends
|
14
|
+
payload[:linked][:friends][0].tap do |f|
|
15
|
+
f[:id].should == friend.id
|
16
|
+
f[:name].should == friend.name
|
17
|
+
f[:age].should == friend.age
|
18
|
+
f[:controller_name].should == 'some_controller'
|
19
|
+
f[:links][:self].should == "http://foo.bar.com/#{friend.id}"
|
20
|
+
end
|
21
|
+
|
22
|
+
# embedded manager
|
23
|
+
payload[:linked][:managers][0].tap do |m|
|
24
|
+
m[:id].should == manager.id
|
25
|
+
m[:name].should == manager.name
|
26
|
+
m[:age].should == manager.age
|
27
|
+
m[:links][:self].should == "http://foo.bar.com/#{manager.id}"
|
28
|
+
end
|
29
|
+
|
30
|
+
payload[:users][0].tap do |h|
|
31
|
+
h[:id].should == user.id
|
32
|
+
h[:name].should == user.name
|
33
|
+
h[:age].should == user.age
|
34
|
+
h[:controller_name].should == 'some_controller'
|
35
|
+
# links
|
36
|
+
h[:links][:self].should == "http://foo.bar.com/#{user.id}"
|
37
|
+
# these links are added by embedding entities
|
38
|
+
h[:links][:manager].should == manager.id
|
39
|
+
h[:links][:friends].should == [friend.id]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'oat/adapters/siren'
|
3
|
+
|
4
|
+
describe Oat::Adapters::Siren do
|
5
|
+
|
6
|
+
include Fixtures
|
7
|
+
|
8
|
+
subject{ serializer_class.new(user, {name: 'some_controller'}, Oat::Adapters::Siren) }
|
9
|
+
|
10
|
+
describe '#to_hash' do
|
11
|
+
it 'produces a Siren-compliant hash' do
|
12
|
+
subject.to_hash.tap do |h|
|
13
|
+
#siren class
|
14
|
+
h[:class].should == ['user']
|
15
|
+
# properties
|
16
|
+
h[:properties][:id].should == user.id
|
17
|
+
h[:properties][:name].should == user.name
|
18
|
+
h[:properties][:age].should == user.age
|
19
|
+
h[:properties][:controller_name].should == 'some_controller'
|
20
|
+
# links
|
21
|
+
h[:links][0][:rel].should == [:self]
|
22
|
+
h[:links][0][:href].should == "http://foo.bar.com/#{user.id}"
|
23
|
+
# embedded manager
|
24
|
+
h[:entities][1].tap do |m|
|
25
|
+
m[:class].should == ['manager']
|
26
|
+
m[:properties][:id].should == manager.id
|
27
|
+
m[:properties][:name].should == manager.name
|
28
|
+
m[:properties][:age].should == manager.age
|
29
|
+
m[:links][0][:rel].should == [:self]
|
30
|
+
m[:links][0][:href].should == "http://foo.bar.com/#{manager.id}"
|
31
|
+
end
|
32
|
+
# embedded friends
|
33
|
+
h[:entities][0].tap do |f|
|
34
|
+
f[:class].should == ['user']
|
35
|
+
f[:properties][:id].should == friend.id
|
36
|
+
f[:properties][:name].should == friend.name
|
37
|
+
f[:properties][:age].should == friend.age
|
38
|
+
f[:properties][:controller_name].should == 'some_controller'
|
39
|
+
f[:links][0][:rel].should == [:self]
|
40
|
+
f[:links][0][:href].should == "http://foo.bar.com/#{friend.id}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/spec/fixtures.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module Fixtures
|
2
|
+
|
3
|
+
def self.included(base)
|
4
|
+
base.let(:user_class) { Struct.new(:name, :age, :id, :friends, :manager) }
|
5
|
+
base.let(:friend) { user_class.new('Joe', 33, 2, []) }
|
6
|
+
base.let(:manager) { user_class.new('Jane', 29, 3, []) }
|
7
|
+
base.let(:user) { user_class.new('Ismael', 35, 1, [friend], manager) }
|
8
|
+
base.let(:serializer_class) do
|
9
|
+
Class.new(Oat::Serializer) do
|
10
|
+
klass = self
|
11
|
+
|
12
|
+
schema do
|
13
|
+
type 'user'
|
14
|
+
link :self, href: url_for(item.id)
|
15
|
+
|
16
|
+
property :id, item.id
|
17
|
+
properties do |attrs|
|
18
|
+
attrs.name item.name
|
19
|
+
attrs.age item.age
|
20
|
+
attrs.controller_name context[:name]
|
21
|
+
end
|
22
|
+
|
23
|
+
entities :friends, item.friends, klass
|
24
|
+
|
25
|
+
entity :manager, item.manager do |manager, s|
|
26
|
+
s.type 'manager'
|
27
|
+
s.link :self, href: url_for(manager.id)
|
28
|
+
s.properties do |attrs|
|
29
|
+
attrs.id manager.id
|
30
|
+
attrs.name manager.name
|
31
|
+
attrs.age manager.age
|
32
|
+
end
|
33
|
+
end if item.manager
|
34
|
+
end
|
35
|
+
|
36
|
+
def url_for(id)
|
37
|
+
"http://foo.bar.com/#{id}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Oat::Serializer do
|
4
|
+
|
5
|
+
before do
|
6
|
+
@adapter_class = Class.new(Oat::Adapter) do
|
7
|
+
def attributes(&block)
|
8
|
+
data[:attributes].merge!(yield_props(&block))
|
9
|
+
end
|
10
|
+
|
11
|
+
def attribute(key, value)
|
12
|
+
data[:attributes][key] = value
|
13
|
+
end
|
14
|
+
|
15
|
+
def link(rel, url)
|
16
|
+
data[:links][rel] = url
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
@sc = Class.new(Oat::Serializer) do
|
21
|
+
|
22
|
+
schema do
|
23
|
+
my_attribute 'Hello'
|
24
|
+
attribute :id, item.id
|
25
|
+
attributes do |attrs|
|
26
|
+
attrs.name item.name
|
27
|
+
attrs.age item.age
|
28
|
+
attrs.controller_name context[:name]
|
29
|
+
end
|
30
|
+
link :self, url_for(item.id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def url_for(id)
|
34
|
+
"http://foo.bar.com/#{id}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def my_attribute(value)
|
38
|
+
attribute :special, value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@sc.adapter @adapter_class
|
43
|
+
end
|
44
|
+
|
45
|
+
let(:user_class) do
|
46
|
+
Struct.new(:name, :age, :id, :friends)
|
47
|
+
end
|
48
|
+
|
49
|
+
let(:user1) { user_class.new('Ismael', 35, 1, []) }
|
50
|
+
|
51
|
+
it 'should have a version number' do
|
52
|
+
Oat::VERSION.should_not be_nil
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#to_hash' do
|
56
|
+
it 'builds Hash from item and context with attributes as defined in adapter' do
|
57
|
+
serializer = @sc.new(user1, name: 'some_controller')
|
58
|
+
serializer.to_hash.tap do |h|
|
59
|
+
h[:attributes][:special].should == 'Hello'
|
60
|
+
h[:attributes][:id].should == user1.id
|
61
|
+
h[:attributes][:name].should == user1.name
|
62
|
+
h[:attributes][:age].should == user1.age
|
63
|
+
h[:attributes][:controller_name].should == 'some_controller'
|
64
|
+
h[:links][:self].should == "http://foo.bar.com/#{user1.id}"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: oat
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ismael Celis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-11-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Oat helps you separate your API schema definitions from the underlying
|
70
|
+
media type. Media types can be plugged or swapped on demand globally or on the content-negotiation
|
71
|
+
phase
|
72
|
+
email:
|
73
|
+
- ismaelct@gmail.com
|
74
|
+
executables: []
|
75
|
+
extensions: []
|
76
|
+
extra_rdoc_files: []
|
77
|
+
files:
|
78
|
+
- .gitignore
|
79
|
+
- .rspec
|
80
|
+
- .travis.yml
|
81
|
+
- Gemfile
|
82
|
+
- LICENSE
|
83
|
+
- LICENSE.txt
|
84
|
+
- README.md
|
85
|
+
- Rakefile
|
86
|
+
- lib/oat.rb
|
87
|
+
- lib/oat/adapter.rb
|
88
|
+
- lib/oat/adapters/hal.rb
|
89
|
+
- lib/oat/adapters/json_api.rb
|
90
|
+
- lib/oat/adapters/siren.rb
|
91
|
+
- lib/oat/props.rb
|
92
|
+
- lib/oat/serializer.rb
|
93
|
+
- lib/oat/version.rb
|
94
|
+
- oat.gemspec
|
95
|
+
- spec/adapters/hal_spec.rb
|
96
|
+
- spec/adapters/json_api_spec.rb
|
97
|
+
- spec/adapters/siren_spec.rb
|
98
|
+
- spec/fixtures.rb
|
99
|
+
- spec/serializer_spec.rb
|
100
|
+
- spec/spec_helper.rb
|
101
|
+
homepage: ''
|
102
|
+
licenses:
|
103
|
+
- MIT
|
104
|
+
metadata: {}
|
105
|
+
post_install_message:
|
106
|
+
rdoc_options: []
|
107
|
+
require_paths:
|
108
|
+
- lib
|
109
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
requirements: []
|
120
|
+
rubyforge_project:
|
121
|
+
rubygems_version: 2.0.3
|
122
|
+
signing_key:
|
123
|
+
specification_version: 4
|
124
|
+
summary: Adapters-based serializers with Hypermedia support
|
125
|
+
test_files:
|
126
|
+
- spec/adapters/hal_spec.rb
|
127
|
+
- spec/adapters/json_api_spec.rb
|
128
|
+
- spec/adapters/siren_spec.rb
|
129
|
+
- spec/fixtures.rb
|
130
|
+
- spec/serializer_spec.rb
|
131
|
+
- spec/spec_helper.rb
|