forestadmin-jsonapi-serializers 2.0.0.pre.beta.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 356df271f9e8100577cd1942d1a511ae1bf6def1f315dc27054e9715decbaf1d
4
+ data.tar.gz: 81a470af825925fffc7f5df7d075fbd664ecab60c80add42225fc1c4663f993e
5
+ SHA512:
6
+ metadata.gz: 38800d5ca9a6fe32a453ae1b29de17f2f1b8455b9d710919186356667ea921312368221535ae7eae503afb6a1056cd3206fdbf2f75ccf6f0dc9e8c4539ea7d29
7
+ data.tar.gz: 0120b3c6fd65b86d51d840aa39b1436e6120a11c4895a98d7d728e3c22567ca352f6fd9e5b15ca9f37fbbe8b3c0e7b324b3eb1226a4629e5629210d4cebdeedd
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ .DS_Store
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format d
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in jsonapi-serializers.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Mike Fotinakis
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,868 @@
1
+ # JSONAPI::Serializers
2
+
3
+ [![Build Status](https://travis-ci.org/fotinakis/jsonapi-serializers.svg?branch=master)](https://travis-ci.org/fotinakis/jsonapi-serializers)
4
+ [![Gem Version](https://badge.fury.io/rb/jsonapi-serializers.svg)](http://badge.fury.io/rb/jsonapi-serializers)
5
+
6
+ JSONAPI::Serializers is a simple library for serializing Ruby objects and their relationships into the [JSON:API format](http://jsonapi.org/format/).
7
+
8
+ This library is up-to-date with the finalized v1 JSON API spec.
9
+
10
+ * [Features](#features)
11
+ * [Installation](#installation)
12
+ * [Usage](#usage)
13
+ * [Define a serializer](#define-a-serializer)
14
+ * [Serialize an object](#serialize-an-object)
15
+ * [Serialize a collection](#serialize-a-collection)
16
+ * [Null handling](#null-handling)
17
+ * [Multiple attributes](#multiple-attributes)
18
+ * [Custom attributes](#custom-attributes)
19
+ * [More customizations](#more-customizations)
20
+ * [Base URL](#base-url)
21
+ * [Root metadata](#root-metadata)
22
+ * [Root links](#root-links)
23
+ * [Root errors](#root-errors)
24
+ * [Root jsonapi object](#root-jsonapi-object)
25
+ * [Explicit serializer discovery](#explicit-serializer-discovery)
26
+ * [Namespace serializers](#namespace-serializers)
27
+ * [Sparse fieldsets](#sparse-fieldsets)
28
+ * [Relationships](#relationships)
29
+ * [Compound documents and includes](#compound-documents-and-includes)
30
+ * [Relationship path handling](#relationship-path-handling)
31
+ * [Control links and data in relationships](#control-links-and-data-in-relationships)
32
+ * [Instrumentation](#instrumentation)
33
+ * [Rails example](#rails-example)
34
+ * [Sinatra example](#sinatra-example)
35
+ * [Unfinished business](#unfinished-business)
36
+ * [Contributing](#contributing)
37
+
38
+ ## Features
39
+
40
+ * Works with **any Ruby web framework**, including Rails, Sinatra, etc. This is a pure Ruby library.
41
+ * Supports the readonly features of the JSON:API spec.
42
+ * **Full support for compound documents** ("side-loading") and the `include` parameter.
43
+ * Similar interface to ActiveModel::Serializers, should provide an easy migration path.
44
+ * Intentionally unopinionated and simple, allows you to structure your app however you would like and then serialize the objects at the end. Easy to integrate with your existing authorization systems and service objects.
45
+
46
+ JSONAPI::Serializers was built as an intentionally simple serialization interface. It makes no assumptions about your database structure or routes and it does not provide controllers or any create/update interface to the objects. It is a library, not a framework. You will probably still need to do work to make your API fully compliant with the nuances of the [JSON:API spec](http://jsonapi.org/format/), for things like supporting `/relationships` routes and for supporting write actions like creating or updating objects. If you are looking for a more complete and opinionated framework, see the [jsonapi-resources](https://github.com/cerebris/jsonapi-resources) project.
47
+
48
+ ## Installation
49
+
50
+ Add this line to your application's Gemfile:
51
+
52
+ ```ruby
53
+ gem 'jsonapi-serializers'
54
+ ```
55
+
56
+ Or install directly with `gem install jsonapi-serializers`.
57
+
58
+ ## Usage
59
+
60
+ ### Define a serializer
61
+
62
+ ```ruby
63
+ require 'jsonapi-serializers'
64
+
65
+ class PostSerializer
66
+ include JSONAPI::Serializer
67
+
68
+ attribute :title
69
+ attribute :content
70
+ end
71
+ ```
72
+
73
+ ### Serialize an object
74
+
75
+ ```ruby
76
+ JSONAPI::Serializer.serialize(post)
77
+ ```
78
+
79
+ Returns a hash:
80
+ ```json
81
+ {
82
+ "data": {
83
+ "id": "1",
84
+ "type": "posts",
85
+ "attributes": {
86
+ "title": "Hello World",
87
+ "content": "Your first post"
88
+ },
89
+ "links": {
90
+ "self": "/posts/1"
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ ### Serialize a collection
97
+
98
+ ```ruby
99
+ JSONAPI::Serializer.serialize(posts, is_collection: true)
100
+ ```
101
+
102
+ Returns:
103
+
104
+ ```json
105
+ {
106
+ "data": [
107
+ {
108
+ "id": "1",
109
+ "type": "posts",
110
+ "attributes": {
111
+ "title": "Hello World",
112
+ "content": "Your first post"
113
+ },
114
+ "links": {
115
+ "self": "/posts/1"
116
+ }
117
+ },
118
+ {
119
+ "id": "2",
120
+ "type": "posts",
121
+ "attributes": {
122
+ "title": "Hello World again",
123
+ "content": "Your second post"
124
+ },
125
+ "links": {
126
+ "self": "/posts/2"
127
+ }
128
+ }
129
+ ]
130
+ }
131
+ ```
132
+
133
+ You must always pass `is_collection: true` when serializing a collection, see [Null handling](#null-handling).
134
+
135
+ ### Null handling
136
+
137
+ ```ruby
138
+ JSONAPI::Serializer.serialize(nil)
139
+ ```
140
+
141
+ Returns:
142
+ ```json
143
+ {
144
+ "data": null
145
+ }
146
+ ```
147
+
148
+ And serializing an empty collection:
149
+ ```ruby
150
+ JSONAPI::Serializer.serialize([], is_collection: true)
151
+ ```
152
+
153
+ Returns:
154
+ ```json
155
+ {
156
+ "data": []
157
+ }
158
+ ```
159
+
160
+ Note that the JSON:API spec distinguishes in how null/empty is handled for single objects vs. collections, so you must always provide `is_collection: true` when serializing multiple objects. If you attempt to serialize multiple objects without this flag (or a single object with it on) a `JSONAPI::Serializer::AmbiguousCollectionError` will be raised.
161
+
162
+ ### Multiple attributes
163
+ You could declare multiple attributes at once:
164
+
165
+ ```ruby
166
+ attributes :title, :body, :contents
167
+ ```
168
+
169
+ ### Custom attributes
170
+
171
+ By default the serializer looks for the same name of the attribute on the object it is given. You can customize this behavior by providing a block to `attribute`, `has_one`, or `has_many`:
172
+
173
+ ```ruby
174
+ attribute :content do
175
+ object.body
176
+ end
177
+
178
+ has_one :comment do
179
+ Comment.where(post: object).take!
180
+ end
181
+
182
+ has_many :authors do
183
+ Author.where(post: object)
184
+ end
185
+ ```
186
+
187
+ The block is evaluated within the serializer instance, so it has access to the `object` and `context` instance variables.
188
+
189
+ ### More customizations
190
+
191
+ Many other formatting and customizations are possible by overriding any of the following instance methods on your serializers.
192
+
193
+ ```ruby
194
+ # Override this to customize the JSON:API "id" for this object.
195
+ # Always return a string from this method to conform with the JSON:API spec.
196
+ def id
197
+ object.slug.to_s
198
+ end
199
+ ```
200
+ ```ruby
201
+ # Override this to customize the JSON:API "type" for this object.
202
+ # By default, the type is the object's class name lowercased, pluralized, and dasherized,
203
+ # per the spec naming recommendations: http://jsonapi.org/recommendations/#naming
204
+ # For example, 'MyApp::LongCommment' will become the 'long-comments' type.
205
+ def type
206
+ 'long-comments'
207
+ end
208
+ ```
209
+ ```ruby
210
+ # Override this to customize how attribute names are formatted.
211
+ # By default, attribute names are dasherized per the spec naming recommendations:
212
+ # http://jsonapi.org/recommendations/#naming
213
+ def format_name(attribute_name)
214
+ attribute_name.to_s.dasherize
215
+ end
216
+ ```
217
+ ```ruby
218
+ # The opposite of format_name. Override this if you override format_name.
219
+ def unformat_name(attribute_name)
220
+ attribute_name.to_s.underscore
221
+ end
222
+ ```
223
+ ```ruby
224
+ # Override this to provide resource-object metadata.
225
+ # http://jsonapi.org/format/#document-structure-resource-objects
226
+ def meta
227
+ end
228
+ ```
229
+ ```ruby
230
+ # Override this to set a base URL (http://example.com) for all links. No trailing slash.
231
+ def base_url
232
+ @base_url
233
+ end
234
+ ```
235
+ ```ruby
236
+ # Override this to provide a resource-object jsonapi object containing the version in use.
237
+ # http://jsonapi.org/format/#document-jsonapi-object
238
+ def jsonapi
239
+ end
240
+ ```
241
+ ```ruby
242
+ def self_link
243
+ "#{base_url}/#{type}/#{id}"
244
+ end
245
+ ```
246
+ ```ruby
247
+ def relationship_self_link(attribute_name)
248
+ "#{self_link}/relationships/#{format_name(attribute_name)}"
249
+ end
250
+ ```
251
+ ```ruby
252
+ def relationship_related_link(attribute_name)
253
+ "#{self_link}/#{format_name(attribute_name)}"
254
+ end
255
+ ```
256
+
257
+ If you override `self_link`, `relationship_self_link`, or `relationship_related_link` to return `nil`, the link will be excluded from the serialized object.
258
+
259
+ ### Base URL
260
+
261
+ You can override the `base_url` instance method to set a URL to be used in all links.
262
+
263
+ ```ruby
264
+ class BaseSerializer
265
+ include JSONAPI::Serializer
266
+
267
+ def base_url
268
+ 'http://example.com'
269
+ end
270
+ end
271
+
272
+ class PostSerializer < BaseSerializer
273
+ attribute :title
274
+ attribute :content
275
+
276
+ has_one :author
277
+ has_many :comments
278
+ end
279
+
280
+ JSONAPI::Serializer.serialize(post)
281
+ ```
282
+
283
+ Returns:
284
+
285
+ ```json
286
+ {
287
+ "data": {
288
+ "id": "1",
289
+ "type": "posts",
290
+ "attributes": {
291
+ "title": "Hello World",
292
+ "content": "Your first post"
293
+ },
294
+ "links": {
295
+ "self": "http://example.com/posts/1"
296
+ },
297
+ "relationships": {
298
+ "author": {
299
+ "links": {
300
+ "self": "http://example.com/posts/1/relationships/author",
301
+ "related": "http://example.com/posts/1/author"
302
+ }
303
+ },
304
+ "comments": {
305
+ "links": {
306
+ "self": "http://example.com/posts/1/relationships/comments",
307
+ "related": "http://example.com/posts/1/comments"
308
+ },
309
+ }
310
+ }
311
+ }
312
+ }
313
+ ```
314
+
315
+ Alternatively, you can specify `base_url` as an argument to `serialize` which allows you to build the URL with different subdomains or other logic from the request:
316
+
317
+ ```ruby
318
+ JSONAPI::Serializer.serialize(post, base_url: 'http://example.com')
319
+ ```
320
+
321
+ Note: if you override `self_link` in your serializer and leave out `base_url`, it will not be included.
322
+
323
+ ### Root metadata
324
+
325
+ You can pass a `meta` argument to specify top-level metadata:
326
+
327
+ ```ruby
328
+ JSONAPI::Serializer.serialize(post, meta: {copyright: 'Copyright 2015 Example Corp.'})
329
+ ```
330
+
331
+ ### Root links
332
+
333
+ You can pass a `links` argument to specify top-level links:
334
+
335
+ ```ruby
336
+ JSONAPI::Serializer.serialize(post, links: {self: 'https://example.com/posts'})
337
+ ```
338
+
339
+ ### Root errors
340
+
341
+ You can use `serialize_errors` method in order to specify top-level errors:
342
+
343
+ ```ruby
344
+ errors = [{ "title": "Invalid Attribute", "detail": "First name must contain at least three characters." }]
345
+ JSONAPI::Serializer.serialize_errors(errors)
346
+ ```
347
+
348
+ If you are using Rails models (ActiveModel by default), you can pass in an object's errors:
349
+
350
+ ```ruby
351
+ JSONAPI::Serializer.serialize_errors(user.errors)
352
+ ```
353
+
354
+ A more complete usage example (assumes ActiveModel):
355
+
356
+ ```ruby
357
+ class Api::V1::ReposController < Api::V1::BaseController
358
+ def create
359
+ post = Post.create(post_params)
360
+ if post.errors
361
+ render json: JSONAPI::Serializer.serialize_errors(post.errors)
362
+ else
363
+ render json: JSONAPI::Serializer.serialize(post)
364
+ end
365
+ end
366
+ end
367
+ ```
368
+
369
+ ### Root 'jsonapi' object
370
+
371
+ You can pass a `jsonapi` argument to specify a [top-level "jsonapi" key](http://jsonapi.org/format/#document-jsonapi-object) containing the version of JSON:API in use:
372
+
373
+ ```ruby
374
+ JSONAPI::Serializer.serialize(post, jsonapi: {version: '1.0'})
375
+ ```
376
+
377
+ ### Explicit serializer discovery
378
+
379
+ By default, jsonapi-serializers assumes that the serializer class for `Namespace::User` is `Namespace::UserSerializer`. You can override this behavior on a per-object basis by implementing the `jsonapi_serializer_class_name` method.
380
+
381
+ ```ruby
382
+ class User
383
+ def jsonapi_serializer_class_name
384
+ 'SomeOtherNamespace::CustomUserSerializer'
385
+ end
386
+ end
387
+ ```
388
+
389
+ Now, when a `User` object is serialized, it will use the `SomeOtherNamespace::CustomUserSerializer`.
390
+
391
+ ### Namespace serializers
392
+
393
+ Assume you have an API with multiple versions:
394
+
395
+ ```ruby
396
+ module Api
397
+ module V1
398
+ class PostSerializer
399
+ include JSONAPI::Serializer
400
+ attribute :title
401
+ end
402
+ end
403
+ module V2
404
+ class PostSerializer
405
+ include JSONAPI::Serializer
406
+ attribute :name
407
+ end
408
+ end
409
+ end
410
+ ```
411
+
412
+ With the namespace option you can choose which serializer is used.
413
+
414
+ ```ruby
415
+ JSONAPI::Serializer.serialize(post, namespace: Api::V1)
416
+ JSONAPI::Serializer.serialize(post, namespace: Api::V2)
417
+ ```
418
+
419
+ This option overrides the `jsonapi_serializer_class_name` method.
420
+
421
+ ### Sparse fieldsets
422
+
423
+ The JSON:API spec allows to return only [specific fields](http://jsonapi.org/format/#fetching-sparse-fieldsets) from attributes and relationships.
424
+
425
+ For example, if you wanted to return only the `title` field and `author` relationship link for `posts`:
426
+
427
+ ```ruby
428
+ fields =
429
+ JSONAPI::Serializer.serialize(post, fields: {posts: [:title]})
430
+ ```
431
+
432
+ Sparse fieldsets also affect relationship links. In this case, only the `author` relationship link would be included:
433
+
434
+ ``` ruby
435
+ JSONAPI::Serializer.serialize(post, fields: {posts: [:title, :author]})
436
+ ```
437
+
438
+ Sparse fieldsets operate on a per-type basis, so they affect all resources in the response including in compound documents. For example, this will affect both the `posts` type in the primary data and the `users` type in the compound data:
439
+
440
+ ``` ruby
441
+ JSONAPI::Serializer.serialize(
442
+ post,
443
+ fields: {posts: ['title', 'author'], users: ['name']},
444
+ include: 'author',
445
+ )
446
+ ```
447
+
448
+ Sparse fieldsets support comma-separated strings (`fields: {posts: 'title,author'}`, arrays of strings (`fields: {posts: ['title', 'author']}`), single symbols (`fields: {posts: :title}`), and arrays of symbols (`fields: {posts: [:title, :author]}`).
449
+
450
+ ## Relationships
451
+
452
+ You can easily specify relationships with the `has_one` and `has_many` directives.
453
+
454
+ ```ruby
455
+ class BaseSerializer
456
+ include JSONAPI::Serializer
457
+ end
458
+
459
+ class PostSerializer < BaseSerializer
460
+ attribute :title
461
+ attribute :content
462
+
463
+ has_one :author
464
+ has_many :comments
465
+ end
466
+
467
+ class UserSerializer < BaseSerializer
468
+ attribute :name
469
+ end
470
+
471
+ class CommentSerializer < BaseSerializer
472
+ attribute :content
473
+
474
+ has_one :user
475
+ end
476
+ ```
477
+
478
+ Note that when serializing a post, the `author` association will come from the `author` attribute on the `Post` instance, no matter what type it is (in this case it is a `User`). This will work just fine, because JSONAPI::Serializers automatically finds serializer classes by appending `Serializer` to the object's class name. This behavior can be customized.
479
+
480
+ Because the full class name is used when discovering serializers, JSONAPI::Serializers works with any custom namespaces you might have, like a Rails Engine or standard Ruby module namespace.
481
+
482
+ ### Compound documents and includes
483
+
484
+ > To reduce the number of HTTP requests, servers MAY allow responses that include related resources along with the requested primary resources. Such responses are called "compound documents".
485
+ > [JSON:API Compound Documents](http://jsonapi.org/format/#document-structure-compound-documents)
486
+
487
+ JSONAPI::Serializers supports compound documents with a simple `include` parameter.
488
+
489
+ For example:
490
+
491
+ ```ruby
492
+ JSONAPI::Serializer.serialize(post, include: ['author', 'comments', 'comments.user'])
493
+ ```
494
+
495
+ Returns:
496
+
497
+ ```json
498
+ {
499
+ "data": {
500
+ "id": "1",
501
+ "type": "posts",
502
+ "attributes": {
503
+ "title": "Hello World",
504
+ "content": "Your first post"
505
+ },
506
+ "links": {
507
+ "self": "/posts/1"
508
+ },
509
+ "relationships": {
510
+ "author": {
511
+ "links": {
512
+ "self": "/posts/1/relationships/author",
513
+ "related": "/posts/1/author"
514
+ },
515
+ "data": {
516
+ "type": "users",
517
+ "id": "1"
518
+ }
519
+ },
520
+ "comments": {
521
+ "links": {
522
+ "self": "/posts/1/relationships/comments",
523
+ "related": "/posts/1/comments"
524
+ },
525
+ "data": [
526
+ {
527
+ "type": "comments",
528
+ "id": "1"
529
+ }
530
+ ]
531
+ }
532
+ }
533
+ },
534
+ "included": [
535
+ {
536
+ "id": "1",
537
+ "type": "users",
538
+ "attributes": {
539
+ "name": "Post Author"
540
+ },
541
+ "links": {
542
+ "self": "/users/1"
543
+ }
544
+ },
545
+ {
546
+ "id": "1",
547
+ "type": "comments",
548
+ "attributes": {
549
+ "content": "Have no fear, sers, your king is safe."
550
+ },
551
+ "links": {
552
+ "self": "/comments/1"
553
+ },
554
+ "relationships": {
555
+ "user": {
556
+ "links": {
557
+ "self": "/comments/1/relationships/user",
558
+ "related": "/comments/1/user"
559
+ },
560
+ "data": {
561
+ "type": "users",
562
+ "id": "2"
563
+ }
564
+ },
565
+ "post": {
566
+ "links": {
567
+ "self": "/comments/1/relationships/post",
568
+ "related": "/comments/1/post"
569
+ }
570
+ }
571
+ }
572
+ },
573
+ {
574
+ "id": "2",
575
+ "type": "users",
576
+ "attributes": {
577
+ "name": "Barristan Selmy"
578
+ },
579
+ "links": {
580
+ "self": "/users/2"
581
+ }
582
+ }
583
+ ]
584
+ }
585
+ ```
586
+
587
+ Notice a few things:
588
+ * The [primary data](http://jsonapi.org/format/#document-structure-top-level) relationships now include "linkage" information for each relationship that was included.
589
+ * The related objects themselves are loaded in the top-level `included` member.
590
+ * The related objects _also_ include "linkage" data when a deeper relationship is also present in the compound document. This is a very powerful feature of the JSON:API spec, and allows you to deeply link complicated relationships all in the same document and in a single HTTP response. JSONAPI::Serializers automatically includes the correct linkage data for whatever `include` paths you specify. This conforms to this part of the spec:
591
+
592
+ > Note: Full linkage ensures that included resources are related to either the primary data (which could be resource objects or resource identifier objects) or to each other.
593
+ > [JSON:API Compound Documents](http://jsonapi.org/format/#document-compound-documents)
594
+
595
+ #### Relationship path handling
596
+
597
+ The `include` param also accepts a string of [relationship paths](http://jsonapi.org/format/#fetching-includes), ie. `include: 'author,comments,comments.user'` so you can pass an `?include` query param directly through to the serialize method. Be aware that letting users pass arbitrary relationship paths might introduce security issues depending on your authorization setup, where a user could `include` a relationship they might not be authorized to see directly. Be aware of what you allow API users to include.
598
+
599
+ ### Control `links` and `data` in relationships
600
+
601
+ The JSON API spec allows relationships objects to contain `links`, `data`, or both.
602
+
603
+ By default, `links` are included in each relationship. You can remove links for a specific relationship by passing `include_links: false` to `has_one` or `has_many`. For example:
604
+
605
+ ```ruby
606
+ has_many :comments, include_links: false # Default is include_links: true.
607
+ ```
608
+
609
+ Notice that `links` are now excluded for the `comments` relationship:
610
+
611
+ ```json
612
+ "relationships": {
613
+ "author": {
614
+ "links": {
615
+ "self": "/posts/1/relationships/author",
616
+ "related": "/posts/1/author"
617
+ }
618
+ },
619
+ "comments": {}
620
+ }
621
+ ```
622
+
623
+ By default, `data` is excluded in each relationship. You can enable data for a specific relationship by passing `include_data: true` to `has_one` or `has_many`. For example:
624
+
625
+ ```ruby
626
+ has_one :author, include_data: true # Default is include_data: false.
627
+ ```
628
+
629
+ Notice that linkage data is now included for the `author` relationship:
630
+
631
+ ```json
632
+ "relationships": {
633
+ "author": {
634
+ "links": {
635
+ "self": "/posts/1/relationships/author",
636
+ "related": "/posts/1/author"
637
+ },
638
+ "data": {
639
+ "type": "users",
640
+ "id": "1"
641
+ }
642
+ }
643
+ ```
644
+
645
+ ## Instrumentation
646
+
647
+ Using [ActiveSupport::Notifications](https://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) you can subscribe to key notifications to better understand the performance of your serialization.
648
+
649
+ The following notifications can be subscribed to:
650
+
651
+ * `render.jsonapi_serializers.serialize_primary` - time spent serializing a single object
652
+ * `render.jsonapi_serializers.find_recursive_relationships` - time spent finding objects to serialize through relationships
653
+
654
+ This is an example of how you might subscribe to all events that come from `jsonapi-serializers`.
655
+
656
+ ```ruby
657
+ require 'active_support/notifications'
658
+
659
+ ActiveSupport::Notifications.subscribe(/^render\.jsonapi_serializers\..*/) do |*args|
660
+ event = ActiveSupport::Notifications::Event.new(*args)
661
+
662
+ puts event.name
663
+ puts event.time
664
+ puts event.end
665
+ puts event.payload
666
+ end
667
+ ```
668
+
669
+ ## Rails example
670
+
671
+ ```ruby
672
+ # app/serializers/base_serializer.rb
673
+ class BaseSerializer
674
+ include JSONAPI::Serializer
675
+
676
+ def self_link
677
+ "/api/v1#{super}"
678
+ end
679
+ end
680
+
681
+ # app/serializers/post_serializer.rb
682
+ class PostSerializer < BaseSerializer
683
+ attribute :title
684
+ attribute :content
685
+ end
686
+
687
+ # app/controllers/api/v1/base_controller.rb
688
+ class Api::V1::BaseController < ActionController::Base
689
+ # Convenience methods for serializing models:
690
+ def serialize_model(model, options = {})
691
+ options[:is_collection] = false
692
+ JSONAPI::Serializer.serialize(model, options)
693
+ end
694
+
695
+ def serialize_models(models, options = {})
696
+ options[:is_collection] = true
697
+ JSONAPI::Serializer.serialize(models, options)
698
+ end
699
+ end
700
+
701
+ # app/controllers/api/v1/posts_controller.rb
702
+ class Api::V1::ReposController < Api::V1::BaseController
703
+ def index
704
+ posts = Post.all
705
+ render json: serialize_models(posts)
706
+ end
707
+
708
+ def show
709
+ post = Post.find(params[:id])
710
+ render json: serialize_model(post)
711
+ end
712
+ end
713
+
714
+ # config/initializers/jsonapi_mimetypes.rb
715
+ # Without this mimetype registration, controllers will not automatically parse JSON API params.
716
+
717
+ # Rails 4
718
+ module JSONAPI
719
+ MIMETYPE = "application/vnd.api+json"
720
+ end
721
+ Mime::Type.register(JSONAPI::MIMETYPE, :api_json)
722
+ ActionDispatch::ParamsParser::DEFAULT_PARSERS[Mime::Type.lookup(JSONAPI::MIMETYPE)] = lambda do |body|
723
+ JSON.parse(body)
724
+ end
725
+
726
+ # Rails 5 Option 1: Add another synonym to the json mime type
727
+ json_mime_type_synonyms = %w[
728
+ text/x-json
729
+ application/jsonrequest
730
+ application/vnd.api+json
731
+ ]
732
+ Mime::Type.register('application/json', :json, json_mime_type_synonyms)
733
+
734
+ # Rails 5 Option 2: Add a separate mime type
735
+ Mime::Type.register('application/vnd.api+json', :api_json)
736
+ ActionDispatch::Request.parameter_parsers[:api_json] = -> (body) {
737
+ JSON.parse(body)
738
+ }
739
+ ```
740
+
741
+ ## Sinatra example
742
+
743
+ Here's an example using [Sinatra](http://www.sinatrarb.com) and
744
+ [Sequel ORM](http://sequel.jeremyevans.net) instead of Rails and ActiveRecord.
745
+ The important takeaways here are that:
746
+
747
+ 1. The `:tactical_eager_loading` plugin will greatly reduce the number of
748
+ queries performed when sideloading associated records. You can add this
749
+ plugin to a single model (as demonstrated here), or globally to all models.
750
+ For more information, please see the Sequel
751
+ [documentation](http://sequel.jeremyevans.net/rdoc-plugins/classes/Sequel/Plugins/TacticalEagerLoading.html).
752
+ 1. The `:skip_collection_check` option must be set to true in order for
753
+ JSONAPI::Serializer to be able to serialize a single Sequel::Model instance.
754
+ 1. You should call `#all` on your Sequel::Dataset instances before passing them
755
+ to JSONAPI::Serializer to greatly reduce the number of queries performed.
756
+
757
+ ```ruby
758
+ require 'sequel'
759
+ require 'sinatra/base'
760
+ require 'json'
761
+ require 'jsonapi-serializers'
762
+
763
+ class Post < Sequel::Model
764
+ plugin :tactical_eager_loading
765
+
766
+ one_to_many :comments
767
+ end
768
+
769
+ class Comment < Sequel::Model
770
+ many_to_one :post
771
+ end
772
+
773
+ class BaseSerializer
774
+ include JSONAPI::Serializer
775
+
776
+ def self_link
777
+ "/api/v1#{super}"
778
+ end
779
+ end
780
+
781
+ class PostSerializer < BaseSerializer
782
+ attributes :title, :content
783
+
784
+ has_many :comments
785
+ end
786
+
787
+ class CommentSerializer < BaseSerializer
788
+ attributes :username, :content
789
+
790
+ has_one :post
791
+ end
792
+
793
+ module Api
794
+ class V1 < Sinatra::Base
795
+ configure do
796
+ mime_type :api_json, 'application/vnd.api+json'
797
+
798
+ set :database, Sequel.connect
799
+ end
800
+
801
+ helpers do
802
+ def parse_request_body
803
+ return unless request.body.respond_to?(:size) &&
804
+ request.body.size > 0
805
+
806
+ halt 415 unless request.content_type &&
807
+ request.content_type[/^[^;]+/] == mime_type(:api_json)
808
+
809
+ request.body.rewind
810
+ JSON.parse(request.body.read, symbolize_names: true)
811
+ end
812
+
813
+ # Convenience methods for serializing models:
814
+ def serialize_model(model, options = {})
815
+ options[:is_collection] = false
816
+ options[:skip_collection_check] = true
817
+ JSONAPI::Serializer.serialize(model, options)
818
+ end
819
+
820
+ def serialize_models(models, options = {})
821
+ options[:is_collection] = true
822
+ JSONAPI::Serializer.serialize(models, options)
823
+ end
824
+ end
825
+
826
+ before do
827
+ halt 406 unless request.preferred_type.entry == mime_type(:api_json)
828
+ @data = parse_request_body
829
+ content_type :api_json
830
+ end
831
+
832
+ get '/posts' do
833
+ posts = Post.all
834
+ serialize_models(posts).to_json
835
+ end
836
+
837
+ get '/posts/:id' do
838
+ post = Post[params[:id].to_i]
839
+ not_found if post.nil?
840
+ serialize_model(post, include: 'comments').to_json
841
+ end
842
+ end
843
+ end
844
+ ```
845
+
846
+ See also: [Sinja](https://github.com/mwpastore/sinja), which extends Sinatra
847
+ and leverages jsonapi-serializers to provide a JSON:API framework.
848
+
849
+ ## Changelog
850
+
851
+ See [Releases](https://github.com/fotinakis/jsonapi-serializers/releases).
852
+
853
+ ## Unfinished business
854
+
855
+ * Support for pagination/sorting is unlikely to be supported because it would likely involve coupling to ActiveRecord, but please open an issue if you have ideas of how to support this generically.
856
+
857
+ ## Contributing
858
+
859
+ 1. Fork it ( https://github.com/fotinakis/jsonapi-serializers/fork )
860
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
861
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
862
+ 4. Push to the branch (`git push origin my-new-feature`)
863
+ 5. Create a new Pull Request
864
+
865
+ Throw a ★ on it! :)
866
+
867
+ # Forked from https://github.com/fotinakis/jsonapi-serializers
868
+ Add a new namespace to avoid conflict with the `jsonapi-serializer` gem.