jei 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ea19615f8a84331de1de9c82554319903558736d
4
- data.tar.gz: 10de19e392712355a970f2696f4e241f8937828e
3
+ metadata.gz: 7bf0d7bbd5077695a2e1219961576f894a5cd41f
4
+ data.tar.gz: 34cb6f17efada6cd82e01d23e8e67a16f7f2414e
5
5
  SHA512:
6
- metadata.gz: aa5ce85017fe71e8552c92362a71bfcf548516a80fab884bbcb752174b2e6b7aac897f75f92643db3db39ec8b42afa93559849bc466cc4c9bb43dae27e330f25
7
- data.tar.gz: 683c3c1b22720d0d1dc0ce20ead770d60664d62dc226c0ad29cf76ccb322d4fbf8477d8ea66eff557d3b228d7a863c661914a11cc77b8aa48f7bdfd621f8cc36
6
+ metadata.gz: 89a27cbfe0af409953efaf4da6d83f7a8f78659bf2ca58a40a22a1b148d8df1eb9d48a2b65e76f374b38598618a2fcf8310b425d001977ef0580a2a3186ed59b
7
+ data.tar.gz: 5972dc16d4c2df09654730607ffbfa92165751a12fc3c6284923b4516af448c987e2b7747f99c4d39bf7266bfb09a5915b0bec0b2496685049bd5388c78764e3
data/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # Changelog
2
+
3
+ ## HEAD
4
+
5
+ ## 0.2.0 (2016-03-22)
6
+
7
+ * [ADD] Add `:fields` document build option for sparse fieldsets.
8
+ * [BREAKING] A relationship links `Proc` is no longer passed the context as
9
+ an argument. Update usages of `links: ->(_) {}` to `links: -> {}`.
10
+ * [ADD] Raise `Jei::Path::NameError` on bad relationship names.
11
+ * [BREAKING] Rename relationship option `no_data` to `data`. Take the
12
+ inverse of each occurance to update, e.g., change `no_data: true` to
13
+ `data: false`.
14
+ * [ADD] Add `:serializer` option to relationships to override the default
15
+ serializer class used.
16
+ * [CHANGE] Serializers are compared via a `(type, id)` tuple rather than
17
+ object identity.
18
+ * [FIX] Fix include usage when serializing a collection.
19
+
20
+ ## 0.1.0 (2016-03-15)
21
+
22
+ * Initial release
data/README.md CHANGED
@@ -17,6 +17,7 @@ install it manually.
17
17
  ```ruby
18
18
  require 'jei'
19
19
 
20
+ # Create resource serializers.
20
21
  class ArtistSerializer < Jei::Serializer
21
22
  attribute :name
22
23
  has_many :albums
@@ -26,18 +27,15 @@ class AlbumSerializer < Jei::Serializer
26
27
  belongs_to :artist
27
28
  end
28
29
 
29
- class Artist < OpenStruct; end
30
- class Album < OpenStruct; end
31
-
32
30
  artist = Artist.new(id: 1, name: 'FIESTAR', albums: [])
33
- 2.times { |i| artist.albums << Album.new(id: i + 1) }
31
+ artist.albums << Album.new(id: 1, artist: artist)
32
+ artist.albums << Album.new(id: 2, artist: artist)
34
33
 
34
+ # Build a JSON API document from the resource.
35
35
  document = Jei::Document.build(artist)
36
36
  document.to_json
37
37
  ```
38
38
 
39
- This emits
40
-
41
39
  ```json
42
40
  {
43
41
  "data": {
@@ -57,3 +55,473 @@ This emits
57
55
  }
58
56
  }
59
57
  ```
58
+
59
+ ### Serializers
60
+
61
+ A `Serializer` defines what attributes and relationships are serialized in a
62
+ document.
63
+
64
+ Jei uses reflection to automatically find the correct serailizer for a
65
+ resource. To do this, create a class that extends `Jei::Serializer` and name it
66
+ `#{resource.class.name}Serializer`. For example, an `Artist` resource would
67
+ have a matching serializer named `ArtistSerializer` in the global namespace.
68
+
69
+ ```ruby
70
+ class Artist; end
71
+ class ArtistSerializer < Jei::Serializer; end
72
+ ```
73
+
74
+ #### Attributes
75
+
76
+ Attributes represent model data. They are defined in a serializer by using the
77
+ `attribute` or `attributes` methods.
78
+
79
+ ```ruby
80
+ class AlbumSerializer < Jei::Serializer
81
+ # Attributes can be listed by name. Each name is used as an attribute key
82
+ # and its value is invoked by its name on the resource.
83
+ attributes :kind, :name
84
+
85
+ # Attributes can also be added individually.
86
+ attribute :released_on
87
+
88
+ # This is useful because `attribute` optionally takes a block. It can be
89
+ # used to rename an attribute in the document when the resource responds to
90
+ # a different name.
91
+ attribute(:release_date) { resource.released_on }
92
+
93
+ # Or it can be use to create entirely new attributes and values.
94
+ attribute :formatted_name do
95
+ date = resource.released_on.strftime('%Y.%m.%d')
96
+ "[#{date}] #{resource.name}"
97
+ end
98
+ end
99
+
100
+ album = Album.new(id: 1, kind: :ep, name: 'A Delicate Sense', released_on: Date.new(2016, 3, 9))
101
+ Jei::Document.build(album).to_json
102
+ ```
103
+
104
+ ```json
105
+ {
106
+ "data": {
107
+ "id": "1",
108
+ "type": "albums",
109
+ "attributes": {
110
+ "kind": "ep",
111
+ "name": "A Delicate Sense",
112
+ "released_on": "2016-03-09",
113
+ "release_date": "2016-03-09",
114
+ "formatted_name": "[2016.03.09] A Delicate Sense"
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ #### Relationships
121
+
122
+ Relationships describe how the primary resource relates to other resources. A
123
+ one-to-one relationship is defined by the `belongs_to` method, and a
124
+ one-to-many relationship, `has_many`.
125
+
126
+ The relationship names are invoked on the resource. A
127
+ belongs-to relationship returns a single resource, whereas has-many returns a
128
+ collection.
129
+
130
+ ```ruby
131
+ class AlbumSerializer < Jei::Serializer
132
+ belongs_to :artist
133
+ has_many :tracks
134
+
135
+ # Like attributes, relationships can also take a block to override its value.
136
+ has_many :even_tracks do
137
+ resource.tracks.select { |t| t.id.even? }
138
+ end
139
+ end
140
+
141
+ class ArtistSerializer < Jei::Serializer; end
142
+ class TrackSerializer < Jei::Serializer; end
143
+
144
+ artist = Artist.new(id: 1)
145
+ tracks = [Track.new(id: 1), Track.new(id: 2)]
146
+ album = Album.new(id: 1, artist: artist, tracks: tracks)
147
+
148
+ Jei::Document.build(album).to_json
149
+ ```
150
+
151
+ ```json
152
+ {
153
+ "data": {
154
+ "id": "1",
155
+ "type": "albums",
156
+ "relationships": {
157
+ "artist": {
158
+ "data": { "id": "1", "type": "artists" }
159
+ },
160
+ "tracks": {
161
+ "data": [
162
+ { "id": "1", "type": "tracks" },
163
+ { "id": "2", "type": "tracks" }
164
+ ]
165
+ },
166
+ "even_tracks": {
167
+ "data": [
168
+ { "id": "2", "type": "tracks" }
169
+ ]
170
+ }
171
+ }
172
+ }
173
+ }
174
+ ```
175
+
176
+ ##### Options
177
+
178
+ Each relationship object can be modified with the following options.
179
+
180
+ * `data`: (`Boolean`; default: `true`) Setting this to `false` supresses
181
+ building a data object with resource identifiers. Note that doing so does not
182
+ emit a valid JSON API document unless a links or meta object is present.
183
+
184
+ ```ruby
185
+ class ArtistSerializer < Jei::Serializer
186
+ has_many :albums, data: false
187
+ end
188
+
189
+ albums = [Album.new(id: 1), Album.new(id: 2)]
190
+ artist = Artist.new(id: 1, albums: albums)
191
+
192
+ Jei::Document.build(artist).to_json
193
+ ```
194
+
195
+ ```json
196
+ {
197
+ "data": {
198
+ "id": "1",
199
+ "type": "artists",
200
+ "relationships": {
201
+ "albums": {}
202
+ }
203
+ }
204
+ }
205
+ ```
206
+
207
+ * `links`: (`Proc -> Array<Jei::Link>`) This is for relationship level links.
208
+ The `Proc` must return a list of `Link`s and is run in the context of the
209
+ serializer.
210
+
211
+ ```ruby
212
+ class ArtistSerializer < Jei::Serializer
213
+ has_many :albums, links: -> {
214
+ [Jei::Link.new(:related, "/#{type}/#{id}/albums")]
215
+ }
216
+ end
217
+
218
+ class AlbumSerializer < Jei::Serializer; end
219
+
220
+ albums = [Album.new(id: 1), Album.new(id: 2)]
221
+ artist = Artist.new(id: 1, albums: albums)
222
+ Jei::Document.build(artist).to_json
223
+ ```
224
+
225
+ ```json
226
+ {
227
+ "data": {
228
+ "id": "1",
229
+ "type": "artists",
230
+ "relationships": {
231
+ "albums": {
232
+ "data": [
233
+ { "id": "1", "type": "albums" },
234
+ { "id": "2", "type": "albums" }
235
+ ],
236
+ "links": {
237
+ "related": "/artists/1/albums"
238
+ }
239
+ }
240
+ }
241
+ }
242
+ }
243
+ ```
244
+
245
+ * `serializer`: (`Class`) Overrides the default serializer used for each
246
+ related resource.
247
+
248
+ ```ruby
249
+ class RecordSerializer < Jei::Serializer
250
+ def type
251
+ 'records'
252
+ end
253
+ end
254
+
255
+ class ArtistSerializer < Jei::Serializer
256
+ has_many :albums, serializer: RecordSerializer
257
+ end
258
+
259
+ artist = Artist.new(id: 1, albums: [Album.new(id: 1)])
260
+ Jei::Document.build(artist).to_json
261
+ ```
262
+
263
+ ```json
264
+ {
265
+ "data": {
266
+ "id": "1",
267
+ "type": "artists",
268
+ "relationships": {
269
+ "albums": {
270
+ "data": [
271
+ { "id": "1", "type": "records" }
272
+ ]
273
+ }
274
+ }
275
+ }
276
+ }
277
+ ```
278
+
279
+ ### Document
280
+
281
+ As seen in previous examples, `Jei::Document` represents a JSON API document.
282
+ After building the structure from a resource or collection of resources using
283
+ `Document.build`, it can be serialized to a Ruby hash (`#to_h`) or a JSON
284
+ string (`#to_json`).
285
+
286
+ #### Options
287
+
288
+ Top level objects can be added using the following options.
289
+
290
+ * `:fields`: (`Hash<String, String>`) A map of resource type-fields that define
291
+ sparse fieldsets. Keys are resource types, and fields are a comma-separated
292
+ list of field names. For example, `{ 'artists' => 'name,albums', 'albums' =>
293
+ 'released_on' }`.
294
+
295
+ ```ruby
296
+ class ArtistSerializer < Jei::Serializer
297
+ attributes :kind, :name
298
+ has_many :albums
299
+ end
300
+
301
+ artist = Artist.new(id: 1, kind: :group, name: 'FIESTAR', albums: [])
302
+
303
+ Jei::Document.build(artist, fields: { 'artists' => 'name' }).to_json
304
+ ```
305
+
306
+ ```json
307
+ {
308
+ "data": {
309
+ "id": "1",
310
+ "type": "artists",
311
+ "attributes": {
312
+ "name": "FIESTAR"
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ * `:include`: (`String`) A comma separated list of relationship paths. Each
319
+ path is a list of relationship names, separated by a period. For example, a
320
+ valid list of paths would be `artist,tracks.song`. The set of resources are
321
+ all unique resources on the include path.
322
+
323
+ ```ruby
324
+ class ArtistSerializer < Jei::Serializer
325
+ attribute :name
326
+ has_many :albums
327
+ end
328
+
329
+ class AlbumSerializer < Jei::Serializer
330
+ attributes :name, :release_date
331
+ belongs_to :artist
332
+ has_many :tracks
333
+ end
334
+
335
+ class TrackSerializer < Jei::Serializer
336
+ attributes :position, :name
337
+ belongs_to :album
338
+ end
339
+
340
+ artist = Artist.new(id: 1, name: 'FIESTAR')
341
+ album1 = Album.new(id: 1, name: 'A Delicate Sense', release_date: '2016-03-09', artist: artist)
342
+ album2 = Album.new(id: 2, name: 'Black Label', release_date: '2015-03-04', artist: artist)
343
+ artist.albums = [album1, album2]
344
+ album1.tracks = [Track.new(id: 1, position: 2, name: 'Mirror', album: album1)]
345
+ album2.tracks = [Track.new(id: 2, position: 1, name: "You're Pitiful", album: album2)]
346
+
347
+ Jei::Document.build(artist, include: 'albums.tracks').to_json
348
+ ```
349
+
350
+ ```json
351
+ {
352
+ "data": {
353
+ "id": "1",
354
+ "type": "artists",
355
+ "attributes": {
356
+ "name": "FIESTAR"
357
+ },
358
+ "relationships": {
359
+ "albums": {
360
+ "data": [
361
+ { "id": "1", "type": "albums" },
362
+ { "id": "2", "type": "albums" }
363
+ ]
364
+ }
365
+ }
366
+ },
367
+ "included": [
368
+ {
369
+ "id": "1",
370
+ "type": "albums",
371
+ "attributes": {
372
+ "name": "A Delicate Sense",
373
+ "release_date": "2016-03-09"
374
+ },
375
+ "relationships": {
376
+ "artist": {
377
+ "data": { "id": "1", "type": "artists" }
378
+ },
379
+ "tracks": {
380
+ "data": [
381
+ { "id": "1", "type": "tracks" }
382
+ ]
383
+ }
384
+ }
385
+ },
386
+ // ...
387
+ ]
388
+ }
389
+ ```
390
+
391
+ * `:jsonapi`: (`Boolean`) Includes a JSON API object in top level of the
392
+ document.
393
+
394
+ ```ruby
395
+ Jei::Document.build(nil, jsonapi: true).to_json
396
+ ```
397
+
398
+ ```json
399
+ {
400
+ "jsonapi": {
401
+ "version": "1.0"
402
+ },
403
+ "data": null
404
+ }
405
+ ```
406
+
407
+ * `:links`: (`Array<Link>`) Includes a links object in the top level of the
408
+ document.
409
+
410
+ ```ruby
411
+ links = [
412
+ Jei::Link.new(:self, '/artists?page[number]=2'),
413
+ Jei::Link.new(:prev, '/artists?page[number]=1'),
414
+ Jei::Link.new(:next, '/artists?page[number]=3')
415
+ ]
416
+ Jei::Document.build(nil, links: links).to_json
417
+ ```
418
+
419
+ ```json
420
+ {
421
+ "links": {
422
+ "self": "/artists?page[number]=2",
423
+ "prev": "/artists?page[number]=1",
424
+ "next": "/artists?page[number]=3"
425
+ },
426
+ "data": null
427
+ }
428
+ ```
429
+
430
+ * `:meta`: (`Hash<Symbol, Object>`) Includes a meta object in the top level of
431
+ the document.
432
+
433
+ ```ruby
434
+ Jei::Document.build(nil, meta: { total_pages: 10 }).to_json
435
+ ```
436
+
437
+ ```json
438
+ {
439
+ "meta": {
440
+ "total_pages": 10
441
+ },
442
+ "data": null
443
+ }
444
+ ```
445
+
446
+ * `:serializer`: (`Class`) Overrides the default serializer used for the
447
+ primary resource.
448
+
449
+ ```ruby
450
+ class SimpleArtistSerializer < Jei::Serializer
451
+ attribute :name
452
+ end
453
+
454
+ artist = Artist.new(id: 1, name: 'FIESTAR')
455
+ Jei::Document.build(artist, serializer: SimpleArtistSerializer).to_json
456
+ ```
457
+
458
+ ```json
459
+ {
460
+ "data": {
461
+ "id": "1",
462
+ "type": "artists",
463
+ "attributes": {
464
+ "name": "FIESTAR"
465
+ }
466
+ }
467
+ }
468
+ ```
469
+
470
+ ## Integration
471
+
472
+ Jei is not tied to any framework and can be integrated as a normal gem.
473
+
474
+ ### Rails
475
+
476
+ The simplest usage with [Rails][rails] is to define a new renderer.
477
+
478
+ ```ruby
479
+ # config/initializers/jei.rb
480
+ ActionController::Renderers.add(:jsonapi) do |resource, options|
481
+ document = Jei::Document.build(resource, options)
482
+ json = document.to_json
483
+ self.content_type = Mime::Type.lookup_by_extension(:jsonapi)
484
+ self.response_body = json
485
+ end
486
+
487
+ # config/initializers/mime_types.rb
488
+ Mime::Type.register 'application/vnd.api+json', :jsonapi
489
+ ```
490
+
491
+ Serializers can be placed in `app/serializers`. Include Rails' url helpers to
492
+ have them conveniently accessible in the serializer context for links.
493
+
494
+ ```ruby
495
+ # app/serializers/application_serializer.rb
496
+ class ApplicationSerializer < Jei::Serializer
497
+ include Rails.application.routes.url_helpers
498
+ end
499
+
500
+ # app/serializers/album_serializer.rb
501
+ class AlbumSerializer < ApplicationSerializer
502
+ attributes :kind, :name, :release_date
503
+ belongs_to :artist
504
+ end
505
+
506
+ # app/serializers/artist_serializer.rb
507
+ class ArtistSerializer < ApplicationSerializer
508
+ attributes :name
509
+ has_many :albums, data: false, links: -> {
510
+ [Jei::Link.new(:related, album_path(resource))]
511
+ }
512
+ end
513
+ ```
514
+
515
+ Specify the `jsonapi` format defined earlier when rendering in a controller.
516
+
517
+ ```ruby
518
+ # app/controllers/artists_controller.rb
519
+ class ArtistsController < ApplicationController
520
+ def show
521
+ artist = Artist.find(params[:id])
522
+ render jsonapi: artist, include: params[:include]
523
+ end
524
+ end
525
+ ```
526
+
527
+ [rails]: http://rubyonrails.org/