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.
- 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 [![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
|
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
|