jsonapi-resources 0.8.3 → 0.9.0.beta1

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: e5c188b1a5be92d5d073582f61ccf9b049ba9c2d
4
- data.tar.gz: 13deeb24963ef64adc49b53562851119411e1b56
3
+ metadata.gz: a6215487122618a6e8526cb39da9ce37f494f248
4
+ data.tar.gz: d8cb24a422186e9dde1488dc172c072cbc456068
5
5
  SHA512:
6
- metadata.gz: e261dde48fdd2f2b0697d9e5ef8bf13087880e508f2692b3c562b84bc357f08c4b524c1de51bc724fd2462aa30eb6cec90d2217933fe2f9212bb598ec395414b
7
- data.tar.gz: f04daba00e00099cb4bc22d197664f8bcbdd29ab5fec8ac9ae0873b400c732de50373d6ed7fc31a96edceda6cd42ee9eb1f5523dc7b1d42e9822e73f16f34b42
6
+ metadata.gz: 1b831258524a83611c492b61fdce91d5b6744b7ef788e038cad3c69955142992d8cfaf156f4cb907a07968dd6b7a0789fabf8d6d0227410d66ddd6867d99bd77
7
+ data.tar.gz: ca5168dd8dd8de04a67ef6ae63160b26e4e0c2e863bc591261b0091063286bfe292ee35eedcaa250dd5637c2fd3eb9e7e5d15241aff17fe05fe15ca34dfbee0b
data/README.md CHANGED
@@ -2,18 +2,60 @@
2
2
 
3
3
  [![Join the chat at https://gitter.im/cerebris/jsonapi-resources](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/cerebris/jsonapi-resources?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
4
4
 
5
- `JSONAPI::Resources`, or "JR", provides a framework for developing an API server that complies with the
6
- [JSON:API](http://jsonapi.org/) specification.
5
+ **NOTE:** This README is the documentation for `JSONAPI::Resources`. If you are viewing this at the
6
+ [project page on Github](https://github.com/cerebris/jsonapi-resources) you are viewing the documentation for the `master`
7
+ branch. This may contain information that is not relevant to the release you are using. Please see the README for the
8
+ [version](https://github.com/cerebris/jsonapi-resources/releases) you are using.
7
9
 
8
- Like JSON:API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition
10
+ ---
11
+
12
+ `JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the
13
+ [JSON API](http://jsonapi.org/) specification.
14
+
15
+ Like JSON API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition
9
16
  of your resources, including their attributes and relationships, to make your server compliant with JSON API.
10
17
 
11
18
  JR is designed to work with Rails 4.0+, and provides custom routes, controllers, and serializers. JR's resources may be
12
19
  backed by ActiveRecord models or by custom objects.
13
20
 
14
- ## Documentation
21
+ ## Table of Contents
15
22
 
16
- Full documentation can be found at [http://jsonapi-resources.com](http://jsonapi-resources.com), including the [v0.8 Guide](http://jsonapi-resources.com/v0.8/guide/) specific to this version.
23
+ * [Demo App] (#demo-app)
24
+ * [Client Libraries] (#client-libraries)
25
+ * [Installation] (#installation)
26
+ * [Usage] (#usage)
27
+ * [Resources] (#resources)
28
+ * [JSONAPI::Resource] (#jsonapiresource)
29
+ * [Context] (#context)
30
+ * [Attributes] (#attributes)
31
+ * [Primary Key] (#primary-key)
32
+ * [Model Name] (#model-name)
33
+ * [Model Hints] (#model-hints)
34
+ * [Relationships] (#relationships)
35
+ * [Filters] (#filters)
36
+ * [Pagination] (#pagination)
37
+ * [Included relationships (side-loading resources)] (#included-relationships-side-loading-resources)
38
+ * [Resource meta] (#resource-meta)
39
+ * [Custom Links] (#custom-links)
40
+ * [Callbacks] (#callbacks)
41
+ * [Controllers] (#controllers)
42
+ * [Namespaces] (#namespaces)
43
+ * [Error Codes] (#error-codes)
44
+ * [Handling Exceptions] (#handling-exceptions)
45
+ * [Action Callbacks] (#action-callbacks)
46
+ * [Operation Processors] (#operation-processors)
47
+ * [Serializer] (#serializer)
48
+ * [Serializer options] (#serializer-options)
49
+ * [Formatting] (#formatting)
50
+ * [Key Format] (#key-format)
51
+ * [Routing] (#routing)
52
+ * [Nested Routes] (#nested-routes)
53
+ * [Authorization](#authorization)
54
+ * [Resource Caching] (#resource-caching)
55
+ * [Caching Caveats] (#caching-caveats)
56
+ * [Configuration] (#configuration)
57
+ * [Contributing] (#contributing)
58
+ * [License] (#license)
17
59
 
18
60
  ## Demo App
19
61
 
@@ -21,8 +63,8 @@ We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), av
21
63
 
22
64
  ## Client Libraries
23
65
 
24
- JSON:API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries)
25
- which *should* be compatible with JSON:API compliant server implementations such as JR.
66
+ JSON API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries)
67
+ which *should* be compatible with JSON API compliant server implementations such as JR.
26
68
 
27
69
  ## Installation
28
70
 
@@ -38,7 +80,2071 @@ Or install it yourself as:
38
80
 
39
81
  $ gem install jsonapi-resources
40
82
 
41
- **For further usage see the [v0.8 Guide](http://jsonapi-resources.com/v0.8/guide/)**
83
+ ## Usage
84
+
85
+ ### Resources
86
+
87
+ Resources define the public interface to your API. A resource defines which attributes are exposed, as well as
88
+ relationships to other resources.
89
+
90
+ Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The file name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example,
91
+ a `Contact` model's resource should have a class named `ContactResource` defined in a file named `contact_resource.rb`.
92
+
93
+ #### JSONAPI::Resource
94
+
95
+ Resources must be derived from `JSONAPI::Resource`, or a class that is itself derived from `JSONAPI::Resource`.
96
+
97
+ For example:
98
+
99
+ ```ruby
100
+ class ContactResource < JSONAPI::Resource
101
+ end
102
+ ```
103
+
104
+ A jsonapi-resource generator is available
105
+ ```
106
+ rails generate jsonapi:resource contact
107
+ ```
108
+
109
+ ##### Abstract Resources
110
+
111
+ Resources that are not backed by a model (purely used as base classes for other resources) should be declared as
112
+ abstract.
113
+
114
+ Because abstract resources do not expect to be backed by a model, they won't attempt to discover the model class
115
+ or any of its relationships.
116
+
117
+ ```ruby
118
+ class BaseResource < JSONAPI::Resource
119
+ abstract
120
+
121
+ has_one :creator
122
+ end
123
+
124
+ class ContactResource < BaseResource
125
+ end
126
+ ```
127
+
128
+ ##### Immutable Resources
129
+
130
+ Resources that are immutable should be declared as such with the `immutable` method. Immutable resources will only
131
+ generate routes for `index`, `show` and `show_relationship`.
132
+
133
+ ###### Immutable for Readonly
134
+
135
+ Some resources are read-only and are not to be modified through the API. Declaring a resource as immutable prevents
136
+ creation of routes that allow modification of the resource.
137
+
138
+ ###### Immutable Heterogeneous Collections
139
+
140
+ Immutable resources can be used as the basis for a heterogeneous collection. Resources in heterogeneous collections can
141
+ still be mutated through their own type-specific endpoints.
142
+
143
+ ```ruby
144
+ class VehicleResource < JSONAPI::Resource
145
+ immutable
146
+
147
+ has_one :owner
148
+ attributes :make, :model, :serial_number
149
+ end
150
+
151
+ class CarResource < VehicleResource
152
+ attributes :drive_layout
153
+ has_one :driver
154
+ end
155
+
156
+ class BoatResource < VehicleResource
157
+ attributes :length_at_water_line
158
+ has_one :captain
159
+ end
160
+
161
+ # routes
162
+ jsonapi_resources :vehicles
163
+ jsonapi_resources :cars
164
+ jsonapi_resources :boats
165
+
166
+ ```
167
+
168
+ In the above example vehicles are immutable. A call to `/vehicles` or `/vehicles/1` will return vehicles with types
169
+ of either `car` or `boat`. But calls to PUT or POST a `car` must be made to `/cars`. The rails models backing the above
170
+ code use Single Table Inheritance.
171
+
172
+ #### Context
173
+
174
+ Sometimes you will want to access things such as the current logged in user (and other state only available within your controllers) from within your resource classes. To make this state available to a resource class you need to put it into the context hash - this can be done via a `context` method on one of your controllers or across all controllers using ApplicationController.
175
+
176
+ For example:
177
+
178
+ ```ruby
179
+ class ApplicationController < JSONAPI::ResourceController
180
+ def context
181
+ {current_user: current_user}
182
+ end
183
+ end
184
+
185
+ # Specific resource controllers derive from ApplicationController
186
+ # and share its context
187
+ class PeopleController < ApplicationController
188
+
189
+ end
190
+
191
+ # Assuming you don't permit user_id (so the client won't assign a wrong user to own the object)
192
+ # you can ensure the current user is assigned the record by using the controller's context hash.
193
+ class PeopleResource < JSONAPI::Resource
194
+ before_save do
195
+ @model.user_id = context[:current_user].id if @model.new_record?
196
+ end
197
+ end
198
+ ```
199
+
200
+ You can put things that affect serialization and resource configuration into the context.
201
+
202
+ #### Attributes
203
+
204
+ Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using
205
+ the `attribute` method, and multiple attributes can be declared with the `attributes` method on the resource class.
206
+
207
+ For example:
208
+
209
+ ```ruby
210
+ class ContactResource < JSONAPI::Resource
211
+ attribute :name_first
212
+ attributes :name_last, :email, :twitter
213
+ end
214
+ ```
215
+
216
+ This resource has 4 defined attributes: `name_first`, `name_last`, `email`, `twitter`, as well as the automatically
217
+ defined attributes `id` and `type`. By default these attributes must exist on the model that is handled by the resource.
218
+
219
+ A resource object wraps a Ruby object, usually an `ActiveModel` record, which is available as the `@model` variable.
220
+ This allows a resource's methods to access the underlying model.
221
+
222
+ For example, a computed attribute for `full_name` could be defined as such:
223
+
224
+ ```ruby
225
+ class ContactResource < JSONAPI::Resource
226
+ attributes :name_first, :name_last, :email, :twitter
227
+ attribute :full_name
228
+
229
+ def full_name
230
+ "#{@model.name_first}, #{@model.name_last}"
231
+ end
232
+ end
233
+ ```
234
+
235
+ ##### Attribute Delegation
236
+
237
+ Normally resource attributes map to an attribute on the model of the same name. Using the `delegate` option allows a resource
238
+ attribute to map to a differently named model attribute. For example:
239
+
240
+ ```ruby
241
+ class ContactResource < JSONAPI::Resource
242
+ attribute :name_first, delegate: :first_name
243
+ attribute :name_last, delegate: :last_name
244
+ end
245
+ ```
246
+
247
+ ##### Fetchable Attributes
248
+
249
+ By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding
250
+ the `fetchable_fields` method.
251
+
252
+ Here's an example that prevents guest users from seeing the `email` field:
253
+
254
+ ```ruby
255
+ class AuthorResource < JSONAPI::Resource
256
+ attributes :name, :email
257
+ model_name 'Person'
258
+ has_many :posts
259
+
260
+ def fetchable_fields
261
+ if (context[:current_user].guest)
262
+ super - [:email]
263
+ else
264
+ super
265
+ end
266
+ end
267
+ end
268
+ ```
269
+
270
+ Context flows through from the controller to the resource and can be used to control the attributes based on the
271
+ current user (or other value).
272
+
273
+ ##### Creatable and Updatable Attributes
274
+
275
+ By default all attributes are assumed to be updatable and creatable. To prevent some attributes from being accepted by
276
+ the `update` or `create` methods, override the `self.updatable_fields` and `self.creatable_fields` methods on a resource.
277
+
278
+ This example prevents `full_name` from being set:
279
+
280
+ ```ruby
281
+ class ContactResource < JSONAPI::Resource
282
+ attributes :name_first, :name_last, :full_name
283
+
284
+ def full_name
285
+ "#{@model.name_first}, #{@model.name_last}"
286
+ end
287
+
288
+ def self.updatable_fields(context)
289
+ super - [:full_name]
290
+ end
291
+
292
+ def self.creatable_fields(context)
293
+ super - [:full_name]
294
+ end
295
+ end
296
+ ```
297
+
298
+ The `context` is not by default used by the `ResourceController`, but may be used if you override the controller methods.
299
+ By using the context you have the option to determine the creatable and updatable fields based on the user.
300
+
301
+ ##### Sortable Attributes
302
+
303
+ JR supports [sorting primary resources by multiple sort criteria](http://jsonapi.org/format/#fetching-sorting).
304
+
305
+ By default all attributes are assumed to be sortable. To prevent some attributes from being sortable, override the
306
+ `self.sortable_fields` method on a resource.
307
+
308
+ Here's an example that prevents sorting by post's `body`:
309
+
310
+ ```ruby
311
+ class PostResource < JSONAPI::Resource
312
+ attributes :title, :body
313
+
314
+ def self.sortable_fields(context)
315
+ super(context) - [:body]
316
+ end
317
+ end
318
+ ```
319
+
320
+ JR also supports sorting primary resources by fields on relationships.
321
+
322
+ Here's an example of sorting books by the author name:
323
+
324
+ ```ruby
325
+ class Book < ActiveRecord::Base
326
+ belongs_to :author
327
+ end
328
+
329
+ class Author < ActiveRecord::Base
330
+ has_many :books
331
+ end
332
+
333
+ class BookResource < JSONAPI::Resource
334
+ attributes :title, :body
335
+
336
+ def self.sortable_fields(context)
337
+ super(context) << :"author.name"
338
+ end
339
+ end
340
+ ```
341
+ The request will look something like:
342
+ ```
343
+ GET /books?include=author&sort=author.name
344
+ ```
345
+
346
+ ###### Default sorting
347
+
348
+ By default JR sorts ascending on the `id` of the primary resource, unless the request specifies an alternate sort order.
349
+ To override this you may override the `self.default_sort` on a `resource`. `default_sort` should return an array of
350
+ `sort_param` hashes. A `sort_param` hash contains a `field` and a `direction`, with `direction` being either `:asc` or
351
+ `:desc`.
352
+
353
+ For example:
354
+
355
+ ```ruby
356
+ def self.default_sort
357
+ [{field: 'name_last', direction: :desc}, {field: 'name_first', direction: :desc}]
358
+ end
359
+ ```
360
+
361
+ ##### Attribute Formatting
362
+
363
+ Attributes can have a `Format`. By default all attributes use the default formatter. If an attribute has the `format`
364
+ option set the system will attempt to find a formatter based on this name. In the following example the `last_login_time`
365
+ will be returned formatted to a certain time zone:
366
+
367
+ ```ruby
368
+ class PersonResource < JSONAPI::Resource
369
+ attributes :name, :email
370
+ attribute :last_login_time, format: :date_with_timezone
371
+ end
372
+ ```
373
+
374
+ The system will lookup a value formatter named `DateWithTimezoneValueFormatter` and will use this when serializing and
375
+ updating the attribute. See the [Value Formatters](#value-formatters) section for more details.
376
+
377
+ ##### Flattening a Rails relationship
378
+
379
+ It is possible to flatten Rails relationships into attributes by using getters and setters. This can become handy if a relation needs to be created alongside the creation of the main object which can be the case if there is a bi-directional presence validation. For example:
380
+
381
+ ```ruby
382
+ # Given Models
383
+ class Person < ActiveRecord::Base
384
+ has_many :spoken_languages
385
+ validates :name, :email, :spoken_languages, presence: true
386
+ end
387
+
388
+ class SpokenLanguage < ActiveRecord::Base
389
+ belongs_to :person, inverse_of: :spoken_languages
390
+ validates :person, :language_code, presence: true
391
+ end
392
+
393
+ # Resource with getters and setter
394
+ class PersonResource < JSONAPI::Resource
395
+ attributes :name, :email, :spoken_languages
396
+
397
+ # Getter
398
+ def spoken_languages
399
+ @model.spoken_languages.pluck(:language_code)
400
+ end
401
+
402
+ # Setter (because spoken_languages needed for creation)
403
+ def spoken_languages=(new_spoken_language_codes)
404
+ @model.spoken_languages.destroy_all
405
+ new_spoken_language_codes.each do |new_lang_code|
406
+ @model.spoken_languages.build(language_code: new_lang_code)
407
+ end
408
+ end
409
+ end
410
+ ```
411
+
412
+ #### Primary Key
413
+
414
+ Resources are always represented using a key of `id`. The resource will interrogate the model to find the primary key.
415
+ If the underlying model does not use `id` as the primary key _and_ does not support the `primary_key` method you
416
+ must use the `primary_key` method to tell the resource which field on the model to use as the primary key. **Note:**
417
+ this _must_ be the actual primary key of the model.
418
+
419
+ By default only integer values are allowed for primary key. To change this behavior you can set the `resource_key_type`
420
+ configuration option:
421
+
422
+ ```ruby
423
+ JSONAPI.configure do |config|
424
+ # Allowed values are :integer(default), :uuid, :string, or a proc
425
+ config.resource_key_type = :uuid
426
+ end
427
+ ```
428
+
429
+ ##### Override key type on a resource
430
+
431
+ You can override the default resource key type on a per-resource basis by calling `key_type` in the resource class,
432
+ with the same allowed values as the `resource_key_type` configuration option.
433
+
434
+ ```ruby
435
+ class ContactResource < JSONAPI::Resource
436
+ attribute :id
437
+ attributes :name_first, :name_last, :email, :twitter
438
+ key_type :uuid
439
+ end
440
+ ```
441
+
442
+ ##### Custom resource key validators
443
+
444
+ If you need more control over the key, you can override the #verify_key method on your resource, or set a lambda that
445
+ accepts key and context arguments in `config/initializers/jsonapi_resources.rb`:
446
+
447
+ ```ruby
448
+ JSONAPI.configure do |config|
449
+ config.resource_key_type = -> (key, context) { key && String(key) }
450
+ end
451
+ ```
452
+
453
+ #### Model Name
454
+
455
+ The name of the underlying model is inferred from the Resource name. It can be overridden by use of the `model_name`
456
+ method. For example:
457
+
458
+ ```ruby
459
+ class AuthorResource < JSONAPI::Resource
460
+ attribute :name
461
+ model_name 'Person'
462
+ has_many :posts
463
+ end
464
+ ```
465
+
466
+ #### Model Hints
467
+
468
+ Resource instances are created from model records. The determination of the correct resource type is performed using a
469
+ simple rule based on the model's name. The name is used to find a resource in the same module (as the originating
470
+ resource) that matches the name. This usually works quite well, however it can fail when model names do not match
471
+ resource names. It can also fail when using namespaced models. In this case a `model_hint` can be created to map model
472
+ names to resources. For example:
473
+
474
+ ```ruby
475
+ class AuthorResource < JSONAPI::Resource
476
+ attribute :name
477
+ model_name 'Person'
478
+ model_hint model: Commenter, resource: :special_person
479
+
480
+ has_many :posts
481
+ has_many :commenters
482
+ end
483
+ ```
484
+
485
+ Note that when `model_name` is set a corresponding `model_hint` is also added. This can be skipped by using the
486
+ `add_model_hint` option set to false. For example:
487
+
488
+ ```ruby
489
+ class AuthorResource < JSONAPI::Resource
490
+ model_name 'Legacy::Person', add_model_hint: false
491
+ end
492
+ ```
493
+
494
+ Model hints inherit from parent resources, but are not global in scope. The `model_hint` method accepts `model` and
495
+ `resource` named parameters. `model` takes an ActiveRecord class or class name (defaults to the model name), and
496
+ `resource` takes a resource type or a resource class (defaults to the current resource's type).
497
+
498
+ #### Relationships
499
+
500
+ Related resources need to be specified in the resource. These may be declared with the `relationship` or the `has_one`
501
+ and the `has_many` methods.
502
+
503
+ Here's a simple example using the `relationship` method where a post has a single author and an author can have many
504
+ posts:
505
+
506
+ ```ruby
507
+ class PostResource < JSONAPI::Resource
508
+ attributes :title, :body
509
+
510
+ relationship :author, to: :one
511
+ end
512
+ ```
513
+
514
+ And the corresponding author:
515
+
516
+ ```ruby
517
+ class AuthorResource < JSONAPI::Resource
518
+ attribute :name
519
+
520
+ relationship :posts, to: :many
521
+ end
522
+ ```
523
+
524
+ And here's the equivalent resources using the `has_one` and `has_many` methods:
525
+
526
+ ```ruby
527
+ class PostResource < JSONAPI::Resource
528
+ attributes :title, :body
529
+
530
+ has_one :author
531
+ end
532
+ ```
533
+
534
+ And the corresponding author:
535
+
536
+ ```ruby
537
+ class AuthorResource < JSONAPI::Resource
538
+ attribute :name
539
+
540
+ has_many :posts
541
+ end
542
+ ```
543
+
544
+ ##### Options
545
+
546
+ The relationship methods (`relationship`, `has_one`, and `has_many`) support the following options:
547
+
548
+ * `class_name` - a string specifying the underlying class for the related resource. Defaults to the `class_name` property on the underlying model.
549
+ * `foreign_key` - the method on the resource used to fetch the related resource. Defaults to `<resource_name>_id` for has_one and `<resource_name>_ids` for has_many relationships.
550
+ * `acts_as_set` - allows the entire set of related records to be replaced in one operation. Defaults to false if not set.
551
+ * `polymorphic` - set to true to identify relationships that are polymorphic.
552
+ * `relation_name` - the name of the relation to use on the model. A lambda may be provided which allows conditional selection of the relation based on the context.
553
+ * `always_include_linkage_data` - if set to true, the relationship includes linkage data. Defaults to false if not set.
554
+ * `eager_load_on_include` - if set to false, will not include this relationship in join SQL when requested via an include. You usually want to leave this on, but it will break 'relationships' which are not active record, for example if you want to expose a tree using the `ancestry` gem or similar, or the SQL query becomes too large to handle. Defaults to true if not set.
555
+
556
+ `to_one` relationships support the additional option:
557
+ * `foreign_key_on` - defaults to `:self`. To indicate that the foreign key is on the related resource specify `:related`.
558
+
559
+ `to_many` relationships support the additional option:
560
+ * `reflect` - defaults to `true`. To indicate that updates to the relationship are performed on the related resource, if relationship reflection is turned on. See [Configuration] (#configuration)
561
+
562
+ Examples:
563
+
564
+ ```ruby
565
+ class CommentResource < JSONAPI::Resource
566
+ attributes :body
567
+ has_one :post
568
+ has_one :author, class_name: 'Person'
569
+ has_many :tags, acts_as_set: true
570
+ end
571
+
572
+ class ExpenseEntryResource < JSONAPI::Resource
573
+ attributes :cost, :transaction_date
574
+
575
+ has_one :currency, class_name: 'Currency', foreign_key: 'currency_code'
576
+ has_one :employee
577
+ end
578
+
579
+ class TagResource < JSONAPI::Resource
580
+ attributes :name
581
+ has_one :taggable, polymorphic: true
582
+ end
583
+ ```
584
+
585
+ ```ruby
586
+ class BookResource < JSONAPI::Resource
587
+
588
+ # Only book_admins may see unapproved comments for a book. Using
589
+ # a lambda to select the correct relation on the model
590
+ has_many :book_comments, relation_name: -> (options = {}) {
591
+ context = options[:context]
592
+ current_user = context ? context[:current_user] : nil
593
+
594
+ unless current_user && current_user.book_admin
595
+ :approved_book_comments
596
+ else
597
+ :book_comments
598
+ end
599
+ }
600
+ ...
601
+ end
602
+ ```
603
+
604
+ The polymorphic relationship will require the resource and controller to exist, although routing to them will cause an
605
+ error.
606
+
607
+ ```ruby
608
+ class TaggableResource < JSONAPI::Resource; end
609
+ class TaggablesController < JSONAPI::ResourceController; end
610
+ ```
611
+
612
+ #### Filters
613
+
614
+ Filters for locating objects of the resource type are specified in the resource definition. Single filters can be
615
+ declared using the `filter` method, and multiple filters can be declared with the `filters` method on the resource
616
+ class.
617
+
618
+ For example:
619
+
620
+ ```ruby
621
+ class ContactResource < JSONAPI::Resource
622
+ attributes :name_first, :name_last, :email, :twitter
623
+
624
+ filter :id
625
+ filters :name_first, :name_last
626
+ end
627
+ ```
628
+
629
+ Then a request could pass in a filter for example `http://example.com/contacts?filter[name_last]=Smith` and the system
630
+ will find all people where the last name exactly matches Smith.
631
+
632
+ ##### Default Filters
633
+
634
+ A default filter may be defined for a resource using the `default` option on the `filter` method. This default is used
635
+ unless the request overrides this value.
636
+
637
+ For example:
638
+
639
+ ```ruby
640
+ class CommentResource < JSONAPI::Resource
641
+ attributes :body, :status
642
+ has_one :post
643
+ has_one :author
644
+
645
+ filter :status, default: 'published,pending'
646
+ end
647
+ ```
648
+
649
+ The default value is used as if it came from the request.
650
+
651
+ ##### Applying Filters
652
+
653
+ You may customize how a filter behaves by supplying a callable to the `:apply` option. This callable will be used to
654
+ apply that filter. The callable is passed the `records`, which is an `ActiveRecord::Relation`, the `value`, and an
655
+ `_options` hash. It is expected to return an `ActiveRecord::Relation`.
656
+
657
+ Note: When a filter is not supplied a `verify` callable to modify the `value` that the `apply` callable receives,
658
+ `value` defaults to an array of the string values provided to the filter parameter.
659
+
660
+ This example shows how you can implement different approaches for different filters.
661
+
662
+ ```ruby
663
+ # When given the following parameter:'filter[visibility]': 'public'
664
+
665
+ filter :visibility, apply: ->(records, value, _options) {
666
+ records.where('users.publicly_visible = ?', value[0] == 'public')
667
+ }
668
+ ```
669
+
670
+ If you omit the `apply` callable the filter will be applied as `records.where(filter => value)`.
671
+
672
+ Note: It is also possible to override the `self.apply_filter` method, though this approach is now deprecated:
673
+
674
+ ```ruby
675
+ def self.apply_filter(records, filter, value, options)
676
+ case filter
677
+ when :last_name, :first_name, :name
678
+ if value.is_a?(Array)
679
+ value.each do |val|
680
+ records = records.where(_model_class.arel_table[filter].matches(val))
681
+ end
682
+ records
683
+ else
684
+ records.where(_model_class.arel_table[filter].matches(value))
685
+ end
686
+ else
687
+ super(records, filter, value)
688
+ end
689
+ end
690
+ ```
691
+
692
+ ##### Verifying Filters
693
+
694
+ Because filters typically come straight from the request, it's prudent to verify their values. To do so, provide a
695
+ callable to the `verify` option. This callable will be passed the `value` and the `context`. Verify should return the
696
+ verified value, which may be modified.
697
+
698
+ ```ruby
699
+ filter :ids,
700
+ verify: ->(values, context) {
701
+ verify_keys(values, context)
702
+ values
703
+ },
704
+ apply: ->(records, value, _options) {
705
+ records.where('id IN (?)', value)
706
+ }
707
+ ```
708
+
709
+ ```ruby
710
+ # A more complex example, showing how to filter for any overlap between the
711
+ # value array and the possible_ids, using both verify and apply callables.
712
+
713
+ filter :possible_ids,
714
+ verify: ->(values, context) {
715
+ values.map {|value| value.to_i}
716
+ },
717
+ apply: ->(records, value, _options) {
718
+ records.where('possible_ids && ARRAY[?]', value)
719
+ }
720
+ ```
721
+
722
+ ##### Finders
723
+
724
+ Basic finding by filters is supported by resources. This is implemented in the `find` and `find_by_key` finder methods.
725
+ Currently this is implemented for `ActiveRecord` based resources. The finder methods rely on the `records` method to get
726
+ an `ActiveRecord::Relation` relation. It is therefore possible to override `records` to affect the three find related
727
+ methods.
728
+
729
+ ###### Customizing base records for finder methods
730
+
731
+ If you need to change the base records on which `find` and `find_by_key` operate, you can override the `records` method
732
+ on the resource class.
733
+
734
+ For example to allow a user to only retrieve his own posts you can do the following:
735
+
736
+ ```ruby
737
+ class PostResource < JSONAPI::Resource
738
+ attributes :title, :body
739
+
740
+ def self.records(options = {})
741
+ context = options[:context]
742
+ context[:current_user].posts
743
+ end
744
+ end
745
+ ```
746
+
747
+ When you create a relationship, a method is created to fetch record(s) for that relationship, using the relation name
748
+ for the relationship.
749
+
750
+ ```ruby
751
+ class PostResource < JSONAPI::Resource
752
+ has_one :author
753
+ has_many :comments
754
+
755
+ # def record_for_author
756
+ # relationship = self.class._relationship(:author)
757
+ # relation_name = relationship.relation_name(context: @context)
758
+ # records_for(relation_name)
759
+ # end
760
+
761
+ # def records_for_comments
762
+ # relationship = self.class._relationship(:comments)
763
+ # relation_name = relationship.relation_name(context: @context)
764
+ # records_for(relation_name)
765
+ # end
766
+ end
767
+
768
+ ```
769
+
770
+ For example, you may want to raise an error if the user is not authorized to view the related records. See the next
771
+ section for additional details on raising errors.
772
+
773
+ ```ruby
774
+ class BaseResource < JSONAPI::Resource
775
+ def records_for(relation_name)
776
+ context = options[:context]
777
+ records = _model.public_send(relation_name)
778
+
779
+ unless context[:current_user].can_view?(records)
780
+ raise NotAuthorizedError
781
+ end
782
+
783
+ records
784
+ end
785
+ end
786
+ ```
787
+
788
+ ###### Raising Errors
789
+
790
+ Inside the finder methods (like `records_for`) or inside of resource callbacks
791
+ (like `before_save`) you can `raise` an error to halt processing. JSONAPI::Resources
792
+ has some built in errors that will return appropriate error codes. By
793
+ default any other error that you raise will return a `500` status code
794
+ for a general internal server error.
795
+
796
+ To return useful error codes that represent application errors you
797
+ should set the `exception_class_whitelist` config variable, and then you
798
+ should use the Rails `rescue_from` macro to render a status code.
799
+
800
+ For example, this config setting allows the `NotAuthorizedError` to bubble up out of
801
+ JSONAPI::Resources and into your application.
802
+
803
+ ```ruby
804
+ # config/initializer/jsonapi-resources.rb
805
+ JSONAPI.configure do |config|
806
+ config.exception_class_whitelist = [NotAuthorizedError]
807
+ end
808
+ ```
809
+
810
+ Handling the error and rendering the appropriate code is now the responsibility of the
811
+ application and could be handled like this:
812
+
813
+ ```ruby
814
+ class ApiController < ApplicationController
815
+ rescue_from NotAuthorizedError, with: :reject_forbidden_request
816
+ def reject_forbidden_request
817
+ render json: {error: 'Forbidden'}, :status => 403
818
+ end
819
+ end
820
+ ```
821
+
822
+
823
+ ###### Applying Filters
824
+
825
+ The `apply_filter` method is called to apply each filter to the `Arel` relation. You may override this method to gain
826
+ control over how the filters are applied to the `Arel` relation.
827
+
828
+ This example shows how you can implement different approaches for different filters.
829
+
830
+ ```ruby
831
+ def self.apply_filter(records, filter, value, options)
832
+ case filter
833
+ when :visibility
834
+ records.where('users.publicly_visible = ?', value == :public)
835
+ when :last_name, :first_name, :name
836
+ if value.is_a?(Array)
837
+ value.each do |val|
838
+ records = records.where(_model_class.arel_table[filter].matches(val))
839
+ end
840
+ records
841
+ else
842
+ records.where(_model_class.arel_table[filter].matches(value))
843
+ end
844
+ else
845
+ super(records, filter, value)
846
+ end
847
+ end
848
+ ```
849
+
850
+
851
+ ###### Applying Sorting
852
+
853
+ You can override the `apply_sort` method to gain control over how the sorting is done. This may be useful in case you'd
854
+ like to base the sorting on variables in your context.
855
+
856
+ Example:
857
+
858
+ ```ruby
859
+ def self.apply_sort(records, order_options, context = {})
860
+ if order_options.has?(:trending)
861
+ records = records.order_by_trending_scope
862
+ order_options - [:trending]
863
+ end
864
+
865
+ super(records, order_options, context)
866
+ end
867
+ ```
868
+
869
+
870
+ ###### Override finder methods
871
+
872
+ Finally if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the
873
+ resource class.
874
+
875
+ Here's an example that defers the `find` operation to a `current_user` set on the `context` option:
876
+
877
+ ```ruby
878
+ class AuthorResource < JSONAPI::Resource
879
+ attribute :name
880
+ model_name 'Person'
881
+ has_many :posts
882
+
883
+ filter :name
884
+
885
+ def self.find(filters, options = {})
886
+ context = options[:context]
887
+ authors = context[:current_user].find_authors(filters)
888
+
889
+ return authors.map do |author|
890
+ self.new(author, context)
891
+ end
892
+ end
893
+ end
894
+ ```
895
+
896
+ #### Pagination
897
+
898
+ Pagination is performed using a `paginator`, which is a class responsible for parsing the `page` request parameters and
899
+ applying the pagination logic to the results.
900
+
901
+ ##### Paginators
902
+
903
+ `JSONAPI::Resource` supports several pagination methods by default, and allows you to implement a custom system if the
904
+ defaults do not meet your needs.
905
+
906
+ ###### Paged Paginator
907
+
908
+ The `paged` `paginator` returns results based on pages of a fixed size. Valid `page` parameters are `number` and `size`.
909
+ If `number` is omitted the first page is returned. If `size` is omitted the `default_page_size` from the configuration
910
+ settings is used.
911
+
912
+ ```
913
+ GET /articles?page%5Bnumber%5D=10&page%5Bsize%5D=10 HTTP/1.1
914
+ Accept: application/vnd.api+json
915
+ ```
916
+
917
+ ###### Offset Paginator
918
+
919
+ The `offset` `paginator` returns results based on an offset from the beginning of the resultset. Valid `page` parameters
920
+ are `offset` and `limit`. If `offset` is omitted a value of 0 will be used. If `limit` is omitted the `default_page_size`
921
+ from the configuration settings is used.
922
+
923
+ ```
924
+ GET /articles?page%5Blimit%5D=10&page%5Boffset%5D=10 HTTP/1.1
925
+ Accept: application/vnd.api+json
926
+ ```
927
+
928
+ ###### Custom Paginators
929
+
930
+ Custom `paginators` can be used. These should derive from `Paginator`. The `apply` method takes a `relation` and
931
+ `order_options` and is expected to return a `relation`. The `initialize` method receives the parameters from the `page`
932
+ request parameters. It is up to the paginator author to parse and validate these parameters.
933
+
934
+ For example, here is a very simple single record at a time paginator:
935
+
936
+ ```ruby
937
+ class SingleRecordPaginator < JSONAPI::Paginator
938
+ def initialize(params)
939
+ # param parsing and validation here
940
+ @page = params.to_i
941
+ end
942
+
943
+ def apply(relation, order_options)
944
+ relation.offset(@page).limit(1)
945
+ end
946
+ end
947
+ ```
948
+
949
+ ##### Paginator Configuration
950
+
951
+ The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example, in your
952
+ `config/initializers/jsonapi_resources.rb`:
953
+
954
+ ```ruby
955
+ JSONAPI.configure do |config|
956
+ # built in paginators are :none, :offset, :paged
957
+ config.default_paginator = :offset
958
+
959
+ config.default_page_size = 10
960
+ config.maximum_page_size = 20
961
+ end
962
+ ```
963
+
964
+ If no `default_paginator` is configured, pagination will be disabled by default.
965
+
966
+ Paginators can also be set at the resource-level, which will override the default setting. This is done using the
967
+ `paginator` method:
968
+
969
+ ```ruby
970
+ class BookResource < JSONAPI::Resource
971
+ attribute :title
972
+ attribute :isbn
973
+
974
+ paginator :offset
975
+ end
976
+ ```
977
+
978
+ To disable pagination in a resource, specify `:none` for `paginator`.
979
+
980
+ #### Included relationships (side-loading resources)
981
+
982
+ JR supports [request include params](http://jsonapi.org/format/#fetching-includes) out of the box, for side loading related resources.
983
+
984
+ Here's an example from the spec:
985
+
986
+ ```
987
+ GET /articles/1?include=comments HTTP/1.1
988
+ Accept: application/vnd.api+json
989
+ ```
990
+
991
+ Will get you the following payload by default:
992
+
993
+ ```
994
+ {
995
+ "data": {
996
+ "type": "articles",
997
+ "id": "1",
998
+ "attributes": {
999
+ "title": "JSON API paints my bikeshed!"
1000
+ },
1001
+ "links": {
1002
+ "self": "http://example.com/articles/1"
1003
+ },
1004
+ "relationships": {
1005
+ "comments": {
1006
+ "links": {
1007
+ "self": "http://example.com/articles/1/relationships/comments",
1008
+ "related": "http://example.com/articles/1/comments"
1009
+ },
1010
+ "data": [
1011
+ { "type": "comments", "id": "5" },
1012
+ { "type": "comments", "id": "12" }
1013
+ ]
1014
+ }
1015
+ }
1016
+ },
1017
+ "included": [{
1018
+ "type": "comments",
1019
+ "id": "5",
1020
+ "attributes": {
1021
+ "body": "First!"
1022
+ },
1023
+ "links": {
1024
+ "self": "http://example.com/comments/5"
1025
+ }
1026
+ }, {
1027
+ "type": "comments",
1028
+ "id": "12",
1029
+ "attributes": {
1030
+ "body": "I like XML better"
1031
+ },
1032
+ "links": {
1033
+ "self": "http://example.com/comments/12"
1034
+ }
1035
+ }]
1036
+ }
1037
+ ```
1038
+
1039
+ Note: When passing `include` and `fields` params together, relationships not included in the `fields` parameter will not be serialized. This will have the side effect of not serializing the included resources. To ensure the related resources are properly side loaded specify them in the `fields`, like `fields[posts]=comments,title&include=comments`.
1040
+
1041
+ #### Resource Meta
1042
+
1043
+ Meta information can be included for each resource using the meta method in the resource declaration. For example:
1044
+
1045
+ ```ruby
1046
+ class BookResource < JSONAPI::Resource
1047
+ attribute :title
1048
+ attribute :isbn
1049
+
1050
+ def meta(options)
1051
+ {
1052
+ copyright: 'API Copyright 2015 - XYZ Corp.',
1053
+ computed_copyright: options[:serialization_options][:copyright],
1054
+ last_updated_at: _model.updated_at
1055
+ }
1056
+ end
1057
+ end
1058
+
1059
+ ```
1060
+
1061
+ The `meta` method will be called for each resource instance. Override the `meta` method on a resource class to control
1062
+ the meta information for the resource. If a non empty hash is returned from `meta` this will be serialized. The `meta`
1063
+ method is called with an `options` hash. The `options` hash will contain the following:
1064
+
1065
+ * `:serializer` -> the serializer instance
1066
+ * `:serialization_options` -> the contents of the `serialization_options` method on the controller.
1067
+
1068
+ #### Custom Links
1069
+
1070
+ Custom links can be included for each resource by overriding the `custom_links` method. If a non empty hash is returned from `custom_links`, it will be merged with the default links hash containing the resource's `self` link. The `custom_links` method is called with the same `options` hash used by for [resource meta information](#resource-meta). The `options` hash contains the following:
1071
+
1072
+ * `:serializer` -> the serializer instance
1073
+ * `:serialization_options` -> the contents of the `serialization_options` method on the controller.
1074
+
1075
+ For example:
1076
+
1077
+ ```ruby
1078
+ class CityCouncilMeeting < JSONAPI::Resource
1079
+ attribute :title, :location, :approved
1080
+
1081
+ def custom_links(options)
1082
+ { minutes: options[:serializer].link_builder.self_link(self) + "/minutes" }
1083
+ end
1084
+ end
1085
+ ```
1086
+
1087
+ This will create a custom link with the key `minutes`, which will be merged with the default `self` link, like so:
1088
+
1089
+ ```json
1090
+ {
1091
+ "data": [
1092
+ {
1093
+ "id": "1",
1094
+ "type": "cityCouncilMeetings",
1095
+ "links": {
1096
+ "self": "http://city.gov/api/city-council-meetings/1",
1097
+ "minutes": "http://city.gov/api/city-council-meetings/1/minutes"
1098
+ },
1099
+ "attributes": {...}
1100
+ },
1101
+ //...
1102
+ ]
1103
+ }
1104
+ ```
1105
+
1106
+ Of course, the `custom_links` method can include logic to include links only when relevant:
1107
+
1108
+ ````ruby
1109
+ class CityCouncilMeeting < JSONAPI::Resource
1110
+ attribute :title, :location, :approved
1111
+
1112
+ delegate :approved?, to: :model
1113
+
1114
+ def custom_links(options)
1115
+ extra_links = {}
1116
+ if approved?
1117
+ extra_links[:minutes] = options[:serializer].link_builder.self_link(self) + "/minutes"
1118
+ end
1119
+ extra_links
1120
+ end
1121
+ end
1122
+ ```
1123
+
1124
+ It's also possibly to suppress the default `self` link by returning a hash with `{self: nil}`:
1125
+
1126
+ ````ruby
1127
+ class Selfless < JSONAPI::Resource
1128
+ def custom_links(options)
1129
+ {self: nil}
1130
+ end
1131
+ end
1132
+ ```
1133
+
1134
+ #### Callbacks
1135
+
1136
+ `ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be
1137
+ used to from `ActiveRecord`.
1138
+
1139
+ For example, you might use a callback to perform authorization on your resource before an action.
1140
+
1141
+ ```ruby
1142
+ class BaseResource < JSONAPI::Resource
1143
+ before_create :authorize_create
1144
+
1145
+ def authorize_create
1146
+ # ...
1147
+ end
1148
+ end
1149
+ ```
1150
+
1151
+ The types of supported callbacks are:
1152
+ - `before`
1153
+ - `after`
1154
+ - `around`
1155
+
1156
+ ##### `JSONAPI::Resource` Callbacks
1157
+
1158
+ Callbacks can be defined for the following `JSONAPI::Resource` events:
1159
+
1160
+ - `:create`
1161
+ - `:update`
1162
+ - `:remove`
1163
+ - `:save`
1164
+ - `:create_to_many_link`
1165
+ - `:replace_to_many_links`
1166
+ - `:create_to_one_link`
1167
+ - `:replace_to_one_link`
1168
+ - `:remove_to_many_link`
1169
+ - `:remove_to_one_link`
1170
+ - `:replace_fields`
1171
+
1172
+ ###### Relationship Reflection
1173
+
1174
+ By default updates to relationships only invoke callbacks on the primary
1175
+ Resource. By setting the `use_relationship_reflection` [Configuration] (#configuration) option
1176
+ updates to `has_many` relationships will occur on the related resource, triggering
1177
+ callbacks on both resources.
1178
+
1179
+ ##### `JSONAPI::Processor` Callbacks
1180
+
1181
+ Callbacks can also be defined for `JSONAPI::Processor` events:
1182
+ - `:operation`: Any individual operation.
1183
+ - `:find`: A `find` operation is being processed.
1184
+ - `:show`: A `show` operation is being processed.
1185
+ - `:show_relationship`: A `show_relationship` operation is being processed.
1186
+ - `:show_related_resource`: A `show_related_resource` operation is being processed.
1187
+ - `:show_related_resources`: A `show_related_resources` operation is being processed.
1188
+ - `:create_resource`: A `create_resource` operation is being processed.
1189
+ - `:remove_resource`: A `remove_resource` operation is being processed.
1190
+ - `:replace_fields`: A `replace_fields` operation is being processed.
1191
+ - `:replace_to_one_relationship`: A `replace_to_one_relationship` operation is being processed.
1192
+ - `:create_to_many_relationship`: A `create_to_many_relationship` operation is being processed.
1193
+ - `:replace_to_many_relationship`: A `replace_to_many_relationship` operation is being processed.
1194
+ - `:remove_to_many_relationship`: A `remove_to_many_relationship` operation is being processed.
1195
+ - `:remove_to_one_relationship`: A `remove_to_one_relationship` operation is being processed.
1196
+
1197
+ See [Operation Processors] (#operation-processors) for details on using OperationProcessors
1198
+
1199
+ ##### `JSONAPI::OperationsProcessor` Callbacks (a removed feature)
1200
+
1201
+ Note: The `JSONAPI::OperationsProcessor` has been removed and replaced with the `JSONAPI::OperationDispatcher`
1202
+ and `Processor` classes per resource. The callbacks have been renamed and moved to the
1203
+ `Processor`s, with the exception of the `operations` callback which is now on the controller.
1204
+
1205
+ ### Controllers
1206
+
1207
+ There are two ways to implement a controller for your resources. Either derive from `ResourceController` or import
1208
+ the `ActsAsResourceController` module.
1209
+
1210
+ ##### ResourceController
1211
+
1212
+ `JSONAPI::Resources` provides a class, `ResourceController`, that can be used as the base class for your controllers.
1213
+ `ResourceController` supports `index`, `show`, `create`, `update`, and `destroy` methods. Just deriving your controller
1214
+ from `ResourceController` will give you a fully functional controller.
1215
+
1216
+ For example:
1217
+
1218
+ ```ruby
1219
+ class PeopleController < JSONAPI::ResourceController
1220
+
1221
+ end
1222
+ ```
1223
+
1224
+ Of course you are free to extend this as needed and override action handlers or other methods.
1225
+
1226
+ A jsonapi-controller generator is avaliable
1227
+
1228
+ ```
1229
+ rails generate jsonapi:controller contact
1230
+ ```
1231
+
1232
+ ###### ResourceControllerMetal
1233
+
1234
+ `JSONAPI::Resources` also provides an alternative class to `ResourceController` called `ResourceControllerMetal`.
1235
+ In order to provide a lighter weight controller option this strips the controller down to just the classes needed
1236
+ to work with `JSONAPI::Resources`.
1237
+
1238
+ For example:
1239
+
1240
+ ```ruby
1241
+ class PeopleController < JSONAPI::ResourceControllerMetal
1242
+
1243
+ end
1244
+ ```
1245
+
1246
+ Note: This may not provide all of the expected controller capabilities if you are using additional gems such as DoorKeeper.
1247
+
1248
+ ###### Serialization Options
1249
+
1250
+ Additional options can be passed to the serializer using the `serialization_options` method.
1251
+
1252
+ For example:
1253
+
1254
+ ```ruby
1255
+ class ApplicationController < JSONAPI::ResourceController
1256
+ def serialization_options
1257
+ {copyright: 'Copyright 2015'}
1258
+ end
1259
+ end
1260
+ ```
1261
+
1262
+ These `serialization_options` are passed to the `meta` method used to generate resource `meta` values.
1263
+
1264
+ ##### ActsAsResourceController
1265
+
1266
+ `JSONAPI::Resources` also provides a module, `JSONAPI::ActsAsResourceController`. You can include this module to
1267
+ mix in all the features of `ResourceController` into your existing controller class.
1268
+
1269
+ For example:
1270
+
1271
+ ```ruby
1272
+ class PostsController < ActionController::Base
1273
+ include JSONAPI::ActsAsResourceController
1274
+ end
1275
+ ```
1276
+
1277
+ #### Namespaces
1278
+
1279
+ JSONAPI::Resources supports namespacing of controllers and resources. With namespacing you can version your API.
1280
+
1281
+ If you namespace your controller it will require a namespaced resource.
1282
+
1283
+ In the following example we have a `resource` that isn't namespaced, and one that has now been namespaced. There are
1284
+ slight differences between the two resources, as might be seen in a new version of an API:
1285
+
1286
+ ```ruby
1287
+ class PostResource < JSONAPI::Resource
1288
+ attribute :title
1289
+ attribute :body
1290
+ attribute :subject
1291
+
1292
+ has_one :author, class_name: 'Person'
1293
+ has_one :section
1294
+ has_many :tags, acts_as_set: true
1295
+ has_many :comments, acts_as_set: false
1296
+ def subject
1297
+ @model.title
1298
+ end
1299
+
1300
+ filters :title, :author, :tags, :comments
1301
+ filter :id
1302
+ end
1303
+
1304
+ ...
1305
+
1306
+ module Api
1307
+ module V1
1308
+ class PostResource < JSONAPI::Resource
1309
+ # V1 replaces the non-namespaced resource
1310
+ # V1 no longer supports tags and now calls author 'writer'
1311
+ attribute :title
1312
+ attribute :body
1313
+ attribute :subject
1314
+
1315
+ has_one :writer, foreign_key: 'author_id'
1316
+ has_one :section
1317
+ has_many :comments, acts_as_set: false
1318
+
1319
+ def subject
1320
+ @model.title
1321
+ end
1322
+
1323
+ filters :writer
1324
+ end
1325
+
1326
+ class WriterResource < JSONAPI::Resource
1327
+ attributes :name, :email
1328
+ model_name 'Person'
1329
+ has_many :posts
1330
+
1331
+ filter :name
1332
+ end
1333
+ end
1334
+ end
1335
+ ```
1336
+
1337
+ The following controllers are used:
1338
+
1339
+ ```ruby
1340
+ class PostsController < JSONAPI::ResourceController
1341
+ end
1342
+
1343
+ module Api
1344
+ module V1
1345
+ class PostsController < JSONAPI::ResourceController
1346
+ end
1347
+ end
1348
+ end
1349
+ ```
1350
+
1351
+ You will also need to namespace your routes:
1352
+
1353
+ ```ruby
1354
+ Rails.application.routes.draw do
1355
+
1356
+ jsonapi_resources :posts
1357
+
1358
+ namespace :api do
1359
+ namespace :v1 do
1360
+ jsonapi_resources :posts
1361
+ end
1362
+ end
1363
+ end
1364
+ ```
1365
+
1366
+ When a namespaced `resource` is used, any related `resources` must also be in the same namespace.
1367
+
1368
+ #### Error codes
1369
+
1370
+ Error codes are provided for each error object returned, based on the error. These errors are:
1371
+
1372
+ ```ruby
1373
+ module JSONAPI
1374
+ VALIDATION_ERROR = '100'
1375
+ INVALID_RESOURCE = '101'
1376
+ FILTER_NOT_ALLOWED = '102'
1377
+ INVALID_FIELD_VALUE = '103'
1378
+ INVALID_FIELD = '104'
1379
+ PARAM_NOT_ALLOWED = '105'
1380
+ PARAM_MISSING = '106'
1381
+ INVALID_FILTER_VALUE = '107'
1382
+ COUNT_MISMATCH = '108'
1383
+ KEY_ORDER_MISMATCH = '109'
1384
+ KEY_NOT_INCLUDED_IN_URL = '110'
1385
+ INVALID_INCLUDE = '112'
1386
+ RELATION_EXISTS = '113'
1387
+ INVALID_SORT_CRITERIA = '114'
1388
+ INVALID_LINKS_OBJECT = '115'
1389
+ TYPE_MISMATCH = '116'
1390
+ INVALID_PAGE_OBJECT = '117'
1391
+ INVALID_PAGE_VALUE = '118'
1392
+ INVALID_FIELD_FORMAT = '119'
1393
+ INVALID_FILTERS_SYNTAX = '120'
1394
+ SAVE_FAILED = '121'
1395
+ FORBIDDEN = '403'
1396
+ RECORD_NOT_FOUND = '404'
1397
+ NOT_ACCEPTABLE = '406'
1398
+ UNSUPPORTED_MEDIA_TYPE = '415'
1399
+ LOCKED = '423'
1400
+ end
1401
+ ```
1402
+
1403
+ These codes can be customized in your app by creating an initializer to override any or all of the codes.
1404
+
1405
+ In addition textual error codes can be returned by setting the configuration option `use_text_errors = true`. For
1406
+ example:
1407
+
1408
+ ```ruby
1409
+ JSONAPI.configure do |config|
1410
+ config.use_text_errors = true
1411
+ end
1412
+ ```
1413
+
1414
+
1415
+ #### Handling Exceptions
1416
+
1417
+ By default, all exceptions raised downstream from a resource controller will be caught, logged, and a ```500 Internal Server Error``` will be rendered. Exceptions can be whitelisted in the config to pass through the handler and be caught manually, or you can pass a callback from a resource controller to insert logic into the rescue block without interrupting the control flow. This can be particularly useful for additional logging or monitoring without the added work of rendering responses.
1418
+
1419
+ Pass a block, refer to controller class methods, or both. Note that methods must be defined as class methods on a controller and accept one parameter, which is passed the exception object that was rescued.
1420
+
1421
+ ```ruby
1422
+ class ApplicationController < JSONAPI::ResourceController
1423
+
1424
+ on_server_error :first_callback
1425
+
1426
+ #or
1427
+
1428
+ # on_server_error do |error|
1429
+ #do things
1430
+ #end
1431
+
1432
+ def self.first_callback(error)
1433
+ #env["airbrake.error_id"] = notify_airbrake(error)
1434
+ end
1435
+ end
1436
+
1437
+ ```
1438
+
1439
+ #### Action Callbacks
1440
+
1441
+ ##### verify_content_type_header
1442
+
1443
+ By default, when controllers extend functionalities from `jsonapi-resources`, the `ActsAsResourceController#verify_content_type_header`
1444
+ method will be triggered before `create`, `update`, `create_relationship` and `update_relationship` actions. This method is responsible
1445
+ for checking if client's request corresponds to the correct media type required by [JSON API](http://jsonapi.org/format/#content-negotiation-clients): `application/vnd.api+json`.
1446
+
1447
+ In case you need to check the media type for custom actions, just make sure to call the method in your controller's `before_action`:
1448
+
1449
+ ```ruby
1450
+ class UsersController < JSONAPI::ResourceController
1451
+ before_action :verify_content_type_header, only: [:auth]
1452
+
1453
+ def auth
1454
+ # some crazy auth code goes here
1455
+ end
1456
+ end
1457
+ ```
1458
+
1459
+ ### Operation Processors
1460
+
1461
+ Operation Processors are called to perform the operation(s) that make up a request. The controller (through the `OperationDispatcher`), creates an `OperatorProcessor` to handle each operation. The processor is created based on the resource name, including the namespace. If a processor does not exist for a resource (namespace matters) the default operation processor is used instead. The default processor can be changed by a configuration setting.
1462
+
1463
+ Defining a custom `Processor` allows for custom callback handling of each operation type for each resource type. For example:
1464
+
1465
+ ```ruby
1466
+ class Api::V4::BookProcessor < JSONAPI::Processor
1467
+ after_find do
1468
+ unless @result.is_a?(JSONAPI::ErrorsOperationResult)
1469
+ @result.meta[:total_records_found] = @result.record_count
1470
+ end
1471
+ end
1472
+ end
1473
+ ```
1474
+
1475
+ This simple example uses a callback to update the result's meta property with the total count of records (a redundant
1476
+ feature only for example purposes), if there wasn't an error in the operation. It is also possible to override the
1477
+ `find` method as well if a different behavior is needed, for example:
1478
+
1479
+ ```ruby
1480
+ class Api::V4::BookProcessor < JSONAPI::Processor
1481
+ def find
1482
+ filters = params[:filters]
1483
+ include_directives = params[:include_directives]
1484
+ sort_criteria = params.fetch(:sort_criteria, [])
1485
+ paginator = params[:paginator]
1486
+
1487
+ verified_filters = resource_klass.verify_filters(filters, context)
1488
+ resource_records = resource_klass.find(verified_filters,
1489
+ context: context,
1490
+ include_directives: include_directives,
1491
+ sort_criteria: sort_criteria,
1492
+ paginator: paginator)
1493
+
1494
+ page_options = {}
1495
+ # Overriding the default record count logic to always include it in the meta
1496
+ #if (JSONAPI.configuration.top_level_meta_include_record_count ||
1497
+ # (paginator && paginator.class.requires_record_count))
1498
+ page_options[:record_count] = resource_klass.find_count(verified_filters,
1499
+ context: context,
1500
+ include_directives: include_directives)
1501
+ #end
1502
+ end
1503
+ ```
1504
+
1505
+ Note: The authors of this gem expect the most common uses cases to be handled using the callbacks. It is likely that the
1506
+ internal functionality of the operation processing methods will change, at least for several revisions. Effort will be
1507
+ made to call this out in release notes. You have been warned.
1508
+
1509
+ ### Serializer
1510
+
1511
+ The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` must be
1512
+ initialized with the primary resource type it will be serializing. `ResourceSerializer` has a `serialize_to_hash`
1513
+ method that takes a resource instance or array of resource instances to serialize. For example:
1514
+
1515
+ ```ruby
1516
+ post = Post.find(1)
1517
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new(post, nil))
1518
+ ```
1519
+
1520
+ Note: If your resource needs to access to state from a context hash, make sure to pass the context hash as the second argument of
1521
+ the resource class new method. For example:
1522
+
1523
+ ```ruby
1524
+ post = Post.find(1)
1525
+ context = { current_user: current_user }
1526
+ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new(post, context))
1527
+ ```
1528
+
1529
+ This returns results like this:
1530
+
1531
+ ```json
1532
+ {
1533
+ "data": {
1534
+ "type": "posts",
1535
+ "id": "1",
1536
+ "links": {
1537
+ "self": "http://example.com/posts/1"
1538
+ },
1539
+ "attributes": {
1540
+ "title": "New post",
1541
+ "body": "A body!!!",
1542
+ "subject": "New post"
1543
+ },
1544
+ "relationships": {
1545
+ "section": {
1546
+ "links": {
1547
+ "self": "http://example.com/posts/1/relationships/section",
1548
+ "related": "http://example.com/posts/1/section"
1549
+ },
1550
+ "data": null
1551
+ },
1552
+ "author": {
1553
+ "links": {
1554
+ "self": "http://example.com/posts/1/relationships/author",
1555
+ "related": "http://example.com/posts/1/author"
1556
+ },
1557
+ "data": {
1558
+ "type": "people",
1559
+ "id": "1"
1560
+ }
1561
+ },
1562
+ "tags": {
1563
+ "links": {
1564
+ "self": "http://example.com/posts/1/relationships/tags",
1565
+ "related": "http://example.com/posts/1/tags"
1566
+ }
1567
+ },
1568
+ "comments": {
1569
+ "links": {
1570
+ "self": "http://example.com/posts/1/relationships/comments",
1571
+ "related": "http://example.com/posts/1/comments"
1572
+ }
1573
+ }
1574
+ }
1575
+ }
1576
+ }
1577
+ ```
1578
+
1579
+ #### Serializer options
1580
+
1581
+ The `ResourceSerializer` can be initialized with some optional parameters:
1582
+
1583
+ ##### `include`
1584
+
1585
+ An array of resources. Nested resources can be specified with dot notation.
1586
+
1587
+ *Purpose*: determines which objects will be side loaded with the source objects in an `included` section
1588
+
1589
+ *Example*: ```include: ['comments','author','comments.tags','author.posts']```
1590
+
1591
+ ##### `fields`
1592
+
1593
+ A hash of resource types and arrays of fields for each resource type.
1594
+
1595
+ *Purpose*: determines which fields are serialized for a resource type. This encompasses both attributes and
1596
+ relationship ids in the links section for a resource. Fields are global for a resource type.
1597
+
1598
+ *Example*: ```fields: { people: [:email, :comments], posts: [:title, :author], comments: [:body, :post]}```
1599
+
1600
+ ```ruby
1601
+ post = Post.find(1)
1602
+ include_resources = ['comments','author','comments.tags','author.posts']
1603
+
1604
+ JSONAPI::ResourceSerializer.new(PostResource, include: include_resources,
1605
+ fields: {
1606
+ people: [:email, :comments],
1607
+ posts: [:title, :author],
1608
+ tags: [:name],
1609
+ comments: [:body, :post]
1610
+ }
1611
+ ).serialize_to_hash(PostResource.new(post, nil))
1612
+ ```
1613
+
1614
+ #### Formatting
1615
+
1616
+ JR by default uses some simple rules to format (and unformat) an attribute for (de-)serialization. Strings and Integers are output to JSON
1617
+ as is, and all other values have `.to_s` applied to them. This outputs something in all cases, but it is certainly not
1618
+ correct for every situation.
1619
+
1620
+ If you want to change the way an attribute is (de-)serialized you have a couple of ways. The simplest method is to create a
1621
+ getter (and setter) method on the resource which overrides the attribute and apply the (un-)formatting there. For example:
1622
+
1623
+ ```ruby
1624
+ class PersonResource < JSONAPI::Resource
1625
+ attributes :name, :email, :last_login_time
1626
+
1627
+ # Setter example
1628
+ def email=(new_email)
1629
+ @model.email = new_email.downcase
1630
+ end
1631
+
1632
+ # Getter example
1633
+ def last_login_time
1634
+ @model.last_login_time.in_time_zone(@context[:current_user].time_zone).to_s
1635
+ end
1636
+ end
1637
+ ```
1638
+
1639
+ This is simple to implement for a one off situation, but not for example if you want to apply the same formatting rules
1640
+ to all DateTime fields in your system. Another issue is the attribute on the resource will always return a formatted
1641
+ response, whether you want it or not.
1642
+
1643
+ ##### Value Formatters
1644
+
1645
+ To overcome the above limitations JR uses Value Formatters. Value Formatters allow you to control the way values are
1646
+ handled for an attribute. The `format` can be set per attribute as it is declared in the resource. For example:
1647
+
1648
+ ```ruby
1649
+ class PersonResource < JSONAPI::Resource
1650
+ attributes :name, :email, :spoken_languages
1651
+ attribute :last_login_time, format: :date_with_utc_timezone
1652
+
1653
+ # Getter/Setter for spoken_languages ...
1654
+ end
1655
+ ```
1656
+
1657
+ A Value formatter has a `format` and an `unformat` method. Here's the base ValueFormatter and DefaultValueFormatter for
1658
+ reference:
1659
+
1660
+ ```ruby
1661
+ module JSONAPI
1662
+ class ValueFormatter < Formatter
1663
+ class << self
1664
+ def format(raw_value)
1665
+ super(raw_value)
1666
+ end
1667
+
1668
+ def unformat(value)
1669
+ super(value)
1670
+ end
1671
+ ...
1672
+ end
1673
+ end
1674
+ end
1675
+
1676
+ class DefaultValueFormatter < JSONAPI::ValueFormatter
1677
+ class << self
1678
+ def format(raw_value)
1679
+ case raw_value
1680
+ when Date, Time, DateTime, ActiveSupport::TimeWithZone, BigDecimal
1681
+ # Use the as_json methods added to various base classes by ActiveSupport
1682
+ return raw_value.as_json
1683
+ else
1684
+ return raw_value
1685
+ end
1686
+ end
1687
+ end
1688
+ end
1689
+ ```
1690
+
1691
+ You can also create your own Value Formatter. Value Formatters must be named with the `format` name followed by
1692
+ `ValueFormatter`, i.e. `DateWithUTCTimezoneValueFormatter` and derive from `JSONAPI::ValueFormatter`. It is
1693
+ recommended that you create a directory for your formatters, called `formatters`.
1694
+
1695
+ The `format` method is called by the `ResourceSerializer` as is serializing a resource. The format method takes the
1696
+ `raw_value` parameter. `raw_value` is the value as read from the model.
1697
+
1698
+ The `unformat` method is called when processing the request. Each incoming attribute (except `links`) are run through
1699
+ the `unformat` method. The `unformat` method takes a `value`, which is the value as it comes in on the
1700
+ request. This allows you process the incoming value to alter its state before it is stored in the model.
1701
+
1702
+ ###### Use a Different Default Value Formatter
1703
+
1704
+ Another way to handle formatting is to set a different default value formatter. This will affect all attributes that do
1705
+ not have a `format` set. You can do this by overriding the `default_attribute_options` method for a resource (or a base
1706
+ resource for a system wide change).
1707
+
1708
+ ```ruby
1709
+ def self.default_attribute_options
1710
+ {format: :my_default}
1711
+ end
1712
+ ```
1713
+
1714
+ and
1715
+
1716
+ ```ruby
1717
+ class MyDefaultValueFormatter < DefaultValueFormatter
1718
+ class << self
1719
+ def format(raw_value)
1720
+ case raw_value
1721
+ when DateTime
1722
+ return super(raw_value.in_time_zone('UTC'))
1723
+ else
1724
+ return super
1725
+ end
1726
+ end
1727
+ end
1728
+ end
1729
+ ```
1730
+
1731
+ This way all DateTime values will be formatted to display in the UTC timezone.
1732
+
1733
+ #### Key Format
1734
+
1735
+ By default JR uses dasherized keys as per the
1736
+ [JSON API naming recommendations](http://jsonapi.org/recommendations/#naming). This can be changed by specifying a
1737
+ different key formatter.
1738
+
1739
+ For example, to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add
1740
+ the following:
1741
+
1742
+ ```ruby
1743
+ JSONAPI.configure do |config|
1744
+ # built in key format options are :underscored_key, :camelized_key and :dasherized_key
1745
+ config.json_key_format = :camelized_key
1746
+ end
1747
+ ```
1748
+
1749
+ This will cause the serializer to use the `CamelizedKeyFormatter`. You can also create your own `KeyFormatter`, for
1750
+ example:
1751
+
1752
+ ```ruby
1753
+ class UpperCamelizedKeyFormatter < JSONAPI::KeyFormatter
1754
+ class << self
1755
+ def format(key)
1756
+ super.camelize(:upper)
1757
+ end
1758
+ end
1759
+ end
1760
+ ```
1761
+
1762
+ You would specify this in `JSONAPI.configure` as `:upper_camelized`.
1763
+
1764
+ ### Routing
1765
+
1766
+ JR has a couple of helper methods available to assist you with setting up routes.
1767
+
1768
+ ##### `jsonapi_resources`
1769
+
1770
+ Like `resources` in `ActionDispatch`, `jsonapi_resources` provides resourceful routes mapping between HTTP verbs and URLs
1771
+ and controller actions. This will also setup mappings for relationship URLs for a resource's relationships. For example:
1772
+
1773
+ ```ruby
1774
+ Rails.application.routes.draw do
1775
+ jsonapi_resources :contacts
1776
+ jsonapi_resources :phone_numbers
1777
+ end
1778
+ ```
1779
+
1780
+ gives the following routes
1781
+
1782
+ ```
1783
+ Prefix Verb URI Pattern Controller#Action
1784
+ contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_relationship {:relationship=>"phone_numbers"}
1785
+ POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_relationship {:relationship=>"phone_numbers"}
1786
+ DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_relationship {:relationship=>"phone_numbers"}
1787
+ contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:relationship=>"phone_numbers", :source=>"contacts"}
1788
+ contacts GET /contacts(.:format) contacts#index
1789
+ POST /contacts(.:format) contacts#create
1790
+ contact GET /contacts/:id(.:format) contacts#show
1791
+ PATCH /contacts/:id(.:format) contacts#update
1792
+ PUT /contacts/:id(.:format) contacts#update
1793
+ DELETE /contacts/:id(.:format) contacts#destroy
1794
+ phone_number_relationships_contact GET /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#show_relationship {:relationship=>"contact"}
1795
+ PUT|PATCH /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#update_relationship {:relationship=>"contact"}
1796
+ DELETE /phone-numbers/:phone_number_id/relationships/contact(.:format) phone_numbers#destroy_relationship {:relationship=>"contact"}
1797
+ phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:relationship=>"contact", :source=>"phone_numbers"}
1798
+ phone_numbers GET /phone-numbers(.:format) phone_numbers#index
1799
+ POST /phone-numbers(.:format) phone_numbers#create
1800
+ phone_number GET /phone-numbers/:id(.:format) phone_numbers#show
1801
+ PATCH /phone-numbers/:id(.:format) phone_numbers#update
1802
+ PUT /phone-numbers/:id(.:format) phone_numbers#update
1803
+ DELETE /phone-numbers/:id(.:format) phone_numbers#destroy
1804
+ ```
1805
+
1806
+ ##### `jsonapi_resource`
1807
+
1808
+ Like `jsonapi_resources`, but for resources you lookup without an id.
1809
+
1810
+ #### Nested Routes
1811
+
1812
+ By default nested routes are created for getting related resources and manipulating relationships. You can control the
1813
+ nested routes by passing a block into `jsonapi_resources` or `jsonapi_resource`. An empty block will not create
1814
+ any nested routes. For example:
1815
+
1816
+ ```ruby
1817
+ Rails.application.routes.draw do
1818
+ jsonapi_resources :contacts do
1819
+ end
1820
+ end
1821
+ ```
1822
+
1823
+ gives routes that are only related to the primary resource, and none for its relationships:
1824
+
1825
+ ```
1826
+ Prefix Verb URI Pattern Controller#Action
1827
+ contacts GET /contacts(.:format) contacts#index
1828
+ POST /contacts(.:format) contacts#create
1829
+ contact GET /contacts/:id(.:format) contacts#show
1830
+ PATCH /contacts/:id(.:format) contacts#update
1831
+ PUT /contacts/:id(.:format) contacts#update
1832
+ DELETE /contacts/:id(.:format) contacts#destroy
1833
+ ```
1834
+
1835
+ To manually add in the nested routes you can use the `jsonapi_links`, `jsonapi_related_resources` and
1836
+ `jsonapi_related_resource` inside the block. Or, you can add the default set of nested routes using the
1837
+ `jsonapi_relationships` method. For example:
1838
+
1839
+ ```ruby
1840
+ Rails.application.routes.draw do
1841
+ jsonapi_resources :contacts do
1842
+ jsonapi_relationships
1843
+ end
1844
+ end
1845
+ ```
1846
+
1847
+ ###### `jsonapi_links`
1848
+
1849
+ You can add relationship routes in with `jsonapi_links`, for example:
1850
+
1851
+ ```ruby
1852
+ Rails.application.routes.draw do
1853
+ jsonapi_resources :contacts do
1854
+ jsonapi_links :phone_numbers
1855
+ end
1856
+ end
1857
+ ```
1858
+
1859
+ Gives the following routes:
1860
+
1861
+ ```
1862
+ contact_relationships_phone_numbers GET /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#show_relationship {:relationship=>"phone_numbers"}
1863
+ POST /contacts/:contact_id/relationships/phone-numbers(.:format) contacts#create_relationship {:relationship=>"phone_numbers"}
1864
+ DELETE /contacts/:contact_id/relationships/phone-numbers/:keys(.:format) contacts#destroy_relationship {:relationship=>"phone_numbers"}
1865
+ contacts GET /contacts(.:format) contacts#index
1866
+ POST /contacts(.:format) contacts#create
1867
+ contact GET /contacts/:id(.:format) contacts#show
1868
+ PATCH /contacts/:id(.:format) contacts#update
1869
+ PUT /contacts/:id(.:format) contacts#update
1870
+ DELETE /contacts/:id(.:format) contacts#destroy
1871
+
1872
+ ```
1873
+
1874
+ The new routes allow you to show, create and destroy the relationships between resources.
1875
+
1876
+ ###### `jsonapi_related_resources`
1877
+
1878
+ Creates a nested route to GET the related has_many resources. For example:
1879
+
1880
+ ```ruby
1881
+ Rails.application.routes.draw do
1882
+ jsonapi_resources :contacts do
1883
+ jsonapi_related_resources :phone_numbers
1884
+ end
1885
+ end
1886
+
1887
+ ```
1888
+
1889
+ gives the following routes:
1890
+
1891
+ ```
1892
+ Prefix Verb URI Pattern Controller#Action
1893
+ contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:relationship=>"phone_numbers", :source=>"contacts"}
1894
+ contacts GET /contacts(.:format) contacts#index
1895
+ POST /contacts(.:format) contacts#create
1896
+ contact GET /contacts/:id(.:format) contacts#show
1897
+ PATCH /contacts/:id(.:format) contacts#update
1898
+ PUT /contacts/:id(.:format) contacts#update
1899
+ DELETE /contacts/:id(.:format) contacts#destroy
1900
+
1901
+ ```
1902
+
1903
+ A single additional route was created to allow you GET the phone numbers through the contact.
1904
+
1905
+ ###### `jsonapi_related_resource`
1906
+
1907
+ Like `jsonapi_related_resources`, but for has_one related resources.
1908
+
1909
+ ```ruby
1910
+ Rails.application.routes.draw do
1911
+ jsonapi_resources :phone_numbers do
1912
+ jsonapi_related_resource :contact
1913
+ end
1914
+ end
1915
+ ```
1916
+
1917
+ gives the following routes:
1918
+
1919
+ ```
1920
+ Prefix Verb URI Pattern Controller#Action
1921
+ phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:relationship=>"contact", :source=>"phone_numbers"}
1922
+ phone_numbers GET /phone-numbers(.:format) phone_numbers#index
1923
+ POST /phone-numbers(.:format) phone_numbers#create
1924
+ phone_number GET /phone-numbers/:id(.:format) phone_numbers#show
1925
+ PATCH /phone-numbers/:id(.:format) phone_numbers#update
1926
+ PUT /phone-numbers/:id(.:format) phone_numbers#update
1927
+ DELETE /phone-numbers/:id(.:format) phone_numbers#destroy
1928
+
1929
+ ```
1930
+
1931
+ ### Authorization
1932
+
1933
+ Currently `json-api-resources` doesn't come with built-in primitives for authorization. However multiple users of the framework have come up with different approaches, check out:
1934
+
1935
+ - [jsonapi-authorization](https://github.com/venuu/jsonapi-authorization)
1936
+ - [pundit-resources](https://github.com/togglepro/pundit-resources)
1937
+
1938
+ Refer to the comments/discussion [here](https://github.com/cerebris/jsonapi-resources/issues/16#issuecomment-222438975) for the differences between approaches
1939
+
1940
+ ### Resource Caching
1941
+
1942
+ To improve the response time of GET requests, JR can cache the generated JSON fragments for
1943
+ Resources which are suitable. First, set `config.resource_cache` to an ActiveSupport cache store:
1944
+
1945
+ ```ruby
1946
+ JSONAPI.configure do |config|
1947
+ config.resource_cache = Rails.cache
1948
+ end
1949
+ ```
1950
+
1951
+ Then, on each Resource you want to cache, call the `caching` method:
1952
+
1953
+ ```ruby
1954
+ class PostResource < JSONAPI::Resource
1955
+ caching
1956
+ end
1957
+ ```
1958
+
1959
+ See the caveats section below for situations where you might not want to enable caching on particular
1960
+ Resources.
1961
+
1962
+ The Resource model must also have a field that is updated whenever any of the model's data changes.
1963
+ The default Rails timestamps handle this pretty well, and the default cache key field is `updated_at` for this reason.
1964
+ You can use an alternate field (which you are then responsible for updating) by calling the `cache_field` method:
1965
+
1966
+ ```ruby
1967
+ class PostResource < JSONAPI::Resource
1968
+ caching
1969
+ cache_field :change_counter
1970
+
1971
+ before_save do
1972
+ if self.change_counter.nil?
1973
+ self.change_counter = 1
1974
+ elsif self.changed?
1975
+ self.change_counter += 1
1976
+ end
1977
+ end
1978
+
1979
+ after_touch do
1980
+ update_attribute(:change_counter, self.change_counter + 1)
1981
+ end
1982
+ end
1983
+ ```
1984
+
1985
+ If context affects the content of the serialized result, you must define a class method `attribute_caching_context` on that Resource, which should return a different value for contexts that produce different results. In particular, if the `meta` or `fetchable_fields` methods, or any method providing the actual content of an attribute, changes depending on context, then you must provide `attribute_caching_context`. The actual value it
1986
+ returns isn't important, what matters is that the value must be different if any relevant part of the context is different.
1987
+
1988
+ ```ruby
1989
+ class PostResource < JSONAPI::Resource
1990
+ caching
1991
+
1992
+ attributes :title, :body, :secret_field
1993
+
1994
+ def fetchable_fields
1995
+ return super if context.user.superuser?
1996
+ return super - [:secret_field]
1997
+ end
1998
+
1999
+ def meta
2000
+ if context.user.can_see_creation_dates?
2001
+ return { created: _model.created_at }
2002
+ else
2003
+ return {}
2004
+ end
2005
+ end
2006
+
2007
+ def self.attribute_caching_context(context)
2008
+ return {
2009
+ admin: context.user.superuser?,
2010
+ creation_date_viewer: context.user.can_see_creation_dates?
2011
+ }
2012
+ end
2013
+ end
2014
+ ```
2015
+
2016
+ #### Caching Caveats
2017
+
2018
+ * Models for cached Resources must update a cache key field whenever their data changes. However, if you bypass Rails and e.g. alter the database row directly without changing the `updated_at` field, the cached entry for that resource will be inaccurate. Also, `updated_at` provides a narrow race condition window; if a resource is updated twice in the same second, it's possible that only the first update will be cached. If you're concerned about this, you will need to find a way to make sure your models' cache fields change on every update, e.g. by using a unique random value or a monotonic clock.
2019
+ * If an attribute's value is affected by related resources, e.g. the `spoken_languages` example above, then changes to the related resource must also touch the cache field on the resource that uses it. The `belongs_to` relation in ActiveRecord provides a `:touch` option for this purpose.
2020
+ * JR does not actively clean the cache, so you must use an ActiveSupport cache that automatically expires old entries, or you will leak resources. The MemoryCache built in to Rails does this by default, but other caches will have to be configured with an `:expires_in` option and/or a cache-specific clearing mechanism.
2021
+ * Similarly, if you make a substantial code change that affects a lot of serialized representations (i.e. changing the way an attribute is shown), you'll have to clear out all relevant cache entries yourself. The simplest way to do this is to run `JSONAPI.configuration.resource_cache.clear` from the console. You do not have to do this after merely adding or removing attributes; only changes that affect the actual content of attributes require manual cache clearing.
2022
+ * If resource caching is enabled at all, then custom relationship methods on any resource might not always be used, even resources that are not cached. For example, if you manually define a `comments` method or `records_for_comments` method on a Resource that `has_many :comments`, you cannot expect it to be used when caching is enabled, even if you never call `caching` on that particular Resource. Instead, you should use relationship name lambdas.
2023
+ * The above also applies to custom `find` or `find_by_key` methods. Instead, if you are using resource caching anywhere in your app, try overriding the `find_records` method to return an appropriate `ActiveRecord::Relation`.
2024
+ * Caching relies on ActiveRecord features; you cannot enable caching on resources based on non-AR models, e.g. PORO objects or singleton resources.
2025
+ * If you write a custom `ResourceSerializer` which takes new options, then you must define `config_description` to include those options if they might impact the serialized value:
2026
+
2027
+ ```ruby
2028
+ class MySerializer < JSONAPI::ResourceSerializer
2029
+ def initialize(primary_resource_klass, options = {})
2030
+ @my_special_option = options.delete(:my_special_option)
2031
+ super
2032
+ end
2033
+
2034
+ def config_description(resource_klass)
2035
+ super.merge({my_special_option: @my_special_option})
2036
+ end
2037
+ end
2038
+ ```
2039
+
2040
+ ## Configuration
2041
+
2042
+ JR has a few configuration options. Some have already been mentioned above. To set configuration options create an
2043
+ initializer and add the options you wish to set. All options have defaults, so you only need to set the options that
2044
+ are different. The default options are shown below.
2045
+
2046
+ If using custom classes (such as a CustomPaginator), be sure to require them at the top of the initializer before usage.
2047
+
2048
+ ```ruby
2049
+ JSONAPI.configure do |config|
2050
+ #:underscored_key, :camelized_key, :dasherized_key, or custom
2051
+ config.json_key_format = :dasherized_key
2052
+
2053
+ #:underscored_route, :camelized_route, :dasherized_route, or custom
2054
+ config.route_format = :dasherized_route
2055
+
2056
+ # Default Processor, used if a resource specific one is not defined.
2057
+ # Must be a class
2058
+ config.default_processor_klass = JSONAPI::Processor
2059
+
2060
+ #:integer, :uuid, :string, or custom (provide a proc)
2061
+ config.resource_key_type = :integer
2062
+
2063
+ # optional request features
2064
+ config.allow_include = true
2065
+ config.allow_sort = true
2066
+ config.allow_filter = true
2067
+
2068
+ # How to handle unsupported attributes and relationships which are provided in the request
2069
+ # true => raises an error
2070
+ # false => allows the request to continue. A warning is included in the response meta data indicating
2071
+ # the fields which were ignored. This is useful for client libraries which send extra parameters.
2072
+ config.raise_if_parameters_not_allowed = true
2073
+
2074
+ # :none, :offset, :paged, or a custom paginator name
2075
+ config.default_paginator = :none
2076
+
2077
+ # Output pagination links at top level
2078
+ config.top_level_links_include_pagination = true
2079
+
2080
+ config.default_page_size = 10
2081
+ config.maximum_page_size = 20
2082
+
2083
+ # Output the record count in top level meta data for find operations
2084
+ config.top_level_meta_include_record_count = false
2085
+ config.top_level_meta_record_count_key = :record_count
2086
+
2087
+ # For :paged paginators, the following are also available
2088
+ config.top_level_meta_include_page_count = false
2089
+ config.top_level_meta_page_count_key = :page_count
2090
+
2091
+ config.use_text_errors = false
2092
+
2093
+ # List of classes that should not be rescued by the operations processor.
2094
+ # For example, if you use Pundit for authorization, you might
2095
+ # raise a Pundit::NotAuthorizedError at some point during operations
2096
+ # processing. If you want to use Rails' `rescue_from` macro to
2097
+ # catch this error and render a 403 status code, you should add
2098
+ # the `Pundit::NotAuthorizedError` to the `exception_class_whitelist`.
2099
+ # Subclasses of the whitelisted classes will also be whitelisted.
2100
+ config.exception_class_whitelist = []
2101
+
2102
+ # If enabled, will override configuration option `exception_class_whitelist`
2103
+ # and whitelist all exceptions.
2104
+ config.whitelist_all_exceptions = false
2105
+
2106
+ # Resource Linkage
2107
+ # Controls the serialization of resource linkage for non compound documents
2108
+ # NOTE: always_include_to_many_linkage_data is not currently implemented
2109
+ config.always_include_to_one_linkage_data = false
2110
+
2111
+ # Relationship reflection invokes the related resource when updates
2112
+ # are made to a has_many relationship. By default relationship_reflection
2113
+ # is turned off because it imposes a small performance penalty.
2114
+ config.use_relationship_reflection = false
2115
+
2116
+ # Allows transactions for creating and updating records
2117
+ # Set this to false if your backend does not support transactions (e.g. Mongodb)
2118
+ config.allow_transactions = true
2119
+
2120
+ # Formatter Caching
2121
+ # Set to false to disable caching of string operations on keys and links.
2122
+ # Note that unlike the resource cache, formatter caching is always done
2123
+ # internally in-memory and per-thread; no ActiveSupport::Cache is used.
2124
+ config.cache_formatters = true
2125
+
2126
+ # Resource cache
2127
+ # An ActiveSupport::Cache::Store or similar, used by Resources with caching enabled.
2128
+ # Set to `nil` (the default) to disable caching, or to `Rails.cache` to use the
2129
+ # Rails cache store.
2130
+ config.resource_cache = nil
2131
+
2132
+ # Default resource cache field
2133
+ # On Resources with caching enabled, this field will be used to check for out-of-date
2134
+ # cache entries, unless overridden on a specific Resource. Defaults to "updated_at".
2135
+ config.default_resource_cache_field = :updated_at
2136
+
2137
+ # Resource cache digest function
2138
+ # Provide a callable that returns a unique value for string inputs with
2139
+ # low chance of collision. The default is SHA256 base64.
2140
+ config.resource_cache_digest_function = Digest::SHA2.new.method(:base64digest)
2141
+
2142
+ # Resource cache usage reporting
2143
+ # Optionally provide a callable which JSONAPI will call with information about cache
2144
+ # performance. Should accept three arguments: resource name, hits count, misses count.
2145
+ config.resource_cache_usage_report_function = nil
2146
+ end
2147
+ ```
42
2148
 
43
2149
  ## Contributing
44
2150
 
@@ -48,6 +2154,16 @@ Or install it yourself as:
48
2154
  4. Push to the branch (`git push origin my-new-feature`)
49
2155
  5. Create a new Pull Request
50
2156
 
2157
+ ### Running Tests
2158
+
2159
+ To run the tests for this project:
2160
+
2161
+ - `rake test` or `bundle exec rake test`
2162
+
2163
+ To run a single test:
2164
+
2165
+ - `bundle exec ruby -I test test/controllers/controller_test.rb -n test_type_formatting`
2166
+
51
2167
  ## License
52
2168
 
53
2169
  Copyright 2014-2016 Cerebris Corporation. MIT License (see LICENSE for details).