jsonapi-resources 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +4 -0
- data/README.md +186 -45
- data/lib/jsonapi/association.rb +6 -20
- data/lib/jsonapi/configuration.rb +27 -0
- data/lib/jsonapi/error_codes.rb +2 -0
- data/lib/jsonapi/exceptions.rb +41 -0
- data/lib/jsonapi/formatter.rb +94 -0
- data/lib/jsonapi/operation.rb +46 -9
- data/lib/jsonapi/request.rb +178 -48
- data/lib/jsonapi/resource.rb +51 -84
- data/lib/jsonapi/resource_controller.rb +53 -25
- data/lib/jsonapi/resource_for.rb +4 -6
- data/lib/jsonapi/resource_serializer.rb +59 -31
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +9 -1
- data/lib/jsonapi-resources.rb +2 -0
- data/test/controllers/controller_test.rb +706 -335
- data/test/fixtures/active_record.rb +60 -24
- data/test/integration/requests/request_test.rb +10 -15
- data/test/integration/routes/routes_test.rb +68 -20
- data/test/test_helper.rb +45 -3
- data/test/unit/operation/operations_processor_test.rb +49 -7
- data/test/unit/resource/resource_test.rb +2 -2
- data/test/unit/serializer/serializer_test.rb +483 -360
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51cd49198136e8a49dd3bb8138721babc499c0cf
|
4
|
+
data.tar.gz: 29ddb62840cb17c2e753cfb03ab5e5cfe9dcdbe0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 276688390c5ae3d33ab1c40ea8a181ed8024c8c99cca514de6a941f52a64ace4b39b81b4aaba7222730b5d0604ec9e52a6cb48e0a49d61933df148daf0ecba3f
|
7
|
+
data.tar.gz: bd36f549771a05afb8efec6ef4337e3888a9b2d9ad153fd870cba852c06c353a32b42484e0eb9b84cbfbfa5c3a14dfcb3254b0d1c3c07099f0debe849524a583
|
data/.travis.yml
ADDED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# JSONAPI::Resources
|
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
3
|
JSONAPI::Resources, or "JR", provides a framework for developing a server that complies with the [JSON API](http://jsonapi.org/) specification.
|
4
4
|
|
@@ -8,7 +8,7 @@ JR is designed to work with Rails, and provides custom routes, controllers, and
|
|
8
8
|
|
9
9
|
## Demo App
|
10
10
|
|
11
|
-
We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), available to show how JR is used.
|
11
|
+
We have a simple demo app, called [Peeps](https://github.com/cerebris/peeps), available to show how JR is used.
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -38,7 +38,7 @@ Resources must be derived from `JSONAPI::Resource`, or a class that is itself de
|
|
38
38
|
|
39
39
|
For example:
|
40
40
|
|
41
|
-
```
|
41
|
+
```ruby
|
42
42
|
require 'jsonapi/resource'
|
43
43
|
|
44
44
|
class ContactResource < JSONAPI::Resource
|
@@ -51,7 +51,7 @@ Any of a resource's attributes that are accessible must be explicitly declared.
|
|
51
51
|
|
52
52
|
For example:
|
53
53
|
|
54
|
-
```
|
54
|
+
```ruby
|
55
55
|
require 'jsonapi/resource'
|
56
56
|
|
57
57
|
class ContactResource < JSONAPI::Resource
|
@@ -63,11 +63,11 @@ end
|
|
63
63
|
|
64
64
|
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.
|
65
65
|
|
66
|
-
A resource object wraps a Ruby object, usually an ActiveModel record, which is available as the `@object` variable. This allows a resource's methods to access the underlying object.
|
66
|
+
A resource object wraps a Ruby object, usually an ActiveModel record, which is available as the `@object` variable. This allows a resource's methods to access the underlying object.
|
67
67
|
|
68
68
|
For example, a computed attribute for `full_name` could be defined as such:
|
69
69
|
|
70
|
-
```
|
70
|
+
```ruby
|
71
71
|
require 'jsonapi/resource'
|
72
72
|
|
73
73
|
class ContactResource < JSONAPI::Resource
|
@@ -82,11 +82,11 @@ end
|
|
82
82
|
|
83
83
|
##### Fetchable Attributes
|
84
84
|
|
85
|
-
By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding the `fetchable` method.
|
85
|
+
By default all attributes are assumed to be fetchable. The list of fetchable attributes can be filtered by overriding the `fetchable` method.
|
86
86
|
|
87
87
|
Here's an example that prevents guest users from seeing the `email` field:
|
88
88
|
|
89
|
-
```
|
89
|
+
```ruby
|
90
90
|
class AuthorResource < JSONAPI::Resource
|
91
91
|
attributes :id, :name, :email
|
92
92
|
model_name 'Person'
|
@@ -110,7 +110,7 @@ By default all attributes are assumed to be updateble and creatable. To prevent
|
|
110
110
|
|
111
111
|
This example prevents `full_name` from being set:
|
112
112
|
|
113
|
-
```
|
113
|
+
```ruby
|
114
114
|
require 'jsonapi/resource'
|
115
115
|
|
116
116
|
class ContactResource < JSONAPI::Resource
|
@@ -119,7 +119,7 @@ class ContactResource < JSONAPI::Resource
|
|
119
119
|
def full_name
|
120
120
|
"#{@object.name_first}, #{@object.name_last}"
|
121
121
|
end
|
122
|
-
|
122
|
+
|
123
123
|
def self.updateable(keys, context = nil)
|
124
124
|
super(keys - [:full_name])
|
125
125
|
end
|
@@ -132,11 +132,24 @@ end
|
|
132
132
|
|
133
133
|
The `context` is not used by the `ResourceController`, but may be used if you override the controller methods.
|
134
134
|
|
135
|
+
##### Attribute Formatting
|
136
|
+
|
137
|
+
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:
|
138
|
+
|
139
|
+
```
|
140
|
+
class PersonResource < JSONAPI::Resource
|
141
|
+
attributes :id, :name, :email
|
142
|
+
attribute :last_login_time, format: :date_with_timezone
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
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.
|
147
|
+
|
135
148
|
#### Key
|
136
149
|
|
137
150
|
The primary key of the resource defaults to `id`, which can be changed using the `key` method.
|
138
151
|
|
139
|
-
```
|
152
|
+
```ruby
|
140
153
|
class CurrencyResource < JSONAPI::Resource
|
141
154
|
key :code
|
142
155
|
attributes :code, :name
|
@@ -150,7 +163,7 @@ end
|
|
150
163
|
|
151
164
|
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:
|
152
165
|
|
153
|
-
```
|
166
|
+
```ruby
|
154
167
|
class AuthorResource < JSONAPI::Resource
|
155
168
|
attributes :id, :name
|
156
169
|
model_name 'Person'
|
@@ -160,11 +173,11 @@ end
|
|
160
173
|
|
161
174
|
#### Associations
|
162
175
|
|
163
|
-
Related resources need to be specified in the resource. These are declared with the `has_one` and the `has_many` methods.
|
176
|
+
Related resources need to be specified in the resource. These are declared with the `has_one` and the `has_many` methods.
|
164
177
|
|
165
178
|
Here's a simple example where a post has a single author and an author can have many posts:
|
166
179
|
|
167
|
-
```
|
180
|
+
```ruby
|
168
181
|
class PostResource < JSONAPI::Resource
|
169
182
|
attribute :id, :title, :body
|
170
183
|
|
@@ -174,7 +187,7 @@ end
|
|
174
187
|
|
175
188
|
And the corresponding author:
|
176
189
|
|
177
|
-
```
|
190
|
+
```ruby
|
178
191
|
class AuthorResource < JSONAPI::Resource
|
179
192
|
attribute :id, :name
|
180
193
|
|
@@ -192,7 +205,7 @@ The association methods support the following options:
|
|
192
205
|
|
193
206
|
Examples:
|
194
207
|
|
195
|
-
```
|
208
|
+
```ruby
|
196
209
|
class CommentResource < JSONAPI::Resource
|
197
210
|
attributes :id, :body
|
198
211
|
has_one :post
|
@@ -201,7 +214,7 @@ Examples:
|
|
201
214
|
end
|
202
215
|
```
|
203
216
|
|
204
|
-
```
|
217
|
+
```ruby
|
205
218
|
class ExpenseEntryResource < JSONAPI::Resource
|
206
219
|
attributes :id, :cost, :transaction_date
|
207
220
|
|
@@ -213,11 +226,11 @@ end
|
|
213
226
|
#### Filters
|
214
227
|
|
215
228
|
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
|
216
|
-
resource class.
|
229
|
+
resource class.
|
217
230
|
|
218
231
|
For example:
|
219
232
|
|
220
|
-
```
|
233
|
+
```ruby
|
221
234
|
require 'jsonapi/resource'
|
222
235
|
|
223
236
|
class ContactResource < JSONAPI::Resource
|
@@ -234,7 +247,7 @@ Basic finding by filters is supported by resources. However if you have more com
|
|
234
247
|
|
235
248
|
Here's an example that defers the `find` operation to a `current_user` set on the `context`:
|
236
249
|
|
237
|
-
```
|
250
|
+
```ruby
|
238
251
|
class AuthorResource < JSONAPI::Resource
|
239
252
|
attributes :id, :name
|
240
253
|
model_name 'Person'
|
@@ -244,7 +257,7 @@ class AuthorResource < JSONAPI::Resource
|
|
244
257
|
|
245
258
|
def self.find(attrs, context = nil)
|
246
259
|
authors = context.current_user.find_authors(attrs)
|
247
|
-
|
260
|
+
|
248
261
|
return authors.map do |author|
|
249
262
|
self.new(author)
|
250
263
|
end
|
@@ -254,11 +267,11 @@ end
|
|
254
267
|
|
255
268
|
### Controllers
|
256
269
|
|
257
|
-
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.
|
270
|
+
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.
|
258
271
|
|
259
272
|
For example:
|
260
273
|
|
261
|
-
```
|
274
|
+
```ruby
|
262
275
|
class PeopleController < JSONAPI::ResourceController
|
263
276
|
|
264
277
|
end
|
@@ -266,11 +279,11 @@ end
|
|
266
279
|
|
267
280
|
Of course you are free to extend this as needed and override action handlers or other methods.
|
268
281
|
|
269
|
-
The context that's used for serialization and resource configuration is set by the controller's `context` method.
|
282
|
+
The context that's used for serialization and resource configuration is set by the controller's `context` method.
|
270
283
|
|
271
284
|
For example:
|
272
285
|
|
273
|
-
```
|
286
|
+
```ruby
|
274
287
|
class ApplicationController < JSONAPI::ResourceController
|
275
288
|
def context
|
276
289
|
{current_user: current_user}
|
@@ -288,7 +301,7 @@ end
|
|
288
301
|
|
289
302
|
Error codes are provided for each error object returned, based on the error. These errors are:
|
290
303
|
|
291
|
-
```
|
304
|
+
```ruby
|
292
305
|
module JSONAPI
|
293
306
|
VALIDATION_ERROR = 100
|
294
307
|
INVALID_RESOURCE = 101
|
@@ -311,18 +324,19 @@ These codes can be customized in your app by creating an initializer to override
|
|
311
324
|
|
312
325
|
### Serializer
|
313
326
|
|
314
|
-
The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` has a `
|
327
|
+
The `ResourceSerializer` can be used to serialize a resource into JSON API compliant JSON. `ResourceSerializer` has a `serialize_to_hash` method that takes a resource instance to serialize. For example:
|
315
328
|
|
316
|
-
```
|
329
|
+
```ruby
|
330
|
+
require 'jsonapi/resource_serializer'
|
317
331
|
post = Post.find(1)
|
318
|
-
JSONAPI::ResourceSerializer.new.
|
332
|
+
JSONAPI::ResourceSerializer.new.serialize_to_hash(PostResource.new(post))
|
319
333
|
```
|
320
334
|
|
321
335
|
This returns results like this:
|
322
336
|
|
323
|
-
```
|
337
|
+
```ruby
|
324
338
|
{
|
325
|
-
posts:
|
339
|
+
posts: {
|
326
340
|
id: 1,
|
327
341
|
title: 'New post',
|
328
342
|
body: 'A body!!!',
|
@@ -332,13 +346,13 @@ This returns results like this:
|
|
332
346
|
tags: [1,2,3],
|
333
347
|
comments: [1,2]
|
334
348
|
}
|
335
|
-
}
|
349
|
+
}
|
336
350
|
}
|
337
|
-
```
|
351
|
+
```
|
338
352
|
|
339
|
-
####
|
353
|
+
#### Serialize_to_hash method options
|
340
354
|
|
341
|
-
The
|
355
|
+
The `serialize_to_hash` method also takes some optional parameters:
|
342
356
|
|
343
357
|
##### `include`
|
344
358
|
|
@@ -347,7 +361,7 @@ An array of resources. Nested resources can be specified with dot notation.
|
|
347
361
|
*Purpose*: determines which objects will be side loaded with the source objects in a linked section
|
348
362
|
|
349
363
|
*Example*: ```include: ['comments','author','comments.tags','author.posts']```
|
350
|
-
|
364
|
+
|
351
365
|
##### `fields`
|
352
366
|
|
353
367
|
A hash of resource types and arrays of fields for each resource type.
|
@@ -356,9 +370,9 @@ A hash of resource types and arrays of fields for each resource type.
|
|
356
370
|
|
357
371
|
*Example*: ```fields: { people: [:id, :email, :comments], posts: [:id, :title, :author], comments: [:id, :body, :post]}```
|
358
372
|
|
359
|
-
```
|
373
|
+
```ruby
|
360
374
|
post = Post.find(1)
|
361
|
-
JSONAPI::ResourceSerializer.new.
|
375
|
+
JSONAPI::ResourceSerializer.new.serialize_to_hash(PostResource.new(post),
|
362
376
|
include: ['comments','author','comments.tags','author.posts'],
|
363
377
|
fields: {
|
364
378
|
people: [:id, :email, :comments],
|
@@ -374,12 +388,12 @@ Context data can be provided to the serializer, which passes it to each resource
|
|
374
388
|
#### Routing
|
375
389
|
|
376
390
|
JR has a couple of helper methods available to assist you with setting up routes.
|
377
|
-
|
391
|
+
|
378
392
|
##### `jsonapi_resources`
|
379
393
|
|
380
394
|
Like `resources` in ActionDispatch, `jsonapi_resources` provides resourceful routes mapping between HTTP verbs and URLs and controller actions. This will also setup mappings for relationship URLs for a resource's associations. For example
|
381
395
|
|
382
|
-
```
|
396
|
+
```ruby
|
383
397
|
require 'jsonapi/routing_ext'
|
384
398
|
|
385
399
|
Peeps::Application.routes.draw do
|
@@ -389,7 +403,7 @@ end
|
|
389
403
|
```
|
390
404
|
|
391
405
|
gives the following routes
|
392
|
-
|
406
|
+
|
393
407
|
```
|
394
408
|
Prefix Verb URI Pattern Controller#Action
|
395
409
|
contact_links_phone_numbers GET /contacts/:contact_id/links/phone_numbers(.:format) contacts#show_association {:association=>"phone_numbers"}
|
@@ -427,15 +441,142 @@ will not create any relationship routes.
|
|
427
441
|
|
428
442
|
You can add relationship routes in with `jsonapi_links`, for example:
|
429
443
|
|
444
|
+
```ruby
|
445
|
+
Rails.application.routes.draw do
|
446
|
+
jsonapi_resources :posts, except: [:destroy] do
|
447
|
+
jsonapi_link :author, except: [:destroy]
|
448
|
+
jsonapi_links :tags, only: [:show, :create]
|
449
|
+
end
|
450
|
+
end
|
451
|
+
```
|
452
|
+
|
453
|
+
This will create relationship routes for author (show and create, but not destroy) and for tags (again show and create, but not destroy).
|
454
|
+
|
455
|
+
#### Formatting
|
456
|
+
|
457
|
+
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.
|
458
|
+
|
459
|
+
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:
|
460
|
+
|
461
|
+
```
|
462
|
+
class PersonResource < JSONAPI::Resource
|
463
|
+
attributes :id, :name, :email
|
464
|
+
attribute :last_login_time
|
465
|
+
|
466
|
+
def last_login_time
|
467
|
+
@object.last_login_time.in_time_zone('Eastern Time (US & Canada)').to_s
|
468
|
+
end
|
469
|
+
end
|
470
|
+
```
|
471
|
+
|
472
|
+
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.
|
473
|
+
|
474
|
+
##### Value Formatters
|
475
|
+
|
476
|
+
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:
|
477
|
+
|
430
478
|
```
|
431
|
-
|
432
|
-
|
433
|
-
|
479
|
+
class PersonResource < JSONAPI::Resource
|
480
|
+
attributes :id, :name, :email
|
481
|
+
attribute :last_login_time, format: :date_with_timezone
|
482
|
+
end
|
483
|
+
```
|
484
|
+
|
485
|
+
A Value formatter has a `format` and an `unformat` method. Here's the base ValueFormatter and DefaultValueFormatter for reference:
|
486
|
+
|
487
|
+
```
|
488
|
+
module JSONAPI
|
489
|
+
class ValueFormatter < Formatter
|
490
|
+
class << self
|
491
|
+
def format(raw_value, source, context)
|
492
|
+
super(raw_value)
|
493
|
+
end
|
494
|
+
|
495
|
+
def unformat(value, resource_klass, context)
|
496
|
+
super(value)
|
434
497
|
end
|
498
|
+
...
|
499
|
+
end
|
500
|
+
end
|
501
|
+
end
|
435
502
|
|
503
|
+
class DefaultValueFormatter < JSONAPI::ValueFormatter
|
504
|
+
class << self
|
505
|
+
def format(raw_value, source, context)
|
506
|
+
case raw_value
|
507
|
+
when String, Integer
|
508
|
+
return raw_value
|
509
|
+
else
|
510
|
+
return raw_value.to_s
|
511
|
+
end
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
436
515
|
```
|
437
516
|
|
438
|
-
|
517
|
+
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`.
|
518
|
+
|
519
|
+
The `format` method is called by the ResourceSerializer as is serializing a resource. The format method takes the `raw_value`, `source`, and `context` parameters. `raw_value` is the value as read from the model, `source` is the resource instance itself, and `context` is the context of the current user/request. From this you can base the formatted version of the attribute on other values on the resource or the current context.
|
520
|
+
|
521
|
+
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`, `resource_klass`, and `context` parameters. `value` is the value as it comes in on the request, `resource_klass` is the resource that is being updated or created, 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.
|
522
|
+
|
523
|
+
###### Use a Different Default Value Formatter
|
524
|
+
|
525
|
+
Another way to handle formatting is to set a different default value formatter. This will affect all attributes that do notw 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).
|
526
|
+
|
527
|
+
```
|
528
|
+
def default_attribute_options
|
529
|
+
{format: :my_default}
|
530
|
+
end
|
531
|
+
```
|
532
|
+
|
533
|
+
and
|
534
|
+
|
535
|
+
```
|
536
|
+
class MyDefaultValueFormatter < JSONAPI::ValueFormatter
|
537
|
+
class << self
|
538
|
+
def format(raw_value, source, context)
|
539
|
+
case raw_value
|
540
|
+
when String, Integer
|
541
|
+
return raw_value
|
542
|
+
when DateTime
|
543
|
+
return raw_value.in_time_zone('Eastern Time (US & Canada)').to_s
|
544
|
+
else
|
545
|
+
return raw_value.to_s
|
546
|
+
end
|
547
|
+
end
|
548
|
+
end
|
549
|
+
end
|
550
|
+
```
|
551
|
+
|
552
|
+
This way all DateTime values will be formatted to display in the specified timezone.
|
553
|
+
|
554
|
+
#### Key Format
|
555
|
+
|
556
|
+
JSONAPI is agnostic on the format of the keys used in the responses. By default JR uses underscored keys which match the attribute names used by rails models. This can be changed by specifying a different key formatter.
|
557
|
+
|
558
|
+
For example to use camel cased keys with an initial lowercase character (JSON's default) create an initializer and add the following:
|
559
|
+
|
560
|
+
```
|
561
|
+
JSONAPI.configure do |config|
|
562
|
+
# built in key format options are :underscored_key, :camelized_key and :dasherized_key
|
563
|
+
config.json_key_format = :camelized_key
|
564
|
+
end
|
565
|
+
```
|
566
|
+
|
567
|
+
This will cause the serializer to use the CamelizedKeyFormatter. Besides UnderscoredKeyFormatter and CamelizedKeyFormatter JR defines the DasherizedKeyFormatter. You can also create your own KeyFormatter, for example:
|
568
|
+
|
569
|
+
```
|
570
|
+
class UpperCamelizedKeyFormatter < JSONAPI::KeyFormatter
|
571
|
+
class << self
|
572
|
+
def format(key)
|
573
|
+
super.camelize(:upper)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
end
|
577
|
+
```
|
578
|
+
|
579
|
+
You would specify this in `JSONAPI.configure` as `:upper_camelized`.
|
439
580
|
|
440
581
|
## Contributing
|
441
582
|
|
data/lib/jsonapi/association.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
class Association
|
3
|
+
attr_reader :primary_key, :acts_as_set, :type, :key, :options, :name, :class_name
|
4
|
+
|
3
5
|
def initialize(name, options={})
|
4
6
|
@name = name.to_s
|
5
7
|
@options = options
|
@@ -8,27 +10,11 @@ module JSONAPI
|
|
8
10
|
@acts_as_set = options.fetch(:acts_as_set, false) == true
|
9
11
|
end
|
10
12
|
|
11
|
-
def key
|
12
|
-
@key
|
13
|
-
end
|
14
|
-
|
15
|
-
def primary_key
|
16
|
-
@primary_key
|
17
|
-
end
|
18
|
-
|
19
|
-
def acts_as_set
|
20
|
-
@acts_as_set
|
21
|
-
end
|
22
|
-
|
23
|
-
def serialize_type_name
|
24
|
-
@serialize_type_name
|
25
|
-
end
|
26
|
-
|
27
13
|
class HasOne < Association
|
28
14
|
def initialize(name, options={})
|
29
15
|
super
|
30
|
-
class_name = options.fetch(:class_name, name.to_s.capitalize)
|
31
|
-
@
|
16
|
+
@class_name = options.fetch(:class_name, name.to_s.capitalize)
|
17
|
+
@type = class_name.underscore.pluralize.to_sym
|
32
18
|
@key ||= "#{name}_id".to_sym
|
33
19
|
end
|
34
20
|
end
|
@@ -36,8 +22,8 @@ module JSONAPI
|
|
36
22
|
class HasMany < Association
|
37
23
|
def initialize(name, options={})
|
38
24
|
super
|
39
|
-
class_name = options.fetch(:class_name, name.to_s.capitalize.singularize)
|
40
|
-
@
|
25
|
+
@class_name = options.fetch(:class_name, name.to_s.capitalize.singularize)
|
26
|
+
@type = class_name.underscore.pluralize.to_sym
|
41
27
|
@key ||= "#{name.to_s.singularize}_ids".to_sym
|
42
28
|
end
|
43
29
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'jsonapi/formatter'
|
2
|
+
|
3
|
+
module JSONAPI
|
4
|
+
class Configuration
|
5
|
+
attr_reader :json_key_format, :key_formatter
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
9
|
+
self.json_key_format = :underscored_key
|
10
|
+
end
|
11
|
+
|
12
|
+
def json_key_format=(format)
|
13
|
+
@json_key_format = format
|
14
|
+
@key_formatter = JSONAPI::Formatter.formatter_for(format)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :configuration
|
20
|
+
end
|
21
|
+
|
22
|
+
@configuration ||= Configuration.new
|
23
|
+
|
24
|
+
def self.configure
|
25
|
+
yield(@configuration)
|
26
|
+
end
|
27
|
+
end
|
data/lib/jsonapi/error_codes.rb
CHANGED
data/lib/jsonapi/exceptions.rb
CHANGED
@@ -30,6 +30,32 @@ module JSONAPI
|
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
33
|
+
class HasManyRelationExists < Error
|
34
|
+
attr_accessor :id
|
35
|
+
def initialize(id)
|
36
|
+
@id = id
|
37
|
+
end
|
38
|
+
|
39
|
+
def errors
|
40
|
+
[JSONAPI::Error.new(code: JSONAPI::RELATION_EXISTS,
|
41
|
+
status: :bad_request,
|
42
|
+
title: 'Relation exists',
|
43
|
+
detail: "The relation to #{id} already exists.")]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class HasOneRelationExists < Error
|
48
|
+
def initialize
|
49
|
+
end
|
50
|
+
|
51
|
+
def errors
|
52
|
+
[JSONAPI::Error.new(code: JSONAPI::RELATION_EXISTS,
|
53
|
+
status: :bad_request,
|
54
|
+
title: 'Relation exists',
|
55
|
+
detail: 'The relation already exists.')]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
33
59
|
class FilterNotAllowed < Error
|
34
60
|
attr_accessor :filter
|
35
61
|
def initialize(filter)
|
@@ -89,6 +115,21 @@ module JSONAPI
|
|
89
115
|
end
|
90
116
|
end
|
91
117
|
|
118
|
+
class InvalidInclude < Error
|
119
|
+
attr_accessor :association, :resource
|
120
|
+
def initialize(resource, association)
|
121
|
+
@resource = resource
|
122
|
+
@association = association
|
123
|
+
end
|
124
|
+
|
125
|
+
def errors
|
126
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_INCLUDE,
|
127
|
+
status: :bad_request,
|
128
|
+
title: 'Invalid field',
|
129
|
+
detail: "#{association} is not a valid association of #{resource}")]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
92
133
|
class ParametersNotAllowed < Error
|
93
134
|
attr_accessor :params
|
94
135
|
def initialize(params)
|