jsonapi-resources 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +274 -102
- data/jsonapi-resources.gemspec +1 -0
- data/lib/jsonapi-resources.rb +15 -0
- data/lib/jsonapi/active_record_operations_processor.rb +21 -10
- data/lib/jsonapi/acts_as_resource_controller.rb +175 -0
- data/lib/jsonapi/configuration.rb +11 -0
- data/lib/jsonapi/error_codes.rb +7 -4
- data/lib/jsonapi/exceptions.rb +23 -15
- data/lib/jsonapi/formatter.rb +5 -5
- data/lib/jsonapi/include_directives.rb +67 -0
- data/lib/jsonapi/operation.rb +185 -65
- data/lib/jsonapi/operation_result.rb +38 -5
- data/lib/jsonapi/operation_results.rb +33 -0
- data/lib/jsonapi/operations_processor.rb +49 -9
- data/lib/jsonapi/paginator.rb +31 -17
- data/lib/jsonapi/request.rb +347 -163
- data/lib/jsonapi/resource.rb +159 -56
- data/lib/jsonapi/resource_controller.rb +1 -234
- data/lib/jsonapi/resource_serializer.rb +55 -69
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +87 -0
- data/lib/jsonapi/routing_ext.rb +17 -11
- data/test/controllers/controller_test.rb +602 -326
- data/test/fixtures/active_record.rb +96 -6
- data/test/fixtures/line_items.yml +7 -1
- data/test/fixtures/numeros_telefone.yml +3 -0
- data/test/fixtures/purchase_orders.yml +6 -0
- data/test/integration/requests/request_test.rb +129 -60
- data/test/integration/routes/routes_test.rb +17 -17
- data/test/test_helper.rb +23 -5
- data/test/unit/jsonapi_request/jsonapi_request_test.rb +48 -0
- data/test/unit/operation/operations_processor_test.rb +242 -54
- data/test/unit/resource/resource_test.rb +108 -2
- data/test/unit/serializer/include_directives_test.rb +108 -0
- data/test/unit/serializer/response_document_test.rb +61 -0
- data/test/unit/serializer/serializer_test.rb +679 -520
- metadata +26 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57590c2f9544bd54d43d1a024ae878bae321f87f
|
4
|
+
data.tar.gz: 102b9414a9ebddd19b249daa2684f9460083c670
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b139efb93f753f5a20e314e02e4ae3cc33bed69d7c9303ebf4bf838c9013ea5a8ef625f4720f70114b3ff6b219788e4b7303e7f55723829980fb1ac729d8d9a
|
7
|
+
data.tar.gz: d7580a2b8e21f6813b2a6e2c7d59f8b8f6230e2834c1e9a16c10215d40ebe3e479e451f8d4fad3a811d301e0abed030a66b27d2c3ce9b37553278d6c44bc5a77
|
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# JSONAPI::Resources [](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
|
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
|
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
|
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)
|
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
|
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
|
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
|
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
|
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.
|
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 :
|
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
|
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 :
|
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
|
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
|
122
|
+
##### Creatable and Updatable Attributes
|
112
123
|
|
113
|
-
By default all attributes are assumed to be
|
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 :
|
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.
|
139
|
+
def self.updatable_fields(context)
|
128
140
|
super - [:full_name]
|
129
141
|
end
|
130
142
|
|
131
|
-
def self.
|
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.
|
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
|
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
|
-
|
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`
|
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 :
|
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
|
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
|
173
|
-
|
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`
|
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
|
-
|
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 :
|
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 :
|
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
|
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 :
|
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 :
|
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
|
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 :
|
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.
|
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
|
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 :
|
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
|
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
|
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
|
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
|
-
|
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
|
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
|
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`.
|
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
|
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
|
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
|
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
|
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
|
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`:
|
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
|
-
|
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
|
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 :
|
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
|
-
```
|
772
|
+
```json
|
653
773
|
{
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
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
|
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: [:
|
839
|
+
*Example*: ```fields: { people: [:email, :comments], posts: [:title, :author], comments: [:body, :post]}```
|
687
840
|
|
688
841
|
```ruby
|
689
842
|
post = Post.find(1)
|
690
|
-
|
691
|
-
|
692
|
-
|
843
|
+
include_resources = ['comments','author','comments.tags','author.posts']
|
844
|
+
|
845
|
+
JSONAPI::ResourceSerializer.new(PostResource, include: include_resources,
|
693
846
|
fields: {
|
694
|
-
people: [:
|
695
|
-
posts: [:
|
847
|
+
people: [:email, :comments],
|
848
|
+
posts: [:title, :author],
|
696
849
|
tags: [:name],
|
697
|
-
comments: [:
|
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
|
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
|
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
|
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 :
|
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
|
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
|
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 :
|
910
|
-
attribute :last_login_time, format: :
|
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
|
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
|
1081
|
+
def format(raw_value)
|
921
1082
|
super(raw_value)
|
922
1083
|
end
|
923
1084
|
|
924
|
-
def unformat(value
|
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
|
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
|
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
|
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
|
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
|
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
|
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(
|
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
|
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
|
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
|
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
|
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
|