jsonapi-resources 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +274 -102
  3. data/jsonapi-resources.gemspec +1 -0
  4. data/lib/jsonapi-resources.rb +15 -0
  5. data/lib/jsonapi/active_record_operations_processor.rb +21 -10
  6. data/lib/jsonapi/acts_as_resource_controller.rb +175 -0
  7. data/lib/jsonapi/configuration.rb +11 -0
  8. data/lib/jsonapi/error_codes.rb +7 -4
  9. data/lib/jsonapi/exceptions.rb +23 -15
  10. data/lib/jsonapi/formatter.rb +5 -5
  11. data/lib/jsonapi/include_directives.rb +67 -0
  12. data/lib/jsonapi/operation.rb +185 -65
  13. data/lib/jsonapi/operation_result.rb +38 -5
  14. data/lib/jsonapi/operation_results.rb +33 -0
  15. data/lib/jsonapi/operations_processor.rb +49 -9
  16. data/lib/jsonapi/paginator.rb +31 -17
  17. data/lib/jsonapi/request.rb +347 -163
  18. data/lib/jsonapi/resource.rb +159 -56
  19. data/lib/jsonapi/resource_controller.rb +1 -234
  20. data/lib/jsonapi/resource_serializer.rb +55 -69
  21. data/lib/jsonapi/resources/version.rb +1 -1
  22. data/lib/jsonapi/response_document.rb +87 -0
  23. data/lib/jsonapi/routing_ext.rb +17 -11
  24. data/test/controllers/controller_test.rb +602 -326
  25. data/test/fixtures/active_record.rb +96 -6
  26. data/test/fixtures/line_items.yml +7 -1
  27. data/test/fixtures/numeros_telefone.yml +3 -0
  28. data/test/fixtures/purchase_orders.yml +6 -0
  29. data/test/integration/requests/request_test.rb +129 -60
  30. data/test/integration/routes/routes_test.rb +17 -17
  31. data/test/test_helper.rb +23 -5
  32. data/test/unit/jsonapi_request/jsonapi_request_test.rb +48 -0
  33. data/test/unit/operation/operations_processor_test.rb +242 -54
  34. data/test/unit/resource/resource_test.rb +108 -2
  35. data/test/unit/serializer/include_directives_test.rb +108 -0
  36. data/test/unit/serializer/response_document_test.rb +61 -0
  37. data/test/unit/serializer/serializer_test.rb +679 -520
  38. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7354d2936f1ae7ba14a27b816ba9e9a0b34b0b13
4
- data.tar.gz: 5dc38e6256771df238ddefd326eae64c72955113
3
+ metadata.gz: 57590c2f9544bd54d43d1a024ae878bae321f87f
4
+ data.tar.gz: 102b9414a9ebddd19b249daa2684f9460083c670
5
5
  SHA512:
6
- metadata.gz: 8ffb7b64c39241f8579ca839c46926c2f90a83335bcff101b46b8eb708f4478c8c9b80ba493aa1991a46a0f995b7d395367372d71b6c01ab97edb7266c1674dc
7
- data.tar.gz: 73b910fe3ef9a75f4b10b2a4bd576e68c42cdcb33224d3f72dc38743cd7808c5b8d54722ca9502787f66183e5a9f28d6131e4afebdda927f3427edabe2fd8349
6
+ metadata.gz: 7b139efb93f753f5a20e314e02e4ae3cc33bed69d7c9303ebf4bf838c9013ea5a8ef625f4720f70114b3ff6b219788e4b7303e7f55723829980fb1ac729d8d9a
7
+ data.tar.gz: d7580a2b8e21f6813b2a6e2c7d59f8b8f6230e2834c1e9a16c10215d40ebe3e479e451f8d4fad3a811d301e0abed030a66b27d2c3ce9b37553278d6c44bc5a77
data/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # JSONAPI::Resources [![Build Status](https://secure.travis-ci.org/cerebris/jsonapi-resources.png?branch=master)](http://travis-ci.org/cerebris/jsonapi-resources)
2
2
 
3
- `JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the [JSON API](http://jsonapi.org/) specification.
3
+ `JSONAPI::Resources`, or "JR", provides a framework for developing a server that complies with the
4
+ [JSON API](http://jsonapi.org/) specification.
4
5
 
5
- Like JSON API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition of your resources, including their attributes and relationships, to make your server compliant with JSON API.
6
+ Like JSON API itself, JR's design is focused on the resources served by an API. JR needs little more than a definition
7
+ of your resources, including their attributes and relationships, to make your server compliant with JSON API.
6
8
 
7
- JR is designed to work with Rails 4.0+, and provides custom routes, controllers, and serializers. JR's resources may be backed by ActiveRecord models or by custom objects.
9
+ JR is designed to work with Rails 4.0+, and provides custom routes, controllers, and serializers. JR's resources may be
10
+ backed by ActiveRecord models or by custom objects.
8
11
 
9
12
  ## Demo App
10
13
 
@@ -12,7 +15,8 @@ We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), av
12
15
 
13
16
  ## Client Libraries
14
17
 
15
- JSON API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries) which *should* be compatible with JSON API compliant server implementations such as JR.
18
+ JSON API maintains a (non-verified) listing of [client libraries](http://jsonapi.org/implementations/#client-libraries)
19
+ which *should* be compatible with JSON API compliant server implementations such as JR.
16
20
 
17
21
  ## Installation
18
22
 
@@ -32,9 +36,12 @@ Or install it yourself as:
32
36
 
33
37
  ### Resources
34
38
 
35
- Resources define the public interface to your API. A resource defines which attributes are exposed, as well as relationships to other resources.
39
+ Resources define the public interface to your API. A resource defines which attributes are exposed, as well as
40
+ relationships to other resources.
36
41
 
37
- Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The class name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example, a `Contact` model's resource should have a class named `ContactResource` defined in a file named `contact_resource.rb`.
42
+ Resource definitions should by convention be placed in a directory under app named resources, `app/resources`. The class
43
+ name should be the single underscored name of the model that backs the resource with `_resource.rb` appended. For example,
44
+ a `Contact` model's resource should have a class named `ContactResource` defined in a file named `contact_resource.rb`.
38
45
 
39
46
  #### JSONAPI::Resource
40
47
 
@@ -51,7 +58,8 @@ end
51
58
 
52
59
  #### Attributes
53
60
 
54
- Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using the `attribute` method, and multiple attributes can be declared with the `attributes` method on the resource class.
61
+ Any of a resource's attributes that are accessible must be explicitly declared. Single attributes can be declared using
62
+ the `attribute` method, and multiple attributes can be declared with the `attributes` method on the resource class.
55
63
 
56
64
  For example:
57
65
 
@@ -59,15 +67,16 @@ For example:
59
67
  require 'jsonapi/resource'
60
68
 
61
69
  class ContactResource < JSONAPI::Resource
62
- attribute :id
63
70
  attribute :name_first
64
71
  attributes :name_last, :email, :twitter
65
72
  end
66
73
  ```
67
74
 
68
- This resource has 5 attributes: `:id`, `:name_first`, `:name_last`, `:email`, `:twitter`. By default these attributes must exist on the model that is handled by the resource.
75
+ This resource has 4 defined attributes: `name_first`, `name_last`, `email`, `twitter`, as well as the automatically
76
+ defined attributes `id` and `type`. By default these attributes must exist on the model that is handled by the resource.
69
77
 
70
- A resource object wraps a Ruby object, usually an `ActiveModel` record, which is available as the `@model` variable. This allows a resource's methods to access the underlying model.
78
+ A resource object wraps a Ruby object, usually an `ActiveModel` record, which is available as the `@model` variable.
79
+ This allows a resource's methods to access the underlying model.
71
80
 
72
81
  For example, a computed attribute for `full_name` could be defined as such:
73
82
 
@@ -75,7 +84,7 @@ For example, a computed attribute for `full_name` could be defined as such:
75
84
  require 'jsonapi/resource'
76
85
 
77
86
  class ContactResource < JSONAPI::Resource
78
- attributes :id, :name_first, :name_last, :email, :twitter
87
+ attributes :name_first, :name_last, :email, :twitter
79
88
  attribute :full_name
80
89
 
81
90
  def full_name
@@ -86,13 +95,14 @@ end
86
95
 
87
96
  ##### Fetchable Attributes
88
97
 
89
- By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding the `fetchable_fields` method.
98
+ By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding
99
+ the `fetchable_fields` method.
90
100
 
91
101
  Here's an example that prevents guest users from seeing the `email` field:
92
102
 
93
103
  ```ruby
94
104
  class AuthorResource < JSONAPI::Resource
95
- attributes :id, :name, :email
105
+ attributes :name, :email
96
106
  model_name 'Person'
97
107
  has_many :posts
98
108
 
@@ -106,11 +116,13 @@ class AuthorResource < JSONAPI::Resource
106
116
  end
107
117
  ```
108
118
 
109
- Context flows through from the controller and can be used to control the attributes based on the current user (or other value).
119
+ Context flows through from the controller and can be used to control the attributes based on the current user (or other
120
+ value).
110
121
 
111
- ##### Creatable and Updateable Attributes
122
+ ##### Creatable and Updatable Attributes
112
123
 
113
- By default all attributes are assumed to be updateable and creatable. To prevent some attributes from being accepted by the `update` or `create` methods, override the `self.updateable_fields` and `self.createable_fields` methods on a resource.
124
+ By default all attributes are assumed to be updatable and creatable. To prevent some attributes from being accepted by
125
+ the `update` or `create` methods, override the `self.updatable_fields` and `self.creatable_fields` methods on a resource.
114
126
 
115
127
  This example prevents `full_name` from being set:
116
128
 
@@ -118,35 +130,37 @@ This example prevents `full_name` from being set:
118
130
  require 'jsonapi/resource'
119
131
 
120
132
  class ContactResource < JSONAPI::Resource
121
- attributes :id, :name_first, :name_last, :full_name
133
+ attributes :name_first, :name_last, :full_name
122
134
 
123
135
  def full_name
124
136
  "#{@model.name_first}, #{@model.name_last}"
125
137
  end
126
138
 
127
- def self.updateable_fields(context)
139
+ def self.updatable_fields(context)
128
140
  super - [:full_name]
129
141
  end
130
142
 
131
- def self.createable_fields(context)
143
+ def self.creatable_fields(context)
132
144
  super - [:full_name]
133
145
  end
134
146
  end
135
147
  ```
136
148
 
137
- The `context` is not by default used by the `ResourceController`, but may be used if you override the controller methods. By using the context you have the option to determine the createable and updateable fields based on the user.
149
+ The `context` is not by default used by the `ResourceController`, but may be used if you override the controller methods.
150
+ By using the context you have the option to determine the creatable and updatable fields based on the user.
138
151
 
139
152
  ##### Sortable Attributes
140
153
 
141
154
  JR supports [sorting primary resources by multiple sort criteria](http://jsonapi.org/format/#fetching-sorting).
142
155
 
143
- By default all attributes are assumed to be sortable. To prevent some attributes from being sortable, override the `self.sortable_fields` method on a resource.
156
+ By default all attributes are assumed to be sortable. To prevent some attributes from being sortable, override the
157
+ `self.sortable_fields` method on a resource.
144
158
 
145
159
  Here's an example that prevents sorting by post's `body`:
146
160
 
147
161
  ```ruby
148
162
  class PostResource < JSONAPI::Resource
149
- attribute :id, :title, :body
163
+ attributes :title, :body
150
164
 
151
165
  def self.sortable_fields(context)
152
166
  super(context) - [:body]
@@ -156,21 +170,29 @@ end
156
170
 
157
171
  ##### Attribute Formatting
158
172
 
159
- Attributes can have a `Format`. By default all attributes use the default formatter. If an attribute has the `format` option set the system will attempt to find a formatter based on this name. In the following example the `last_login_time` will be returned formatted to a certain time zone:
173
+ Attributes can have a `Format`. By default all attributes use the default formatter. If an attribute has the `format`
174
+ option set the system will attempt to find a formatter based on this name. In the following example the `last_login_time`
175
+ will be returned formatted to a certain time zone:
160
176
 
161
177
  ```ruby
162
178
  class PersonResource < JSONAPI::Resource
163
- attributes :id, :name, :email
179
+ attributes :name, :email
164
180
  attribute :last_login_time, format: :date_with_timezone
165
181
  end
166
182
  ```
167
183
 
168
- The system will lookup a value formatter named `DateWithTimezoneValueFormatter` and will use this when serializing and updating the attribute. See the [Value Formatters](#value-formatters) section for more details.
184
+ The system will lookup a value formatter named `DateWithTimezoneValueFormatter` and will use this when serializing and
185
+ updating the attribute. See the [Value Formatters](#value-formatters) section for more details.
169
186
 
170
187
  #### Primary Key
171
188
 
172
- Resources are always represented using a key of `id`. If the underlying model does not use `id` as the primary key you can use the `primary_key` method to tell the resource which field on the model to use as the primary key. Note: this doesn't have to be the actual primary key of the model. For example you may wish to use integers internally and a different scheme publicly.
173
- By default only integer values are allowed for primary key. To change this behavior you can override `verify_key` class method:
189
+ Resources are always represented using a key of `id`. If the underlying model does not use `id` as the primary key you
190
+ can use the `primary_key` method to tell the resource which field on the model to use as the primary key. Note: this
191
+ doesn't have to be the actual primary key of the model. For example you may wish to use integers internally and a
192
+ different scheme publicly.
193
+
194
+ By default only integer values are allowed for primary key. To change this behavior you can override
195
+ `verify_key` class method:
174
196
 
175
197
  ```ruby
176
198
  class CurrencyResource < JSONAPI::Resource
@@ -187,11 +209,12 @@ end
187
209
 
188
210
  #### Model Name
189
211
 
190
- The name of the underlying model is inferred from the Resource name. It can be overridden by use of the `model_name` method. For example:
212
+ The name of the underlying model is inferred from the Resource name. It can be overridden by use of the `model_name`
213
+ method. For example:
191
214
 
192
215
  ```ruby
193
216
  class AuthorResource < JSONAPI::Resource
194
- attributes :id, :name
217
+ attribute :name
195
218
  model_name 'Person'
196
219
  has_many :posts
197
220
  end
@@ -205,7 +228,7 @@ Here's a simple example where a post has a single author and an author can have
205
228
 
206
229
  ```ruby
207
230
  class PostResource < JSONAPI::Resource
208
- attribute :id, :title, :body
231
+ attribute :title, :body
209
232
 
210
233
  has_one :author
211
234
  end
@@ -215,7 +238,7 @@ And the corresponding author:
215
238
 
216
239
  ```ruby
217
240
  class AuthorResource < JSONAPI::Resource
218
- attribute :id, :name
241
+ attribute :name
219
242
 
220
243
  has_many :posts
221
244
  end
@@ -225,14 +248,15 @@ end
225
248
 
226
249
  The association methods support the following options:
227
250
  * `class_name` - a string specifying the underlying class for the related resource
228
- * `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.
251
+ * `foreign_key` - the method on the resource used to fetch the related resource. Defaults to `<resource_name>_id` for
252
+ has_one and `<resource_name>_ids` for has_many relationships.
229
253
  * `acts_as_set` - allows the entire set of related records to be replaced in one operation. Defaults to false if not set.
230
254
 
231
255
  Examples:
232
256
 
233
257
  ```ruby
234
258
  class CommentResource < JSONAPI::Resource
235
- attributes :id, :body
259
+ attributes :body
236
260
  has_one :post
237
261
  has_one :author, class_name: 'Person'
238
262
  has_many :tags, acts_as_set: true
@@ -241,7 +265,7 @@ Examples:
241
265
 
242
266
  ```ruby
243
267
  class ExpenseEntryResource < JSONAPI::Resource
244
- attributes :id, :cost, :transaction_date
268
+ attributes :cost, :transaction_date
245
269
 
246
270
  has_one :currency, class_name: 'Currency', foreign_key: 'currency_code'
247
271
  has_one :employee
@@ -250,8 +274,8 @@ end
250
274
 
251
275
  #### Filters
252
276
 
253
- Filters for locating objects of the resource type are specified in the resource definition. Single filters can be declared using the `filter` method, and multiple filters can be declared with the `filters` method on the
254
- resource class.
277
+ Filters for locating objects of the resource type are specified in the resource definition. Single filters can be
278
+ declared using the `filter` method, and multiple filters can be declared with the `filters` method on the resource class.
255
279
 
256
280
  For example:
257
281
 
@@ -259,7 +283,7 @@ For example:
259
283
  require 'jsonapi/resource'
260
284
 
261
285
  class ContactResource < JSONAPI::Resource
262
- attributes :id, :name_first, :name_last, :email, :twitter
286
+ attributes :name_first, :name_last, :email, :twitter
263
287
 
264
288
  filter :id
265
289
  filters :name_first, :name_last
@@ -268,17 +292,20 @@ end
268
292
 
269
293
  ##### Finders
270
294
 
271
- Basic finding by filters is supported by resources. This is implemented in the `find` and `find_by_key` finder methods. Currently this is implemented for `ActiveRecord` based resources. The finder methods rely on the `records` method to get an `Arel` relation. It is therefore possible to override `records` to affect the three find related methods.
295
+ Basic finding by filters is supported by resources. This is implemented in the `find` and `find_by_key` finder methods.
296
+ Currently this is implemented for `ActiveRecord` based resources. The finder methods rely on the `records` method to get
297
+ an `Arel` relation. It is therefore possible to override `records` to affect the three find related methods.
272
298
 
273
299
  ###### Customizing base records for finder methods
274
300
 
275
- If you need to change the base records on which `find` and `find_by_key` operate, you can override the `records` method on the resource class.
301
+ If you need to change the base records on which `find` and `find_by_key` operate, you can override the `records` method
302
+ on the resource class.
276
303
 
277
304
  For example to allow a user to only retrieve his own posts you can do the following:
278
305
 
279
306
  ```ruby
280
307
  class PostResource < JSONAPI::Resource
281
- attribute :id, :title, :body
308
+ attribute :title, :body
282
309
 
283
310
  def self.records(options = {})
284
311
  context = options[:context]
@@ -287,7 +314,8 @@ class PostResource < JSONAPI::Resource
287
314
  end
288
315
  ```
289
316
 
290
- When you create a relationship, a method is created to fetch record(s) for that relationship. This method calls `records_for(association_name)` by default.
317
+ When you create a relationship, a method is created to fetch record(s) for that relationship. This method calls
318
+ `records_for(association_name)` by default.
291
319
 
292
320
  ```ruby
293
321
  class PostResource < JSONAPI::Resource
@@ -324,12 +352,13 @@ end
324
352
 
325
353
  ###### Applying Filters
326
354
 
327
- The `apply_filter` method is called to apply each filter to the `Arel` relation. You may override this method to gain control over how the filters are applied to the `Arel` relation.
355
+ The `apply_filter` method is called to apply each filter to the `Arel` relation. You may override this method to gain
356
+ control over how the filters are applied to the `Arel` relation.
328
357
 
329
358
  This example shows how you can implement different approaches for different filters.
330
359
 
331
360
  ```ruby
332
- def self.apply_filter(records, filter, value)
361
+ def self.apply_filter(records, filter, value, options)
333
362
  case filter
334
363
  when :visibility
335
364
  records.where('users.publicly_visible = ?', value == :public)
@@ -350,13 +379,14 @@ end
350
379
 
351
380
  ###### Override finder methods
352
381
 
353
- Finally if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the resource class.
382
+ Finally if you have more complex requirements for finding you can override the `find` and `find_by_key` methods on the
383
+ resource class.
354
384
 
355
385
  Here's an example that defers the `find` operation to a `current_user` set on the `context` option:
356
386
 
357
387
  ```ruby
358
388
  class AuthorResource < JSONAPI::Resource
359
- attributes :id, :name
389
+ attribute :name
360
390
  model_name 'Person'
361
391
  has_many :posts
362
392
 
@@ -375,23 +405,31 @@ end
375
405
 
376
406
  #### Pagination
377
407
 
378
- Pagination is performed using a `paginator`, which is a class responsible for parsing the `page` request parameters and applying the pagination logic to the results.
408
+ Pagination is performed using a `paginator`, which is a class responsible for parsing the `page` request parameters and
409
+ applying the pagination logic to the results.
379
410
 
380
411
  ##### Paginators
381
412
 
382
- `JSONAPI::Resource` supports several pagination methods by default, and allows you to implement a custom system if the defaults do not meet your needs.
413
+ `JSONAPI::Resource` supports several pagination methods by default, and allows you to implement a custom system if the
414
+ defaults do not meet your needs.
383
415
 
384
416
  ###### Paged Paginator
385
417
 
386
- The `paged` `paginator` returns results based on pages of a fixed size. Valid `page` parameters are `number` and `size`. If `number` is omitted the first page is returned. If `size` is omitted the `default_page_size` from the configuration settings is used.
418
+ The `paged` `paginator` returns results based on pages of a fixed size. Valid `page` parameters are `number` and `size`.
419
+ If `number` is omitted the first page is returned. If `size` is omitted the `default_page_size` from the configuration
420
+ settings is used.
387
421
 
388
422
  ###### Offset Paginator
389
423
 
390
- The `offset` `paginator` returns results based on an offset from the beginning of the resultset. Valid `page` parameters are `offset` and `limit`. If `offset` is omitted a value of 0 will be used. If `limit` is omitted the `default_page_size` from the configuration settings is used.
424
+ The `offset` `paginator` returns results based on an offset from the beginning of the resultset. Valid `page` parameters
425
+ are `offset` and `limit`. If `offset` is omitted a value of 0 will be used. If `limit` is omitted the `default_page_size`
426
+ from the configuration settings is used.
391
427
 
392
428
  ###### Custom Paginators
393
429
 
394
- Custom `paginators` can be used. These should derive from `Paginator`. The `apply` method takes a `relation` and is expected to return a `relation`. The `initialize` method receives the parameters from the `page` request parameters. It is up to the paginator author to parse and validate these parameters.
430
+ Custom `paginators` can be used. These should derive from `Paginator`. The `apply` method takes a `relation` and
431
+ `order_options` and is expected to return a `relation`. The `initialize` method receives the parameters from the `page`
432
+ request parameters. It is up to the paginator author to parse and validate these parameters.
395
433
 
396
434
  For example, here is a very simple single record at a time paginator:
397
435
 
@@ -402,7 +440,7 @@ class SingleRecordPaginator < JSONAPI::Paginator
402
440
  @page = params.to_i
403
441
  end
404
442
 
405
- def apply(relation)
443
+ def apply(relation, order_options)
406
444
  relation.offset(@page).limit(1)
407
445
  end
408
446
  end
@@ -410,7 +448,8 @@ end
410
448
 
411
449
  ##### Paginator Configuration
412
450
 
413
- The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example, in your `config/initializers/jsonapi_resources.rb`:
451
+ The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example, in your
452
+ `config/initializers/jsonapi_resources.rb`:
414
453
 
415
454
  ```ruby
416
455
  JSONAPI.configure do |config|
@@ -424,7 +463,8 @@ end
424
463
 
425
464
  If no `default_paginator` is configured, pagination will be disabled by default.
426
465
 
427
- Paginators can also be set at the resource-level, which will override the default setting. This is done using the `paginator` method:
466
+ Paginators can also be set at the resource-level, which will override the default setting. This is done using the
467
+ `paginator` method:
428
468
 
429
469
  ```ruby
430
470
  class BookResource < JSONAPI::Resource
@@ -439,7 +479,8 @@ To disable pagination in a resource, specify `:none` for `paginator`.
439
479
 
440
480
  #### Callbacks
441
481
 
442
- `ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be used to from `ActiveRecord`.
482
+ `ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be
483
+ used to from `ActiveRecord`.
443
484
 
444
485
  For example, you might use a callback to perform authorization on your resource before an action.
445
486
 
@@ -478,11 +519,65 @@ Callbacks can be defined for the following `JSONAPI::Resource` events:
478
519
 
479
520
  Callbacks can also be defined for `JSONAPI::OperationsProcessor` events:
480
521
  - `:operations`: The set of operations.
481
- - `:operation`: The individual operations.
522
+ - `:operation`: Any individual operation.
523
+ - `:find_operation`: A `find_operation`.
524
+ - `:show_operation`: A `show_operation`.
525
+ - `:show_association_operation`: A `show_association_operation`.
526
+ - `:show_related_resource_operation`: A `show_related_resource_operation`.
527
+ - `:show_related_resources_operation`: A `show_related_resources_operation`.
528
+ - `:create_resource_operation`: A `create_resource_operation`.
529
+ - `:remove_resource_operation`: A `remove_resource_operation`.
530
+ - `:replace_fields_operation`: A `replace_fields_operation`.
531
+ - `:replace_has_one_association_operation`: A `replace_has_one_association_operation`.
532
+ - `:create_has_many_association_operation`: A `create_has_many_association_operation`.
533
+ - `:replace_has_many_association_operation`: A `replace_has_many_association_operation`.
534
+ - `:remove_has_many_association_operation`: A `remove_has_many_association_operation`.
535
+ - `:remove_has_one_association_operation`: A `remove_has_one_association_operation`.
536
+
537
+ The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`, the full list of
538
+ `@operations`, each individual `@operation` and the `@result` variables.
539
+
540
+ ##### Custom `OperationsProcessor` Example to Return total_count in Meta
541
+
542
+ To return the total record count of a find operation in the meta data of a find operation you can create a custom
543
+ OperationsProcessor. For example:
544
+
545
+ ```ruby
546
+ class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
547
+ after_find_operation do
548
+ count = @operation.resource_klass.find_count(@operation.resource_klass.verify_filters(@operation.filters, @context),
549
+ context: @context,
550
+ include_directives: @operation.include_directives,
551
+ sort_criteria: @operation.sort_criteria)
552
+
553
+ @operation_meta[:total_records] = count
554
+ end
555
+ end
556
+ ```
557
+
558
+ Set the configuration option `operations_processor` to use the new `CountingActiveRecordOperationsProcessor` by
559
+ specifying the snake cased name of the class (without the `OperationsProcessor`).
560
+
561
+ ```ruby
562
+ JSONAPI.configure do |config|
563
+ config.operations_processor = :counting_active_record
564
+ end
565
+ ```
566
+
567
+ The callback code will be called after each find. It will use the same options as the find operation, without the
568
+ pagination, to collect the record count. This is stored in the `operation_meta`, which will be returned in the top level
569
+ meta element.
482
570
 
483
571
  ### Controllers
484
572
 
485
- `JSONAPI::Resources` provides a class, `ResourceController`, that can be used as the base class for your controllers. `ResourceController` supports `index`, `show`, `create`, `update`, and `destroy` methods. Just deriving your controller from `ResourceController` will give you a fully functional controller.
573
+ There are two ways to implement a controller for your resources. Either derive from `ResourceController` or import
574
+ the `ActsAsResourceController` module.
575
+
576
+ ##### ResourceController
577
+
578
+ `JSONAPI::Resources` provides a class, `ResourceController`, that can be used as the base class for your controllers.
579
+ `ResourceController` supports `index`, `show`, `create`, `update`, and `destroy` methods. Just deriving your controller
580
+ from `ResourceController` will give you a fully functional controller.
486
581
 
487
582
  For example:
488
583
 
@@ -512,17 +607,30 @@ class PeopleController < ApplicationController
512
607
  end
513
608
  ```
514
609
 
610
+ ##### ActsAsResourceController
611
+
612
+ `JSONAPI::Resources` also provides a module, `JSONAPI::ActsAsResourceController`. You can include this module to
613
+ mix in all the features of `ResourceController` into your existing controller class.
614
+
615
+ For example:
616
+
617
+ ```ruby
618
+ class PostsController < ActionController::Base
619
+ include JSONAPI::ActsAsResourceController
620
+ end
621
+ ```
622
+
515
623
  #### Namespaces
516
624
 
517
625
  JSONAPI::Resources supports namespacing of controllers and resources. With namespacing you can version your API.
518
626
 
519
627
  If you namespace your controller it will require a namespaced resource.
520
628
 
521
- In the following example we have a `resource` that isn't namespaced, and one the has now been namespaced. There are slight differences between the two resources, as might be seen in a new version of an API:
629
+ In the following example we have a `resource` that isn't namespaced, and one the has now been namespaced. There are
630
+ slight differences between the two resources, as might be seen in a new version of an API:
522
631
 
523
632
  ```ruby
524
633
  class PostResource < JSONAPI::Resource
525
- attribute :id
526
634
  attribute :title
527
635
  attribute :body
528
636
  attribute :subject
@@ -546,7 +654,6 @@ module Api
546
654
  class PostResource < JSONAPI::Resource
547
655
  # V1 replaces the non-namespaced resource
548
656
  # V1 no longer supports tags and now calls author 'writer'
549
- attribute :id
550
657
  attribute :title
551
658
  attribute :body
552
659
  attribute :subject
@@ -563,7 +670,7 @@ module Api
563
670
  end
564
671
 
565
672
  class WriterResource < JSONAPI::Resource
566
- attributes :id, :name, :email
673
+ attributes :name, :email
567
674
  model_name 'Person'
568
675
  has_many :posts
569
676
 
@@ -628,13 +735,26 @@ module JSONAPI
628
735
  TYPE_MISMATCH = 116
629
736
  INVALID_PAGE_OBJECT = 117
630
737
  INVALID_PAGE_VALUE = 118
738
+ INVALID_FIELD_FORMAT = 119
739
+ INVALID_FILTERS_SYNTAX = 120
740
+ FORBIDDEN = 403
631
741
  RECORD_NOT_FOUND = 404
742
+ UNSUPPORTED_MEDIA_TYPE = 415
632
743
  LOCKED = 423
633
744
  end
634
745
  ```
635
746
 
636
747
  These codes can be customized in your app by creating an initializer to override any or all of the codes.
637
748
 
749
+ In addition textual error coses can be returned by setting the configuration option `use_text_errors = true`. For
750
+ example:
751
+
752
+ ```ruby
753
+ JSONAPI.configure do |config|
754
+ config.use_text_errors = :true
755
+ end
756
+ ```
757
+
638
758
  ### Serializer
639
759
 
640
760
  The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` must be
@@ -649,17 +769,49 @@ JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new
649
769
 
650
770
  This returns results like this:
651
771
 
652
- ```ruby
772
+ ```json
653
773
  {
654
- posts: {
655
- id: 1,
656
- title: 'New post',
657
- body: 'A body!!!',
658
- links: {
659
- section: nil,
660
- author: 1,
661
- tags: [1,2,3],
662
- comments: [1,2]
774
+ "data": {
775
+ "type": "posts",
776
+ "id": "1",
777
+ "links": {
778
+ "self": "http://example.com/posts/1"
779
+ },
780
+ "attributes": {
781
+ "title": "New post",
782
+ "body": "A body!!!",
783
+ "subject": "New post"
784
+ },
785
+ "relationships": {
786
+ "section": {
787
+ "links": {
788
+ "self": "http://example.com/posts/1/links/section",
789
+ "related": "http://example.com/posts/1/section"
790
+ },
791
+ "data": null
792
+ },
793
+ "author": {
794
+ "links": {
795
+ "self": "http://example.com/posts/1/links/author",
796
+ "related": "http://example.com/posts/1/author"
797
+ },
798
+ "data": {
799
+ "type": "people",
800
+ "id": "1"
801
+ }
802
+ },
803
+ "tags": {
804
+ "links": {
805
+ "self": "http://example.com/posts/1/links/tags",
806
+ "related": "http://example.com/posts/1/tags"
807
+ }
808
+ },
809
+ "comments": {
810
+ "links": {
811
+ "self": "http://example.com/posts/1/links/comments",
812
+ "related": "http://example.com/posts/1/comments"
813
+ }
814
+ }
663
815
  }
664
816
  }
665
817
  }
@@ -681,22 +833,23 @@ An array of resources. Nested resources can be specified with dot notation.
681
833
 
682
834
  A hash of resource types and arrays of fields for each resource type.
683
835
 
684
- *Purpose*: determines which fields are serialized for a resource type. This encompasses both attributes and association ids in the links section for a resource. Fields are global for a resource type.
836
+ *Purpose*: determines which fields are serialized for a resource type. This encompasses both attributes and
837
+ association ids in the links section for a resource. Fields are global for a resource type.
685
838
 
686
- *Example*: ```fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}```
839
+ *Example*: ```fields: { people: [:email, :comments], posts: [:title, :author], comments: [:body, :post]}```
687
840
 
688
841
  ```ruby
689
842
  post = Post.find(1)
690
- JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(
691
- PostResource.new(post),
692
- include: ['comments','author','comments.tags','author.posts'],
843
+ include_resources = ['comments','author','comments.tags','author.posts']
844
+
845
+ JSONAPI::ResourceSerializer.new(PostResource, include: include_resources,
693
846
  fields: {
694
- people: [:id, :email, :comments],
695
- posts: [:id, :title, :author],
847
+ people: [:email, :comments],
848
+ posts: [:title, :author],
696
849
  tags: [:name],
697
- comments: [:id, :body, :post]
850
+ comments: [:body, :post]
698
851
  }
699
- )
852
+ ).serialize_to_hash(PostResource.new(post))
700
853
  ```
701
854
 
702
855
  ##### `context`
@@ -781,7 +934,8 @@ edit_contact GET /contacts/:id/edit(.:format) contacts#edit
781
934
  ```
782
935
 
783
936
  To manually add in the nested routes you can use the `jsonapi_links`, `jsonapi_related_resources` and
784
- `jsonapi_related_resource` inside the block. Or, you can add the default set of nested routes using the `jsonapi_relationships` method. For example:
937
+ `jsonapi_related_resource` inside the block. Or, you can add the default set of nested routes using the
938
+ `jsonapi_relationships` method. For example:
785
939
 
786
940
  ```ruby
787
941
  Rails.application.routes.draw do
@@ -883,13 +1037,16 @@ phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) co
883
1037
 
884
1038
  #### Formatting
885
1039
 
886
- JR by default uses some simple rules to format an attribute for serialization. Strings and Integers are output to JSON as is, and all other values have `.to_s` applied to them. This outputs something in all cases, but it is certainly not correct for every situation.
1040
+ JR by default uses some simple rules to format an attribute for serialization. Strings and Integers are output to JSON
1041
+ as is, and all other values have `.to_s` applied to them. This outputs something in all cases, but it is certainly not
1042
+ correct for every situation.
887
1043
 
888
- If you want to change the way an attribute is serialized you have a couple of ways. The simplest method is to create a getter method on the resource which overrides the attribute and apply the formatting there. For example:
1044
+ If you want to change the way an attribute is serialized you have a couple of ways. The simplest method is to create a
1045
+ getter method on the resource which overrides the attribute and apply the formatting there. For example:
889
1046
 
890
1047
  ```ruby
891
1048
  class PersonResource < JSONAPI::Resource
892
- attributes :id, :name, :email
1049
+ attributes :name, :email
893
1050
  attribute :last_login_time
894
1051
 
895
1052
  def last_login_time
@@ -898,30 +1055,34 @@ class PersonResource < JSONAPI::Resource
898
1055
  end
899
1056
  ```
900
1057
 
901
- This is simple to implement for a one off situation, but not for example if you want to apply the same formatting rules to all DateTime fields in your system. Another issue is the attribute on the resource will always return a formatted response, whether you want it or not.
1058
+ This is simple to implement for a one off situation, but not for example if you want to apply the same formatting rules
1059
+ to all DateTime fields in your system. Another issue is the attribute on the resource will always return a formatted
1060
+ response, whether you want it or not.
902
1061
 
903
1062
  ##### Value Formatters
904
1063
 
905
- To overcome the above limitations JR uses Value Formatters. Value Formatters allow you to control the way values are handled for an attribute. The `format` can be set per attribute as it is declared in the resource. For example:
1064
+ To overcome the above limitations JR uses Value Formatters. Value Formatters allow you to control the way values are
1065
+ handled for an attribute. The `format` can be set per attribute as it is declared in the resource. For example:
906
1066
 
907
1067
  ```ruby
908
1068
  class PersonResource < JSONAPI::Resource
909
- attributes :id, :name, :email
910
- attribute :last_login_time, format: :date_with_timezone
1069
+ attributes :name, :email
1070
+ attribute :last_login_time, format: :date_with_utc_timezone
911
1071
  end
912
1072
  ```
913
1073
 
914
- A Value formatter has a `format` and an `unformat` method. Here's the base ValueFormatter and DefaultValueFormatter for reference:
1074
+ A Value formatter has a `format` and an `unformat` method. Here's the base ValueFormatter and DefaultValueFormatter for
1075
+ reference:
915
1076
 
916
1077
  ```ruby
917
1078
  module JSONAPI
918
1079
  class ValueFormatter < Formatter
919
1080
  class << self
920
- def format(raw_value, context)
1081
+ def format(raw_value)
921
1082
  super(raw_value)
922
1083
  end
923
1084
 
924
- def unformat(value, context)
1085
+ def unformat(value)
925
1086
  super(value)
926
1087
  end
927
1088
  ...
@@ -931,7 +1092,7 @@ end
931
1092
 
932
1093
  class DefaultValueFormatter < JSONAPI::ValueFormatter
933
1094
  class << self
934
- def format(raw_value, context)
1095
+ def format(raw_value)
935
1096
  case raw_value
936
1097
  when String, Integer
937
1098
  return raw_value
@@ -943,15 +1104,22 @@ class DefaultValueFormatter < JSONAPI::ValueFormatter
943
1104
  end
944
1105
  ```
945
1106
 
946
- You can also create your own Value Formatter. Value Formatters must be named with the `format` name followed by `ValueFormatter`, i.e. `DateWithTimezoneValueFormatter` and derive from `JSONAPI::ValueFormatter`. It is recommended that you create a directory for your formatters, called `formatters`.
1107
+ You can also create your own Value Formatter. Value Formatters must be named with the `format` name followed by
1108
+ `ValueFormatter`, i.e. `DateWithUTCTimezoneValueFormatter` and derive from `JSONAPI::ValueFormatter`. It is
1109
+ recommended that you create a directory for your formatters, called `formatters`.
947
1110
 
948
- The `format` method is called by the `ResourceSerializer` as is serializing a resource. The format method takes the `raw_value`, and `context` parameters. `raw_value` is the value as read from the model, and `context` is the context of the current user/request. From this you can base the formatted version of the attribute current context.
1111
+ The `format` method is called by the `ResourceSerializer` as is serializing a resource. The format method takes the
1112
+ `raw_value` parameter. `raw_value` is the value as read from the model.
949
1113
 
950
- The `unformat` method is called when processing the request. Each incoming attribute (except `links`) are run through the `unformat` method. The `unformat` method takes the `value`, and `context` parameters. `value` is the value as it comes in on the request, and `context` is the context of the current user/request. This allows you process the incoming value to alter its state before it is stored in the model. By default no processing is applied.
1114
+ The `unformat` method is called when processing the request. Each incoming attribute (except `links`) are run through
1115
+ the `unformat` method. The `unformat` method takes a `value`, which is the value as it comes in on the
1116
+ request. This allows you process the incoming value to alter its state before it is stored in the model.
951
1117
 
952
1118
  ###### Use a Different Default Value Formatter
953
1119
 
954
- Another way to handle formatting is to set a different default value formatter. This will affect all attributes that do not have a `format` set. You can do this by overriding the `default_attribute_options` method for a resource (or a base resource for a system wide change).
1120
+ Another way to handle formatting is to set a different default value formatter. This will affect all attributes that do
1121
+ not have a `format` set. You can do this by overriding the `default_attribute_options` method for a resource (or a base
1122
+ resource for a system wide change).
955
1123
 
956
1124
  ```ruby
957
1125
  def default_attribute_options
@@ -964,12 +1132,12 @@ and
964
1132
  ```ruby
965
1133
  class MyDefaultValueFormatter < JSONAPI::ValueFormatter
966
1134
  class << self
967
- def format(raw_value, context)
1135
+ def format(raw_value)
968
1136
  case raw_value
969
1137
  when String, Integer
970
1138
  return raw_value
971
1139
  when DateTime
972
- return raw_value.in_time_zone(context[:current_user].time_zone).to_s
1140
+ return raw_value.in_time_zone('UTC').to_s
973
1141
  else
974
1142
  return raw_value.to_s
975
1143
  end
@@ -978,22 +1146,26 @@ class MyDefaultValueFormatter < JSONAPI::ValueFormatter
978
1146
  end
979
1147
  ```
980
1148
 
981
- This way all DateTime values will be formatted to display in the specified timezone.
1149
+ This way all DateTime values will be formatted to display in the UTC timezone.
982
1150
 
983
1151
  #### Key Format
984
1152
 
985
- By default JR uses dasherized keys as per the [JSON API naming recommendations](http://jsonapi.org/recommendations/#naming). This can be changed by specifying a different key formatter.
1153
+ By default JR uses dasherized keys as per the
1154
+ [JSON API naming recommendations](http://jsonapi.org/recommendations/#naming). This can be changed by specifying a
1155
+ different key formatter.
986
1156
 
987
- For example, to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add the following:
1157
+ For example, to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add
1158
+ the following:
988
1159
 
989
- ```
1160
+ ```ruby
990
1161
  JSONAPI.configure do |config|
991
1162
  # built in key format options are :underscored_key, :camelized_key and :dasherized_key
992
1163
  config.json_key_format = :camelized_key
993
1164
  end
994
1165
  ```
995
1166
 
996
- This will cause the serializer to use the `CamelizedKeyFormatter`. You can also create your own `KeyFormatter`, for example:
1167
+ This will cause the serializer to use the `CamelizedKeyFormatter`. You can also create your own `KeyFormatter`, for
1168
+ example:
997
1169
 
998
1170
  ```ruby
999
1171
  class UpperCamelizedKeyFormatter < JSONAPI::KeyFormatter