jei 0.1.0 → 0.2.0

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 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/