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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8d4cbc3730824bb1f4ad419724a0a472fd16d2e0
4
- data.tar.gz: 7b30da4000a4c8996bd2e2eb2cc8288e9303b728
3
+ metadata.gz: 51cd49198136e8a49dd3bb8138721babc499c0cf
4
+ data.tar.gz: 29ddb62840cb17c2e753cfb03ab5e5cfe9dcdbe0
5
5
  SHA512:
6
- metadata.gz: 00aa2506b52f8635e7f7c92ef5755bf2028c4229bdc55aef4dca8339151af3222f05f677b4b2d26af91e55ec3d04ce9bc765d4e2552691662936634cfed69bd1
7
- data.tar.gz: 75e1b2f23fc2b9f8f046768cb213edd888b951ae3a490c2d5890381141674283ab8fe26c701cce6948e43e3c15978127eb1b9d9039e129d03c6cb79e7123500c
6
+ metadata.gz: 276688390c5ae3d33ab1c40ea8a181ed8024c8c99cca514de6a941f52a64ace4b39b81b4aaba7222730b5d0604ec9e52a6cb48e0a49d61933df148daf0ecba3f
7
+ data.tar.gz: bd36f549771a05afb8efec6ef4337e3888a9b2d9ad153fd870cba852c06c353a32b42484e0eb9b84cbfbfa5c3a14dfcb3254b0d1c3c07099f0debe849524a583
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.1
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 `serialize` method that takes a resource instance to serialize. For example:
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.serialize(PostResource.new(post))
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
- #### Serialize method options
353
+ #### Serialize_to_hash method options
340
354
 
341
- The serialize method also takes some optional parameters:
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.serialize(PostResource.new(post),
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
- jsonapi_resources :posts, except: [:destroy] do
432
- jsonapi_link :author, except: [:destroy]
433
- jsonapi_links :tags, only: [:show, :create]
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
- This will create relationship routes for author (show and create, but not destroy) and for tags (again show and create, but not destroy).
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
 
@@ -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
- @serialize_type_name = class_name.underscore.pluralize.to_sym
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).to_sym
40
- @serialize_type_name = class_name.to_s.underscore.pluralize.to_sym
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
@@ -10,6 +10,8 @@ module JSONAPI
10
10
  COUNT_MISMATCH = 108
11
11
  KEY_ORDER_MISMATCH = 109
12
12
  KEY_NOT_INCLUDED_IN_URL = 110
13
+ INVALID_INCLUDE = 112
14
+ RELATION_EXISTS = 113
13
15
 
14
16
  RECORD_NOT_FOUND = 404
15
17
  LOCKED = 423
@@ -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)