jsonapi-resources 0.1.1 → 0.2.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 +68 -1
- data/Rakefile +3 -7
- data/lib/jsonapi-resources.rb +1 -0
- data/lib/jsonapi/configuration.rb +29 -4
- data/lib/jsonapi/error_codes.rb +6 -2
- data/lib/jsonapi/exceptions.rb +92 -19
- data/lib/jsonapi/operation.rb +0 -18
- data/lib/jsonapi/paginator.rb +98 -0
- data/lib/jsonapi/request.rb +257 -182
- data/lib/jsonapi/resource.rb +58 -47
- data/lib/jsonapi/resource_controller.rb +85 -29
- data/lib/jsonapi/resource_serializer.rb +88 -33
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/routing_ext.rb +38 -12
- data/test/controllers/controller_test.rb +761 -455
- data/test/fixtures/active_record.rb +90 -18
- data/test/integration/requests/request_test.rb +183 -25
- data/test/integration/routes/routes_test.rb +0 -5
- data/test/test_helper.rb +31 -7
- data/test/unit/operation/operations_processor_test.rb +28 -1
- data/test/unit/resource/resource_test.rb +4 -0
- data/test/unit/serializer/serializer_test.rb +882 -377
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7639392d6acfd2092e36563bebd32e791b7df37a
|
4
|
+
data.tar.gz: 6c7e794b2646d60214746515810d9648566699e8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0d53447be085f1ce717782ad9238116e30968e0380fe6e5612e3b5b1a680978285e363904c9c4a96b5ac115bcec8addad959851cd4dfeb58244ab704215eba1c
|
7
|
+
data.tar.gz: 725dd95e1409afd23a1f22a6f7399a8fcc402734adc5da297cc019f65186c2777b3bb8c2aacf7955e1dcd1cc89af1fced6702898222ca2b3746be2e4800bbebc
|
data/README.md
CHANGED
@@ -330,6 +330,70 @@ class AuthorResource < JSONAPI::Resource
|
|
330
330
|
end
|
331
331
|
```
|
332
332
|
|
333
|
+
#### Pagination
|
334
|
+
|
335
|
+
Pagination is performed using a `paginator`, which is a class responsible for parsing the `page` request parameters and applying the pagination logic to the results.
|
336
|
+
|
337
|
+
##### Paginators
|
338
|
+
|
339
|
+
`JSONAPI::Resource` supports several pagination methods by default, and allows you to implement a custom system if the defaults do not meet your needs.
|
340
|
+
|
341
|
+
###### Paged Paginator
|
342
|
+
|
343
|
+
The `paged` `paginator` returns results based on pages of a fixed size. Valid `page` parameters are `number` and `size`. If `number` is omitted the first page is returned. If `size` is omitted the `default_page_size` from the configuration settings is used.
|
344
|
+
|
345
|
+
###### Offset Paginator
|
346
|
+
|
347
|
+
The `offset` `paginator` returns results based on an offset from the beginning of the resultset. Valid `page` parameters are `offset` and `limit`. If `offset` is omitted a value of 0 will be used. If `limit` is omitted the `default_page_size` from the configuration settings is used.
|
348
|
+
|
349
|
+
###### Custom Paginators
|
350
|
+
|
351
|
+
Custom `paginators` can be used. These should derive from `Paginator`. The `apply` method takes a `relation` and is expected to return a `relation`. The `initialize` method receives the parameters from the `page` request parameters. It is up to the paginator author to parse and validate these parameters.
|
352
|
+
|
353
|
+
For example, here is a very simple single record at a time paginator:
|
354
|
+
|
355
|
+
```ruby
|
356
|
+
class SingleRecordPaginator < JSONAPI::Paginator
|
357
|
+
def initialize(params)
|
358
|
+
# param parsing and validation here
|
359
|
+
@page = params.to_i
|
360
|
+
end
|
361
|
+
|
362
|
+
def apply(relation)
|
363
|
+
relation.offset(@page).limit(1)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
```
|
367
|
+
|
368
|
+
##### Paginator Configuration
|
369
|
+
|
370
|
+
The default paginator, which will be used for all resources, is set using `JSONAPI.configure`. For example:
|
371
|
+
|
372
|
+
```ruby
|
373
|
+
JSONAPI.configure do |config|
|
374
|
+
# built in paginators are :none, :offset, :cursor, :paged
|
375
|
+
self.default_paginator = :offset
|
376
|
+
|
377
|
+
self.default_page_size = 10
|
378
|
+
self.maximum_page_size = 20
|
379
|
+
end
|
380
|
+
```
|
381
|
+
|
382
|
+
If no `default_paginator` is configured, pagination will be disabled by default.
|
383
|
+
|
384
|
+
Paginators can also be set at the resource-level, which will override the default setting. This is done using the `paginator` method:
|
385
|
+
|
386
|
+
```ruby
|
387
|
+
class BookResource < JSONAPI::Resource
|
388
|
+
attribute :title
|
389
|
+
attribute :isbn
|
390
|
+
|
391
|
+
paginator :offset
|
392
|
+
end
|
393
|
+
```
|
394
|
+
|
395
|
+
To disable pagination in a resource, specify `:none` for `paginator`.
|
396
|
+
|
333
397
|
#### Callbacks
|
334
398
|
|
335
399
|
`ActiveSupport::Callbacks` is used to provide callback functionality, so the behavior is very similar to what you may be used to from `ActiveRecord`.
|
@@ -517,7 +581,10 @@ module JSONAPI
|
|
517
581
|
INVALID_INCLUDE = 112
|
518
582
|
RELATION_EXISTS = 113
|
519
583
|
INVALID_SORT_PARAM = 114
|
520
|
-
|
584
|
+
INVALID_LINKS_OBJECT = 115
|
585
|
+
TYPE_MISMATCH = 116
|
586
|
+
INVALID_PAGE_OBJECT = 117
|
587
|
+
INVALID_PAGE_VALUE = 118
|
521
588
|
RECORD_NOT_FOUND = 404
|
522
589
|
LOCKED = 423
|
523
590
|
end
|
data/Rakefile
CHANGED
@@ -1,13 +1,9 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
require "rake/testtask"
|
4
|
+
require './test/test_helper.rb'
|
4
5
|
|
5
|
-
|
6
|
-
test_task = Rake::TestTask.new(:test) do |t|
|
7
|
-
t.libs << 'test'
|
8
|
-
t.pattern = 'test/**/*_test.rb'
|
9
|
-
t.verbose = true
|
10
|
-
end
|
6
|
+
TestApp.load_tasks
|
11
7
|
|
12
8
|
task default: :test
|
13
9
|
|
@@ -21,4 +17,4 @@ namespace :test do
|
|
21
17
|
sh cmd.join(' ')
|
22
18
|
end
|
23
19
|
end
|
24
|
-
end
|
20
|
+
end
|
data/lib/jsonapi-resources.rb
CHANGED
@@ -2,16 +2,29 @@ require 'jsonapi/formatter'
|
|
2
2
|
|
3
3
|
module JSONAPI
|
4
4
|
class Configuration
|
5
|
-
attr_reader :json_key_format,
|
5
|
+
attr_reader :json_key_format,
|
6
|
+
:key_formatter,
|
7
|
+
:route_format,
|
8
|
+
:route_formatter,
|
9
|
+
:allowed_request_params,
|
10
|
+
:default_paginator,
|
11
|
+
:default_page_size,
|
12
|
+
:maximum_page_size
|
6
13
|
|
7
14
|
def initialize
|
8
15
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
9
|
-
self.json_key_format = :
|
16
|
+
self.json_key_format = :dasherized_key
|
10
17
|
|
11
18
|
#:underscored_route, :camelized_route, :dasherized_route, or custom
|
12
|
-
self.route_format = :
|
19
|
+
self.route_format = :dasherized_route
|
13
20
|
|
14
|
-
self.allowed_request_params = [:include, :fields, :format, :controller, :action, :sort]
|
21
|
+
self.allowed_request_params = [:include, :fields, :format, :controller, :action, :sort, :page]
|
22
|
+
|
23
|
+
# :none, :offset, :paged, or a custom paginator name
|
24
|
+
self.default_paginator = :none
|
25
|
+
|
26
|
+
self.default_page_size = 10
|
27
|
+
self.maximum_page_size = 20
|
15
28
|
end
|
16
29
|
|
17
30
|
def json_key_format=(format)
|
@@ -27,6 +40,18 @@ module JSONAPI
|
|
27
40
|
def allowed_request_params=(allowed_request_params)
|
28
41
|
@allowed_request_params = allowed_request_params
|
29
42
|
end
|
43
|
+
|
44
|
+
def default_paginator=(default_paginator)
|
45
|
+
@default_paginator = default_paginator
|
46
|
+
end
|
47
|
+
|
48
|
+
def default_page_size=(default_page_size)
|
49
|
+
@default_page_size = default_page_size
|
50
|
+
end
|
51
|
+
|
52
|
+
def maximum_page_size=(maximum_page_size)
|
53
|
+
@maximum_page_size = maximum_page_size
|
54
|
+
end
|
30
55
|
end
|
31
56
|
|
32
57
|
class << self
|
data/lib/jsonapi/error_codes.rb
CHANGED
@@ -12,8 +12,12 @@ module JSONAPI
|
|
12
12
|
KEY_NOT_INCLUDED_IN_URL = 110
|
13
13
|
INVALID_INCLUDE = 112
|
14
14
|
RELATION_EXISTS = 113
|
15
|
-
|
16
|
-
|
15
|
+
INVALID_SORT_CRITERIA = 114
|
16
|
+
INVALID_LINKS_OBJECT = 115
|
17
|
+
TYPE_MISMATCH = 116
|
18
|
+
INVALID_PAGE_OBJECT = 117
|
19
|
+
INVALID_PAGE_VALUE = 118
|
20
|
+
INVALID_SORT_FORMAT = 119
|
17
21
|
RECORD_NOT_FOUND = 404
|
18
22
|
UNSUPPORTED_MEDIA_TYPE = 415
|
19
23
|
LOCKED = 423
|
data/lib/jsonapi/exceptions.rb
CHANGED
@@ -58,18 +58,6 @@ module JSONAPI
|
|
58
58
|
end
|
59
59
|
end
|
60
60
|
|
61
|
-
class HasOneRelationExists < Error
|
62
|
-
def initialize
|
63
|
-
end
|
64
|
-
|
65
|
-
def errors
|
66
|
-
[JSONAPI::Error.new(code: JSONAPI::RELATION_EXISTS,
|
67
|
-
status: :bad_request,
|
68
|
-
title: 'Relation exists',
|
69
|
-
detail: 'The relation already exists.')]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
61
|
class FilterNotAllowed < Error
|
74
62
|
attr_accessor :filter
|
75
63
|
def initialize(filter)
|
@@ -114,6 +102,34 @@ module JSONAPI
|
|
114
102
|
end
|
115
103
|
end
|
116
104
|
|
105
|
+
class InvalidLinksObject < Error
|
106
|
+
attr_accessor :value
|
107
|
+
def initialize(value)
|
108
|
+
@value = value
|
109
|
+
end
|
110
|
+
|
111
|
+
def errors
|
112
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_LINKS_OBJECT,
|
113
|
+
status: :bad_request,
|
114
|
+
title: 'Invalid Links Object',
|
115
|
+
detail: "#{value} is not a valid Links Object.")]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class TypeMismatch < Error
|
120
|
+
attr_accessor :type
|
121
|
+
def initialize(type)
|
122
|
+
@type = type
|
123
|
+
end
|
124
|
+
|
125
|
+
def errors
|
126
|
+
[JSONAPI::Error.new(code: JSONAPI::TYPE_MISMATCH,
|
127
|
+
status: :bad_request,
|
128
|
+
title: 'Type Mismatch',
|
129
|
+
detail: "#{type} is not a valid type for this operation.")]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
117
133
|
class InvalidField < Error
|
118
134
|
attr_accessor :field, :type
|
119
135
|
def initialize(type, field)
|
@@ -144,18 +160,33 @@ module JSONAPI
|
|
144
160
|
end
|
145
161
|
end
|
146
162
|
|
147
|
-
class
|
148
|
-
attr_accessor :
|
149
|
-
def initialize(resource,
|
163
|
+
class InvalidSortCriteria < Error
|
164
|
+
attr_accessor :sort_criteria, :resource
|
165
|
+
def initialize(resource, sort_criteria)
|
150
166
|
@resource = resource
|
151
|
-
@
|
167
|
+
@sort_criteria = sort_criteria
|
152
168
|
end
|
153
169
|
|
154
170
|
def errors
|
155
|
-
[JSONAPI::Error.new(code: JSONAPI::
|
171
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_SORT_CRITERIA,
|
156
172
|
status: :bad_request,
|
157
|
-
title: 'Invalid sort
|
158
|
-
detail: "#{
|
173
|
+
title: 'Invalid sort criteria',
|
174
|
+
detail: "#{sort_criteria} is not a valid sort criteria for #{resource}")]
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
class InvalidSortFormat < Error
|
179
|
+
attr_accessor :sort_criteria, :resource
|
180
|
+
def initialize(resource, sort_criteria)
|
181
|
+
@resource = resource
|
182
|
+
@sort_criteria = sort_criteria
|
183
|
+
end
|
184
|
+
|
185
|
+
def errors
|
186
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_SORT_FORMAT,
|
187
|
+
status: :bad_request,
|
188
|
+
title: 'Invalid sort format',
|
189
|
+
detail: "#{sort_criteria} must start with a direction (+ or -)")]
|
159
190
|
end
|
160
191
|
end
|
161
192
|
|
@@ -257,5 +288,47 @@ module JSONAPI
|
|
257
288
|
end
|
258
289
|
end
|
259
290
|
|
291
|
+
class InvalidPageObject < Error
|
292
|
+
def errors
|
293
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_PAGE_OBJECT,
|
294
|
+
status: :bad_request,
|
295
|
+
title: 'Invalid Page Object',
|
296
|
+
detail: 'Invalid Page Object.')]
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
class PageParametersNotAllowed < Error
|
302
|
+
attr_accessor :params
|
303
|
+
def initialize(params)
|
304
|
+
@params = params
|
305
|
+
end
|
306
|
+
|
307
|
+
def errors
|
308
|
+
params.collect { |param|
|
309
|
+
JSONAPI::Error.new(code: JSONAPI::PARAM_NOT_ALLOWED,
|
310
|
+
status: :bad_request,
|
311
|
+
title: 'Page parameter not allowed',
|
312
|
+
detail: "#{param} is not an allowed page parameter.")
|
313
|
+
}
|
314
|
+
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
class InvalidPageValue < Error
|
319
|
+
attr_accessor :page, :value
|
320
|
+
def initialize(page, value, msg = nil)
|
321
|
+
@page = page
|
322
|
+
@value = value
|
323
|
+
@msg = msg.nil? ? "#{value} is not a valid value for #{page} page parameter." : msg
|
324
|
+
end
|
325
|
+
|
326
|
+
def errors
|
327
|
+
[JSONAPI::Error.new(code: JSONAPI::INVALID_PAGE_VALUE,
|
328
|
+
status: :bad_request,
|
329
|
+
title: 'Invalid page value',
|
330
|
+
detail: @msg)]
|
331
|
+
end
|
332
|
+
end
|
260
333
|
end
|
261
334
|
end
|
data/lib/jsonapi/operation.rb
CHANGED
@@ -68,24 +68,6 @@ module JSONAPI
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
class CreateHasOneAssociationOperation < Operation
|
72
|
-
attr_reader :resource_id, :association_type, :key_value
|
73
|
-
|
74
|
-
def initialize(resource_klass, resource_id, association_type, key_value)
|
75
|
-
@resource_id = resource_id
|
76
|
-
@key_value = key_value
|
77
|
-
@association_type = association_type.to_sym
|
78
|
-
super(resource_klass)
|
79
|
-
end
|
80
|
-
|
81
|
-
def apply(context)
|
82
|
-
resource = @resource_klass.find_by_key(@resource_id, context: context)
|
83
|
-
resource.create_has_one_link(@association_type, @key_value)
|
84
|
-
|
85
|
-
return JSONAPI::OperationResult.new(:no_content)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
71
|
class ReplaceHasOneAssociationOperation < Operation
|
90
72
|
attr_reader :resource_id, :association_type, :key_value
|
91
73
|
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
class Paginator
|
3
|
+
def initialize(params)
|
4
|
+
end
|
5
|
+
|
6
|
+
def apply(relation)
|
7
|
+
# :nocov:
|
8
|
+
relation
|
9
|
+
# :nocov:
|
10
|
+
end
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# :nocov:
|
14
|
+
if RUBY_VERSION >= '2.0'
|
15
|
+
def paginator_for(paginator)
|
16
|
+
paginator_class_name = "#{paginator.to_s.camelize}Paginator"
|
17
|
+
Object.const_get(paginator_class_name) if paginator_class_name
|
18
|
+
end
|
19
|
+
else
|
20
|
+
def paginator_for(paginator)
|
21
|
+
paginator_class_name = "#{paginator.to_s.camelize}Paginator"
|
22
|
+
paginator_class_name.safe_constantize if paginator_class_name
|
23
|
+
end
|
24
|
+
end
|
25
|
+
# :nocov:
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class OffsetPaginator < JSONAPI::Paginator
|
31
|
+
def initialize(params)
|
32
|
+
parse_pagination_params(params)
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply(relation)
|
36
|
+
relation.offset(@offset).limit(@limit)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def parse_pagination_params(params)
|
41
|
+
if params.nil?
|
42
|
+
@offset = 0
|
43
|
+
@limit = JSONAPI.configuration.default_page_size
|
44
|
+
elsif params.is_a?(ActionController::Parameters)
|
45
|
+
validparams = params.permit(:offset, :limit)
|
46
|
+
|
47
|
+
@offset = validparams[:offset] ? validparams[:offset].to_i : 0
|
48
|
+
@limit = validparams[:limit] ? validparams[:limit].to_i : JSONAPI.configuration.default_page_size
|
49
|
+
|
50
|
+
if @limit < 1
|
51
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit])
|
52
|
+
elsif @limit > JSONAPI.configuration.maximum_page_size
|
53
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:limit, validparams[:limit],
|
54
|
+
"Limit exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
55
|
+
end
|
56
|
+
else
|
57
|
+
raise JSONAPI::Exceptions::InvalidPageObject.new
|
58
|
+
end
|
59
|
+
rescue ActionController::UnpermittedParameters => e
|
60
|
+
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class PagedPaginator < JSONAPI::Paginator
|
65
|
+
def initialize(params)
|
66
|
+
parse_pagination_params(params)
|
67
|
+
end
|
68
|
+
|
69
|
+
def apply(relation)
|
70
|
+
offset = (@number - 1) * @size
|
71
|
+
relation.offset(offset).limit(@size)
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def parse_pagination_params(params)
|
76
|
+
if params.nil?
|
77
|
+
@number = 1
|
78
|
+
@size = JSONAPI.configuration.default_page_size
|
79
|
+
elsif params.is_a?(ActionController::Parameters)
|
80
|
+
validparams = params.permit(:number, :size)
|
81
|
+
|
82
|
+
@size = validparams[:size] ? validparams[:size].to_i : JSONAPI.configuration.default_page_size
|
83
|
+
@number = validparams[:number] ? validparams[:number].to_i : 1
|
84
|
+
|
85
|
+
if @size < 1
|
86
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size])
|
87
|
+
elsif @size > JSONAPI.configuration.maximum_page_size
|
88
|
+
raise JSONAPI::Exceptions::InvalidPageValue.new(:size, validparams[:size],
|
89
|
+
"size exceeds maximum page size of #{JSONAPI.configuration.maximum_page_size}.")
|
90
|
+
end
|
91
|
+
else
|
92
|
+
@size = JSONAPI.configuration.default_page_size
|
93
|
+
@number = params.to_i
|
94
|
+
end
|
95
|
+
rescue ActionController::UnpermittedParameters => e
|
96
|
+
raise JSONAPI::Exceptions::PageParametersNotAllowed.new(e.params)
|
97
|
+
end
|
98
|
+
end
|