jsonapi-resources 0.1.1 → 0.2.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 +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
|