jsonapi-resources 0.4.0 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +65 -33
- data/lib/jsonapi/acts_as_resource_controller.rb +7 -1
- data/lib/jsonapi/configuration.rb +25 -1
- data/lib/jsonapi/operation.rb +28 -1
- data/lib/jsonapi/operation_result.rb +17 -11
- data/lib/jsonapi/operation_results.rb +2 -0
- data/lib/jsonapi/operations_processor.rb +6 -0
- data/lib/jsonapi/paginator.rb +100 -0
- data/lib/jsonapi/request.rb +3 -2
- data/lib/jsonapi/resource_serializer.rb +9 -1
- data/lib/jsonapi/resources/version.rb +1 -1
- data/lib/jsonapi/response_document.rb +39 -0
- data/lib/jsonapi/routing_ext.rb +19 -5
- data/test/controllers/controller_test.rb +41 -2
- data/test/fixtures/active_record.rb +2 -7
- data/test/test_helper.rb +1 -1
- data/test/unit/pagination/offset_paginator_test.rb +206 -0
- data/test/unit/pagination/paged_paginator_test.rb +203 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ccc95678a9008bcac7b1b375cd19e684c626effb
|
4
|
+
data.tar.gz: e7b641e7055f1a88f9673082617812ffe4b8ae25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 77b1fa60f243eb0fe5d62d9a28f6336d613def402e5d933a3e5157be520eb789c8f073630dbe69982c7e2bc8a1f1203a500c74764c46e0c37ce963ef5e86897c
|
7
|
+
data.tar.gz: af564dac990b904cc6bb4ad8b38fe928043c988d6d7d83619a1f538be8522fa1d72b4c7e2aa4ab01e34e96d3742174c8c70495ef054315b51d195fffab369b42
|
data/README.md
CHANGED
@@ -50,8 +50,6 @@ Resources must be derived from `JSONAPI::Resource`, or a class that is itself de
|
|
50
50
|
For example:
|
51
51
|
|
52
52
|
```ruby
|
53
|
-
require 'jsonapi/resource'
|
54
|
-
|
55
53
|
class ContactResource < JSONAPI::Resource
|
56
54
|
end
|
57
55
|
```
|
@@ -64,8 +62,6 @@ the `attribute` method, and multiple attributes can be declared with the `attrib
|
|
64
62
|
For example:
|
65
63
|
|
66
64
|
```ruby
|
67
|
-
require 'jsonapi/resource'
|
68
|
-
|
69
65
|
class ContactResource < JSONAPI::Resource
|
70
66
|
attribute :name_first
|
71
67
|
attributes :name_last, :email, :twitter
|
@@ -81,8 +77,6 @@ This allows a resource's methods to access the underlying model.
|
|
81
77
|
For example, a computed attribute for `full_name` could be defined as such:
|
82
78
|
|
83
79
|
```ruby
|
84
|
-
require 'jsonapi/resource'
|
85
|
-
|
86
80
|
class ContactResource < JSONAPI::Resource
|
87
81
|
attributes :name_first, :name_last, :email, :twitter
|
88
82
|
attribute :full_name
|
@@ -127,8 +121,6 @@ the `update` or `create` methods, override the `self.updatable_fields` and `self
|
|
127
121
|
This example prevents `full_name` from being set:
|
128
122
|
|
129
123
|
```ruby
|
130
|
-
require 'jsonapi/resource'
|
131
|
-
|
132
124
|
class ContactResource < JSONAPI::Resource
|
133
125
|
attributes :name_first, :name_last, :full_name
|
134
126
|
|
@@ -280,8 +272,6 @@ declared using the `filter` method, and multiple filters can be declared with th
|
|
280
272
|
For example:
|
281
273
|
|
282
274
|
```ruby
|
283
|
-
require 'jsonapi/resource'
|
284
|
-
|
285
275
|
class ContactResource < JSONAPI::Resource
|
286
276
|
attributes :name_first, :name_last, :email, :twitter
|
287
277
|
|
@@ -290,6 +280,25 @@ class ContactResource < JSONAPI::Resource
|
|
290
280
|
end
|
291
281
|
```
|
292
282
|
|
283
|
+
##### Default Filters
|
284
|
+
|
285
|
+
A default filter may be defined for a resource using the `default` option on the `filter` method. This default is used
|
286
|
+
unless the request overrides this value.
|
287
|
+
|
288
|
+
For example:
|
289
|
+
|
290
|
+
```ruby
|
291
|
+
class CommentResource < JSONAPI::Resource
|
292
|
+
attributes :body, :status
|
293
|
+
has_one :post
|
294
|
+
has_one :author
|
295
|
+
|
296
|
+
filter :status, default: 'published,pending'
|
297
|
+
end
|
298
|
+
```
|
299
|
+
|
300
|
+
The default value is used as if it came from the request.
|
301
|
+
|
293
302
|
##### Finders
|
294
303
|
|
295
304
|
Basic finding by filters is supported by resources. This is implemented in the `find` and `find_by_key` finder methods.
|
@@ -534,23 +543,22 @@ Callbacks can also be defined for `JSONAPI::OperationsProcessor` events:
|
|
534
543
|
- `:remove_has_many_association_operation`: A `remove_has_many_association_operation`.
|
535
544
|
- `:remove_has_one_association_operation`: A `remove_has_one_association_operation`.
|
536
545
|
|
537
|
-
The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`,
|
538
|
-
`@operations`, each individual `@operation` and the
|
546
|
+
The operation callbacks have access to two meta data hashes, `@operations_meta` and `@operation_meta`, two links hashes,
|
547
|
+
`@operations_links` and `@operation_links`, the full list of `@operations`, each individual `@operation` and the
|
548
|
+
`@result` variables.
|
539
549
|
|
540
550
|
##### Custom `OperationsProcessor` Example to Return total_count in Meta
|
541
551
|
|
552
|
+
Note: this can also be accomplished with the `top_level_meta_include_record_count` option, and in most cases that will
|
553
|
+
be the better option.
|
554
|
+
|
542
555
|
To return the total record count of a find operation in the meta data of a find operation you can create a custom
|
543
556
|
OperationsProcessor. For example:
|
544
557
|
|
545
558
|
```ruby
|
546
559
|
class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
|
547
560
|
after_find_operation do
|
548
|
-
|
549
|
-
context: @context,
|
550
|
-
include_directives: @operation.include_directives,
|
551
|
-
sort_criteria: @operation.sort_criteria)
|
552
|
-
|
553
|
-
@operation_meta[:total_records] = count
|
561
|
+
@operation_meta[:total_records] = @operation.record_count
|
554
562
|
end
|
555
563
|
end
|
556
564
|
```
|
@@ -730,13 +738,14 @@ module JSONAPI
|
|
730
738
|
KEY_NOT_INCLUDED_IN_URL = 110
|
731
739
|
INVALID_INCLUDE = 112
|
732
740
|
RELATION_EXISTS = 113
|
733
|
-
|
741
|
+
INVALID_SORT_CRITERIA = 114
|
734
742
|
INVALID_LINKS_OBJECT = 115
|
735
743
|
TYPE_MISMATCH = 116
|
736
744
|
INVALID_PAGE_OBJECT = 117
|
737
745
|
INVALID_PAGE_VALUE = 118
|
738
746
|
INVALID_FIELD_FORMAT = 119
|
739
747
|
INVALID_FILTERS_SYNTAX = 120
|
748
|
+
SAVE_FAILED = 121
|
740
749
|
FORBIDDEN = 403
|
741
750
|
RECORD_NOT_FOUND = 404
|
742
751
|
UNSUPPORTED_MEDIA_TYPE = 415
|
@@ -746,7 +755,7 @@ end
|
|
746
755
|
|
747
756
|
These codes can be customized in your app by creating an initializer to override any or all of the codes.
|
748
757
|
|
749
|
-
In addition textual error
|
758
|
+
In addition textual error codes can be returned by setting the configuration option `use_text_errors = true`. For
|
750
759
|
example:
|
751
760
|
|
752
761
|
```ruby
|
@@ -762,7 +771,6 @@ The `ResourceSerializer` can be used to serialize a resource into JSON API compl
|
|
762
771
|
method that takes a resource instance or array of resource instances to serialize. For example:
|
763
772
|
|
764
773
|
```ruby
|
765
|
-
require 'jsonapi/resource_serializer'
|
766
774
|
post = Post.find(1)
|
767
775
|
JSONAPI::ResourceSerializer.new(PostResource).serialize_to_hash(PostResource.new(post))
|
768
776
|
```
|
@@ -882,8 +890,6 @@ contact_links_phone_numbers GET /contacts/:contact_id/links/phone-numbers(
|
|
882
890
|
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:association=>"phone_numbers", :source=>"contacts"}
|
883
891
|
contacts GET /contacts(.:format) contacts#index
|
884
892
|
POST /contacts(.:format) contacts#create
|
885
|
-
new_contact GET /contacts/new(.:format) contacts#new
|
886
|
-
edit_contact GET /contacts/:id/edit(.:format) contacts#edit
|
887
893
|
contact GET /contacts/:id(.:format) contacts#show
|
888
894
|
PATCH /contacts/:id(.:format) contacts#update
|
889
895
|
PUT /contacts/:id(.:format) contacts#update
|
@@ -894,8 +900,6 @@ contact_links_phone_numbers GET /contacts/:contact_id/links/phone-numbers(
|
|
894
900
|
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:association=>"contact", :source=>"phone_numbers"}
|
895
901
|
phone_numbers GET /phone-numbers(.:format) phone_numbers#index
|
896
902
|
POST /phone-numbers(.:format) phone_numbers#create
|
897
|
-
new_phone_number GET /phone-numbers/new(.:format) phone_numbers#new
|
898
|
-
edit_phone_number GET /phone-numbers/:id/edit(.:format) phone_numbers#edit
|
899
903
|
phone_number GET /phone-numbers/:id(.:format) phone_numbers#show
|
900
904
|
PATCH /phone-numbers/:id(.:format) phone_numbers#update
|
901
905
|
PUT /phone-numbers/:id(.:format) phone_numbers#update
|
@@ -925,8 +929,6 @@ gives routes that are only related to the primary resource, and none for its rel
|
|
925
929
|
Prefix Verb URI Pattern Controller#Action
|
926
930
|
contacts GET /contacts(.:format) contacts#index
|
927
931
|
POST /contacts(.:format) contacts#create
|
928
|
-
new_contact GET /contacts/new(.:format) contacts#new
|
929
|
-
edit_contact GET /contacts/:id/edit(.:format) contacts#edit
|
930
932
|
contact GET /contacts/:id(.:format) contacts#show
|
931
933
|
PATCH /contacts/:id(.:format) contacts#update
|
932
934
|
PUT /contacts/:id(.:format) contacts#update
|
@@ -965,8 +967,6 @@ contact_links_phone_numbers GET /contacts/:contact_id/links/phone-numbers(.:f
|
|
965
967
|
DELETE /contacts/:contact_id/links/phone-numbers/:keys(.:format) contacts#destroy_association {:association=>"phone_numbers"}
|
966
968
|
contacts GET /contacts(.:format) contacts#index
|
967
969
|
POST /contacts(.:format) contacts#create
|
968
|
-
new_contact GET /contacts/new(.:format) contacts#new
|
969
|
-
edit_contact GET /contacts/:id/edit(.:format) contacts#edit
|
970
970
|
contact GET /contacts/:id(.:format) contacts#show
|
971
971
|
PATCH /contacts/:id(.:format) contacts#update
|
972
972
|
PUT /contacts/:id(.:format) contacts#update
|
@@ -996,8 +996,6 @@ gives the following routes:
|
|
996
996
|
contact_phone_numbers GET /contacts/:contact_id/phone-numbers(.:format) phone_numbers#get_related_resources {:association=>"phone_numbers", :source=>"contacts"}
|
997
997
|
contacts GET /contacts(.:format) contacts#index
|
998
998
|
POST /contacts(.:format) contacts#create
|
999
|
-
new_contact GET /contacts/new(.:format) contacts#new
|
1000
|
-
edit_contact GET /contacts/:id/edit(.:format) contacts#edit
|
1001
999
|
contact GET /contacts/:id(.:format) contacts#show
|
1002
1000
|
PATCH /contacts/:id(.:format) contacts#update
|
1003
1001
|
PUT /contacts/:id(.:format) contacts#update
|
@@ -1026,8 +1024,6 @@ gives the following routes:
|
|
1026
1024
|
phone_number_contact GET /phone-numbers/:phone_number_id/contact(.:format) contacts#get_related_resource {:association=>"contact", :source=>"phone_numbers"}
|
1027
1025
|
phone_numbers GET /phone-numbers(.:format) phone_numbers#index
|
1028
1026
|
POST /phone-numbers(.:format) phone_numbers#create
|
1029
|
-
new_phone_number GET /phone-numbers/new(.:format) phone_numbers#new
|
1030
|
-
edit_phone_number GET /phone-numbers/:id/edit(.:format) phone_numbers#edit
|
1031
1027
|
phone_number GET /phone-numbers/:id(.:format) phone_numbers#show
|
1032
1028
|
PATCH /phone-numbers/:id(.:format) phone_numbers#update
|
1033
1029
|
PUT /phone-numbers/:id(.:format) phone_numbers#update
|
@@ -1179,6 +1175,42 @@ end
|
|
1179
1175
|
|
1180
1176
|
You would specify this in `JSONAPI.configure` as `:upper_camelized`.
|
1181
1177
|
|
1178
|
+
## Configuration
|
1179
|
+
|
1180
|
+
JR has a few configuration options. Some have already been mentioned above. To set configuration options create an
|
1181
|
+
initializer and add the options you wish to set. All options have defaults, so you only need to set the options that
|
1182
|
+
are different. The default options are shown below.
|
1183
|
+
|
1184
|
+
```ruby
|
1185
|
+
JSONAPI.configure do |config|
|
1186
|
+
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
1187
|
+
config.json_key_format = :dasherized_key
|
1188
|
+
|
1189
|
+
#:underscored_route, :camelized_route, :dasherized_route, or custom
|
1190
|
+
config.route_format = :dasherized_route
|
1191
|
+
|
1192
|
+
#:basic, :active_record, or custom
|
1193
|
+
config.operations_processor = :active_record
|
1194
|
+
|
1195
|
+
config.allowed_request_params = [:include, :fields, :format, :controller, :action, :sort, :page]
|
1196
|
+
|
1197
|
+
# :none, :offset, :paged, or a custom paginator name
|
1198
|
+
config.default_paginator = :none
|
1199
|
+
|
1200
|
+
# Output pagination links at top level
|
1201
|
+
config.top_level_links_include_pagination = true
|
1202
|
+
|
1203
|
+
config.default_page_size = 10
|
1204
|
+
config.maximum_page_size = 20
|
1205
|
+
|
1206
|
+
# Output the record count in top level meta data for find operations
|
1207
|
+
config.top_level_meta_include_record_count = false
|
1208
|
+
config.top_level_meta_record_count_key = :record_count
|
1209
|
+
|
1210
|
+
config.use_text_errors = false
|
1211
|
+
end
|
1212
|
+
```
|
1213
|
+
|
1182
1214
|
## Contributing
|
1183
1215
|
|
1184
1216
|
1. Fork it ( http://github.com/cerebris/jsonapi-resources/fork )
|
@@ -123,6 +123,10 @@ module JSONAPI
|
|
123
123
|
{}
|
124
124
|
end
|
125
125
|
|
126
|
+
def base_response_links
|
127
|
+
{}
|
128
|
+
end
|
129
|
+
|
126
130
|
def render_errors(errors)
|
127
131
|
operation_results = JSONAPI::OperationResults.new()
|
128
132
|
result = JSONAPI::ErrorsOperationResult.new(errors[0].status, errors)
|
@@ -147,7 +151,9 @@ module JSONAPI
|
|
147
151
|
key_formatter: key_formatter,
|
148
152
|
route_formatter: route_formatter,
|
149
153
|
base_meta: base_response_meta,
|
150
|
-
|
154
|
+
base_links: base_response_links,
|
155
|
+
resource_serializer_klass: resource_serializer_klass,
|
156
|
+
request: @request
|
151
157
|
}
|
152
158
|
)
|
153
159
|
end
|
@@ -13,7 +13,10 @@ module JSONAPI
|
|
13
13
|
:default_paginator,
|
14
14
|
:default_page_size,
|
15
15
|
:maximum_page_size,
|
16
|
-
:use_text_errors
|
16
|
+
:use_text_errors,
|
17
|
+
:top_level_links_include_pagination,
|
18
|
+
:top_level_meta_include_record_count,
|
19
|
+
:top_level_meta_record_count_key
|
17
20
|
|
18
21
|
def initialize
|
19
22
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
@@ -30,8 +33,17 @@ module JSONAPI
|
|
30
33
|
# :none, :offset, :paged, or a custom paginator name
|
31
34
|
self.default_paginator = :none
|
32
35
|
|
36
|
+
# Output pagination links at top level
|
37
|
+
self.top_level_links_include_pagination = true
|
38
|
+
|
33
39
|
self.default_page_size = 10
|
34
40
|
self.maximum_page_size = 20
|
41
|
+
|
42
|
+
# Metadata
|
43
|
+
# Output record count in top level meta for find operation
|
44
|
+
self.top_level_meta_include_record_count = false
|
45
|
+
self.top_level_meta_record_count_key = :record_count
|
46
|
+
|
35
47
|
self.use_text_errors = false
|
36
48
|
end
|
37
49
|
|
@@ -69,6 +81,18 @@ module JSONAPI
|
|
69
81
|
def use_text_errors=(use_text_errors)
|
70
82
|
@use_text_errors = use_text_errors
|
71
83
|
end
|
84
|
+
|
85
|
+
def top_level_links_include_pagination=(top_level_links_include_pagination)
|
86
|
+
@top_level_links_include_pagination = top_level_links_include_pagination
|
87
|
+
end
|
88
|
+
|
89
|
+
def top_level_meta_include_record_count=(top_level_meta_include_record_count)
|
90
|
+
@top_level_meta_include_record_count = top_level_meta_include_record_count
|
91
|
+
end
|
92
|
+
|
93
|
+
def top_level_meta_record_count_key=(top_level_meta_record_count_key)
|
94
|
+
@top_level_meta_record_count_key = top_level_meta_record_count_key
|
95
|
+
end
|
72
96
|
end
|
73
97
|
|
74
98
|
class << self
|
data/lib/jsonapi/operation.rb
CHANGED
@@ -23,6 +23,22 @@ module JSONAPI
|
|
23
23
|
super(resource_klass, false)
|
24
24
|
end
|
25
25
|
|
26
|
+
def record_count
|
27
|
+
@_record_count ||= @resource_klass.find_count(@resource_klass.verify_filters(@filters, @context),
|
28
|
+
context: @context,
|
29
|
+
include_directives: @include_directives)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pagination_params
|
33
|
+
if @paginator && JSONAPI.configuration.top_level_links_include_pagination
|
34
|
+
options = {}
|
35
|
+
options[:record_count] = record_count if @paginator.class.requires_record_count
|
36
|
+
return @paginator.links_page_params(options)
|
37
|
+
else
|
38
|
+
return {}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
26
42
|
def apply(context)
|
27
43
|
resource_records = @resource_klass.find(@resource_klass.verify_filters(@filters, context),
|
28
44
|
context: context,
|
@@ -30,7 +46,18 @@ module JSONAPI
|
|
30
46
|
sort_criteria: @sort_criteria,
|
31
47
|
paginator: @paginator)
|
32
48
|
|
33
|
-
|
49
|
+
options = {}
|
50
|
+
if JSONAPI.configuration.top_level_links_include_pagination
|
51
|
+
options[:pagination_params] = pagination_params
|
52
|
+
end
|
53
|
+
|
54
|
+
if JSONAPI.configuration.top_level_meta_include_record_count
|
55
|
+
options[:record_count] = record_count
|
56
|
+
end
|
57
|
+
|
58
|
+
return JSONAPI::ResourcesOperationResult.new(:ok,
|
59
|
+
resource_records,
|
60
|
+
options)
|
34
61
|
|
35
62
|
rescue JSONAPI::Exceptions::Error => e
|
36
63
|
return JSONAPI::ErrorsOperationResult.new(e.errors[0].code, e.errors)
|
@@ -2,47 +2,53 @@ module JSONAPI
|
|
2
2
|
class OperationResult
|
3
3
|
attr_accessor :code
|
4
4
|
attr_accessor :meta
|
5
|
+
attr_accessor :links
|
6
|
+
attr_accessor :options
|
5
7
|
|
6
|
-
def initialize(code)
|
8
|
+
def initialize(code, options = {})
|
7
9
|
@code = code
|
8
|
-
@
|
10
|
+
@options = options
|
11
|
+
@meta = options.fetch(:meta, {})
|
12
|
+
@links = options.fetch(:links, {})
|
9
13
|
end
|
10
14
|
end
|
11
15
|
|
12
16
|
class ErrorsOperationResult < OperationResult
|
13
17
|
attr_accessor :errors
|
14
18
|
|
15
|
-
def initialize(code, errors)
|
19
|
+
def initialize(code, errors, options = {})
|
16
20
|
@errors = errors
|
17
|
-
super(code)
|
21
|
+
super(code, options)
|
18
22
|
end
|
19
23
|
end
|
20
24
|
|
21
25
|
class ResourceOperationResult < OperationResult
|
22
26
|
attr_accessor :resource
|
23
27
|
|
24
|
-
def initialize(code, resource)
|
28
|
+
def initialize(code, resource, options = {})
|
25
29
|
@resource = resource
|
26
|
-
super(code)
|
30
|
+
super(code, options)
|
27
31
|
end
|
28
32
|
end
|
29
33
|
|
30
34
|
class ResourcesOperationResult < OperationResult
|
31
|
-
attr_accessor :resources
|
35
|
+
attr_accessor :resources, :pagination_params, :record_count
|
32
36
|
|
33
|
-
def initialize(code, resources)
|
37
|
+
def initialize(code, resources, options = {})
|
34
38
|
@resources = resources
|
35
|
-
|
39
|
+
@pagination_params = options.fetch(:pagination_params, {})
|
40
|
+
@record_count = options[:record_count]
|
41
|
+
super(code, options)
|
36
42
|
end
|
37
43
|
end
|
38
44
|
|
39
45
|
class LinksObjectOperationResult < OperationResult
|
40
46
|
attr_accessor :parent_resource, :association
|
41
47
|
|
42
|
-
def initialize(code, parent_resource, association)
|
48
|
+
def initialize(code, parent_resource, association, options = {})
|
43
49
|
@parent_resource = parent_resource
|
44
50
|
@association = association
|
45
|
-
super(code)
|
51
|
+
super(code, options)
|
46
52
|
end
|
47
53
|
end
|
48
54
|
end
|
@@ -41,16 +41,21 @@ module JSONAPI
|
|
41
41
|
|
42
42
|
run_callbacks :operations do
|
43
43
|
transaction do
|
44
|
+
# Links and meta data global to the set of operations
|
44
45
|
@operations_meta = {}
|
46
|
+
@operations_links = {}
|
45
47
|
@operations.each do |operation|
|
46
48
|
@operation = operation
|
49
|
+
# Links and meta data for each operation
|
47
50
|
@operation_meta = {}
|
51
|
+
@operation_links = {}
|
48
52
|
run_callbacks :operation do
|
49
53
|
@result = nil
|
50
54
|
run_callbacks @operation.class.name.demodulize.underscore.to_sym do
|
51
55
|
@result = process_operation(@operation)
|
52
56
|
end
|
53
57
|
@result.meta.merge!(@operation_meta)
|
58
|
+
@result.links.merge!(@operation_links)
|
54
59
|
@results.add_result(@result)
|
55
60
|
if @results.has_errors?
|
56
61
|
rollback
|
@@ -58,6 +63,7 @@ module JSONAPI
|
|
58
63
|
end
|
59
64
|
end
|
60
65
|
@results.meta = @operations_meta
|
66
|
+
@results.links = @operations_links
|
61
67
|
end
|
62
68
|
end
|
63
69
|
@results
|
data/lib/jsonapi/paginator.rb
CHANGED
@@ -7,7 +7,19 @@ module JSONAPI
|
|
7
7
|
# relation
|
8
8
|
end
|
9
9
|
|
10
|
+
def links_page_params(options = {})
|
11
|
+
# :nocov:
|
12
|
+
{}
|
13
|
+
# :nocov:
|
14
|
+
end
|
15
|
+
|
10
16
|
class << self
|
17
|
+
def requires_record_count
|
18
|
+
# :nocov:
|
19
|
+
false
|
20
|
+
# :nocov:
|
21
|
+
end
|
22
|
+
|
11
23
|
def paginator_for(paginator)
|
12
24
|
paginator_class_name = "#{paginator.to_s.camelize}Paginator"
|
13
25
|
paginator_class_name.safe_constantize if paginator_class_name
|
@@ -17,15 +29,62 @@ module JSONAPI
|
|
17
29
|
end
|
18
30
|
|
19
31
|
class OffsetPaginator < JSONAPI::Paginator
|
32
|
+
attr_reader :limit, :offset
|
33
|
+
|
20
34
|
def initialize(params)
|
21
35
|
parse_pagination_params(params)
|
22
36
|
verify_pagination_params
|
23
37
|
end
|
24
38
|
|
39
|
+
def self.requires_record_count
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
25
43
|
def apply(relation, order_options)
|
26
44
|
relation.offset(@offset).limit(@limit)
|
27
45
|
end
|
28
46
|
|
47
|
+
def links_page_params(options = {})
|
48
|
+
record_count = options[:record_count]
|
49
|
+
links_page_params = {}
|
50
|
+
|
51
|
+
links_page_params['first'] = {
|
52
|
+
'offset' => 0,
|
53
|
+
'limit' => @limit
|
54
|
+
}
|
55
|
+
|
56
|
+
if @offset > 0
|
57
|
+
previous_offset = @offset - @limit
|
58
|
+
|
59
|
+
if previous_offset < 0
|
60
|
+
previous_offset = 0
|
61
|
+
end
|
62
|
+
|
63
|
+
links_page_params['previous'] = {
|
64
|
+
'offset' => previous_offset,
|
65
|
+
'limit' => @limit
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
next_offset = @offset + @limit
|
70
|
+
|
71
|
+
unless next_offset >= record_count
|
72
|
+
links_page_params['next'] = {
|
73
|
+
'offset' => next_offset,
|
74
|
+
'limit'=> @limit
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
if record_count
|
79
|
+
links_page_params['last'] = {
|
80
|
+
'offset' => record_count - @limit,
|
81
|
+
'limit' => @limit
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
links_page_params
|
86
|
+
end
|
87
|
+
|
29
88
|
private
|
30
89
|
def parse_pagination_params(params)
|
31
90
|
if params.nil?
|
@@ -58,16 +117,57 @@ class OffsetPaginator < JSONAPI::Paginator
|
|
58
117
|
end
|
59
118
|
|
60
119
|
class PagedPaginator < JSONAPI::Paginator
|
120
|
+
attr_reader :size, :number
|
121
|
+
|
61
122
|
def initialize(params)
|
62
123
|
parse_pagination_params(params)
|
63
124
|
verify_pagination_params
|
64
125
|
end
|
65
126
|
|
127
|
+
def self.requires_record_count
|
128
|
+
true
|
129
|
+
end
|
130
|
+
|
66
131
|
def apply(relation, order_options)
|
67
132
|
offset = (@number - 1) * @size
|
68
133
|
relation.offset(offset).limit(@size)
|
69
134
|
end
|
70
135
|
|
136
|
+
def links_page_params(options = {})
|
137
|
+
record_count = options[:record_count]
|
138
|
+
page_count = (record_count / @size.to_f).ceil
|
139
|
+
|
140
|
+
links_page_params = {}
|
141
|
+
|
142
|
+
links_page_params['first'] = {
|
143
|
+
'number' => 1,
|
144
|
+
'size' => @size
|
145
|
+
}
|
146
|
+
|
147
|
+
if @number > 1
|
148
|
+
links_page_params['previous'] = {
|
149
|
+
'number' => @number - 1,
|
150
|
+
'size' => @size
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
unless @number >= page_count
|
155
|
+
links_page_params['next'] = {
|
156
|
+
'number' => @number + 1,
|
157
|
+
'size'=> @size
|
158
|
+
}
|
159
|
+
end
|
160
|
+
|
161
|
+
if record_count
|
162
|
+
links_page_params['last'] = {
|
163
|
+
'number' => page_count,
|
164
|
+
'size' => @size
|
165
|
+
}
|
166
|
+
end
|
167
|
+
|
168
|
+
links_page_params
|
169
|
+
end
|
170
|
+
|
71
171
|
private
|
72
172
|
def parse_pagination_params(params)
|
73
173
|
if params.nil?
|
data/lib/jsonapi/request.rb
CHANGED
@@ -5,9 +5,10 @@ module JSONAPI
|
|
5
5
|
class Request
|
6
6
|
attr_accessor :fields, :include, :filters, :sort_criteria, :errors, :operations,
|
7
7
|
:resource_klass, :context, :paginator, :source_klass, :source_id,
|
8
|
-
:include_directives
|
8
|
+
:include_directives, :params
|
9
9
|
|
10
10
|
def initialize(params = nil, options = {})
|
11
|
+
@params = params
|
11
12
|
@context = options[:context]
|
12
13
|
@key_formatter = options.fetch(:key_formatter, JSONAPI.configuration.key_formatter)
|
13
14
|
@errors = []
|
@@ -21,7 +22,7 @@ module JSONAPI
|
|
21
22
|
@paginator = nil
|
22
23
|
@id = nil
|
23
24
|
|
24
|
-
setup_action(params)
|
25
|
+
setup_action(@params)
|
25
26
|
end
|
26
27
|
|
27
28
|
def setup_action(params)
|
@@ -67,6 +67,10 @@ module JSONAPI
|
|
67
67
|
}
|
68
68
|
end
|
69
69
|
|
70
|
+
def find_link(query_params)
|
71
|
+
"#{@base_url}/#{formatted_module_path_from_klass(@primary_resource_klass)}#{@route_formatter.format(@primary_resource_klass._type.to_s)}?#{query_params.to_query}"
|
72
|
+
end
|
73
|
+
|
70
74
|
private
|
71
75
|
# Process the primary source object(s). This will then serialize associated object recursively based on the
|
72
76
|
# requested includes. Fields are controlled fields option for each resource type, such
|
@@ -200,7 +204,11 @@ module JSONAPI
|
|
200
204
|
end
|
201
205
|
|
202
206
|
def formatted_module_path(source)
|
203
|
-
source.class
|
207
|
+
formatted_module_path_from_klass(source.class)
|
208
|
+
end
|
209
|
+
|
210
|
+
def formatted_module_path_from_klass(klass)
|
211
|
+
klass.name =~ /::[^:]+\Z/ ? (@route_formatter.format($`).freeze.gsub('::', '/') + '/').downcase : ''
|
204
212
|
end
|
205
213
|
|
206
214
|
def self_href(source)
|
@@ -13,6 +13,9 @@ module JSONAPI
|
|
13
13
|
meta = top_level_meta
|
14
14
|
hash.merge!(meta: meta) unless meta.empty?
|
15
15
|
|
16
|
+
links = top_level_links
|
17
|
+
hash.merge!(links: links) unless links.empty?
|
18
|
+
|
16
19
|
hash
|
17
20
|
end
|
18
21
|
|
@@ -37,6 +40,8 @@ module JSONAPI
|
|
37
40
|
)
|
38
41
|
end
|
39
42
|
|
43
|
+
# Rolls up the top level meta data from the base_meta, the set of operations,
|
44
|
+
# and the result of each operation. The keys are then formatted.
|
40
45
|
def top_level_meta
|
41
46
|
meta = @options.fetch(:base_meta, {})
|
42
47
|
|
@@ -44,11 +49,45 @@ module JSONAPI
|
|
44
49
|
|
45
50
|
@operation_results.results.each do |result|
|
46
51
|
meta.merge!(result.meta)
|
52
|
+
|
53
|
+
if JSONAPI.configuration.top_level_meta_include_record_count
|
54
|
+
meta[JSONAPI.configuration.top_level_meta_record_count_key] = result.record_count
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
meta.deep_transform_keys { |key| @key_formatter.format(key) }
|
50
59
|
end
|
51
60
|
|
61
|
+
# Rolls up the top level links from the base_links, the set of operations,
|
62
|
+
# and the result of each operation. The keys are then formatted.
|
63
|
+
def top_level_links
|
64
|
+
links = @options.fetch(:base_links, {})
|
65
|
+
|
66
|
+
links.merge!(@operation_results.links)
|
67
|
+
|
68
|
+
@operation_results.results.each do |result|
|
69
|
+
links.merge!(result.links)
|
70
|
+
|
71
|
+
# Build pagination links
|
72
|
+
if result.is_a?(JSONAPI::ResourcesOperationResult)
|
73
|
+
result.pagination_params.each_pair do |link_name, params|
|
74
|
+
query_params = {}
|
75
|
+
query_params[:page] = params
|
76
|
+
|
77
|
+
request = @options[:request]
|
78
|
+
query_params[:fields] = request.params[:fields] if request.params[:fields]
|
79
|
+
query_params[:include] = request.params[:include] if request.params[:include]
|
80
|
+
query_params[:sort] = request.params[:sort] if request.params[:sort]
|
81
|
+
query_params[:filter] = request.params[:filter] if request.params[:filter]
|
82
|
+
|
83
|
+
links[link_name] = serializer.find_link(query_params)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
links.deep_transform_keys { |key| @key_formatter.format(key) }
|
89
|
+
end
|
90
|
+
|
52
91
|
def results_to_hash
|
53
92
|
if @operation_results.has_errors?
|
54
93
|
{errors: @operation_results.all_errors}
|
data/lib/jsonapi/routing_ext.rb
CHANGED
@@ -26,6 +26,13 @@ module ActionDispatch
|
|
26
26
|
options.merge!(res.routing_resource_options)
|
27
27
|
options[:path] = format_route(@resource_type)
|
28
28
|
|
29
|
+
if options[:except]
|
30
|
+
options[:except] << :new unless options[:except].include?(:new) || options[:except].include?('new')
|
31
|
+
options[:except] << :edit unless options[:except].include?(:edit) || options[:except].include?('edit')
|
32
|
+
else
|
33
|
+
options[:except] = [:new, :edit]
|
34
|
+
end
|
35
|
+
|
29
36
|
resource @resource_type, options do
|
30
37
|
@scope[:jsonapi_resource] = @resource_type
|
31
38
|
|
@@ -37,15 +44,15 @@ module ActionDispatch
|
|
37
44
|
end
|
38
45
|
end
|
39
46
|
|
40
|
-
def jsonapi_relationships
|
47
|
+
def jsonapi_relationships(options = {})
|
41
48
|
res = JSONAPI::Resource.resource_for(resource_type_with_module_prefix(@resource_type))
|
42
49
|
res._associations.each do |association_name, association|
|
43
50
|
if association.is_a?(JSONAPI::Association::HasMany)
|
44
|
-
jsonapi_links(association_name)
|
45
|
-
jsonapi_related_resources(association_name)
|
51
|
+
jsonapi_links(association_name, options)
|
52
|
+
jsonapi_related_resources(association_name, options)
|
46
53
|
else
|
47
|
-
jsonapi_link(association_name)
|
48
|
-
jsonapi_related_resource(association_name)
|
54
|
+
jsonapi_link(association_name, options)
|
55
|
+
jsonapi_related_resource(association_name, options)
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
@@ -62,6 +69,13 @@ module ActionDispatch
|
|
62
69
|
|
63
70
|
options[:path] = format_route(@resource_type)
|
64
71
|
|
72
|
+
if options[:except]
|
73
|
+
options[:except] << :new unless options[:except].include?(:new) || options[:except].include?('new')
|
74
|
+
options[:except] << :edit unless options[:except].include?(:edit) || options[:except].include?('edit')
|
75
|
+
else
|
76
|
+
options[:except] = [:new, :edit]
|
77
|
+
end
|
78
|
+
|
65
79
|
resources @resource_type, options do
|
66
80
|
@scope[:jsonapi_resource] = @resource_type
|
67
81
|
|
@@ -2148,6 +2148,33 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
|
|
2148
2148
|
assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
|
2149
2149
|
end
|
2150
2150
|
|
2151
|
+
def test_books_record_count_in_meta
|
2152
|
+
Api::V2::BookResource.paginator :offset
|
2153
|
+
JSONAPI.configuration.top_level_meta_include_record_count = true
|
2154
|
+
get :index, {include: 'book-comments'}
|
2155
|
+
JSONAPI.configuration.top_level_meta_include_record_count = false
|
2156
|
+
|
2157
|
+
assert_response :success
|
2158
|
+
assert_equal 1000, json_response['meta']['record-count']
|
2159
|
+
assert_equal 10, json_response['data'].size
|
2160
|
+
assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
|
2161
|
+
end
|
2162
|
+
|
2163
|
+
def test_books_record_count_in_meta_custom_name
|
2164
|
+
Api::V2::BookResource.paginator :offset
|
2165
|
+
JSONAPI.configuration.top_level_meta_include_record_count = true
|
2166
|
+
JSONAPI.configuration.top_level_meta_record_count_key = 'total_records'
|
2167
|
+
|
2168
|
+
get :index, {include: 'book-comments'}
|
2169
|
+
JSONAPI.configuration.top_level_meta_include_record_count = false
|
2170
|
+
JSONAPI.configuration.top_level_meta_record_count_key = :record_count
|
2171
|
+
|
2172
|
+
assert_response :success
|
2173
|
+
assert_equal 1000, json_response['meta']['total-records']
|
2174
|
+
assert_equal 10, json_response['data'].size
|
2175
|
+
assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
|
2176
|
+
end
|
2177
|
+
|
2151
2178
|
def test_books_offset_pagination_no_params_includes_query_count_one_level
|
2152
2179
|
Api::V2::BookResource.paginator :offset
|
2153
2180
|
|
@@ -2157,7 +2184,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
|
|
2157
2184
|
assert_response :success
|
2158
2185
|
assert_equal 10, json_response['data'].size
|
2159
2186
|
assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
|
2160
|
-
assert_equal
|
2187
|
+
assert_equal 3, query_count
|
2161
2188
|
end
|
2162
2189
|
|
2163
2190
|
def test_books_offset_pagination_no_params_includes_query_count_two_levels
|
@@ -2169,7 +2196,7 @@ class Api::V2::BooksControllerTest < ActionController::TestCase
|
|
2169
2196
|
assert_response :success
|
2170
2197
|
assert_equal 10, json_response['data'].size
|
2171
2198
|
assert_equal 'Book 0', json_response['data'][0]['attributes']['title']
|
2172
|
-
assert_equal
|
2199
|
+
assert_equal 4, query_count
|
2173
2200
|
end
|
2174
2201
|
|
2175
2202
|
def test_books_offset_pagination
|
@@ -2305,6 +2332,18 @@ class Api::V4::BooksControllerTest < ActionController::TestCase
|
|
2305
2332
|
assert_equal 1000, json_response['meta']['totalRecords']
|
2306
2333
|
JSONAPI.configuration.operations_processor = :active_record
|
2307
2334
|
end
|
2335
|
+
|
2336
|
+
def test_books_operation_links
|
2337
|
+
JSONAPI.configuration.operations_processor = :counting_active_record
|
2338
|
+
Api::V4::BookResource.paginator :offset
|
2339
|
+
get :index, {page: {offset: 50, limit: 12}}
|
2340
|
+
assert_response :success
|
2341
|
+
assert_equal 12, json_response['data'].size
|
2342
|
+
assert_equal 'Book 50', json_response['data'][0]['attributes']['title']
|
2343
|
+
assert_equal 5, json_response['links'].size
|
2344
|
+
assert_equal 'https://test_corp.com', json_response['links']['spec']
|
2345
|
+
JSONAPI.configuration.operations_processor = :active_record
|
2346
|
+
end
|
2308
2347
|
end
|
2309
2348
|
|
2310
2349
|
class CategoriesControllerTest < ActionController::TestCase
|
@@ -353,13 +353,8 @@ $breed_data.add(Breed.new(3, 'to_delete'))
|
|
353
353
|
### OperationsProcessor
|
354
354
|
class CountingActiveRecordOperationsProcessor < ActiveRecordOperationsProcessor
|
355
355
|
after_find_operation do
|
356
|
-
|
357
|
-
|
358
|
-
context: @context,
|
359
|
-
include_directives: @operation.include_directives,
|
360
|
-
sort_criteria: @operation.sort_criteria)
|
361
|
-
|
362
|
-
@operation_meta[:total_records] = count
|
356
|
+
@operation_meta[:total_records] = @operation.record_count
|
357
|
+
@operation_links['spec'] = 'https://test_corp.com'
|
363
358
|
end
|
364
359
|
end
|
365
360
|
|
data/test/test_helper.rb
CHANGED
@@ -0,0 +1,206 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
require 'jsonapi-resources'
|
3
|
+
|
4
|
+
class OffsetPaginatorTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
def test_offset_default_page_params
|
7
|
+
params = ActionController::Parameters.new(
|
8
|
+
{
|
9
|
+
}
|
10
|
+
)
|
11
|
+
|
12
|
+
paginator = OffsetPaginator.new(params)
|
13
|
+
|
14
|
+
assert_equal JSONAPI.configuration.default_page_size, paginator.limit
|
15
|
+
assert_equal 0, paginator.offset
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_offset_parse_page_params_default_offset
|
19
|
+
params = ActionController::Parameters.new(
|
20
|
+
{
|
21
|
+
limit: 20
|
22
|
+
}
|
23
|
+
)
|
24
|
+
|
25
|
+
paginator = OffsetPaginator.new(params)
|
26
|
+
|
27
|
+
assert_equal 20, paginator.limit
|
28
|
+
assert_equal 0, paginator.offset
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_offset_parse_page_params
|
32
|
+
params = ActionController::Parameters.new(
|
33
|
+
{
|
34
|
+
limit: 5,
|
35
|
+
offset: 7
|
36
|
+
}
|
37
|
+
)
|
38
|
+
|
39
|
+
paginator = OffsetPaginator.new(params)
|
40
|
+
|
41
|
+
assert_equal 5, paginator.limit
|
42
|
+
assert_equal 7, paginator.offset
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_offset_parse_page_params_limit_too_large
|
46
|
+
params = ActionController::Parameters.new(
|
47
|
+
{
|
48
|
+
limit: 50,
|
49
|
+
offset: 0
|
50
|
+
}
|
51
|
+
)
|
52
|
+
|
53
|
+
assert_raises JSONAPI::Exceptions::InvalidPageValue do
|
54
|
+
OffsetPaginator.new(params)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_offset_parse_page_params_not_allowed
|
59
|
+
params = ActionController::Parameters.new(
|
60
|
+
{
|
61
|
+
limit: 50,
|
62
|
+
start: 0
|
63
|
+
}
|
64
|
+
)
|
65
|
+
|
66
|
+
assert_raises JSONAPI::Exceptions::PageParametersNotAllowed do
|
67
|
+
OffsetPaginator.new(params)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_offset_parse_page_params_start
|
72
|
+
params = ActionController::Parameters.new(
|
73
|
+
{
|
74
|
+
limit: 5,
|
75
|
+
offset: 0
|
76
|
+
}
|
77
|
+
)
|
78
|
+
|
79
|
+
paginator = OffsetPaginator.new(params)
|
80
|
+
|
81
|
+
assert_equal 5, paginator.limit
|
82
|
+
assert_equal 0, paginator.offset
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def test_offset_links_page_params_large_data_set_start
|
87
|
+
params = ActionController::Parameters.new(
|
88
|
+
{
|
89
|
+
limit: 5,
|
90
|
+
offset: 0
|
91
|
+
}
|
92
|
+
)
|
93
|
+
|
94
|
+
paginator = OffsetPaginator.new(params)
|
95
|
+
links_params = paginator.links_page_params(record_count: 50)
|
96
|
+
|
97
|
+
assert_equal 3, links_params.size
|
98
|
+
|
99
|
+
assert_equal 5, links_params['first']['limit']
|
100
|
+
assert_equal 0, links_params['first']['offset']
|
101
|
+
|
102
|
+
assert_equal 5, links_params['next']['limit']
|
103
|
+
assert_equal 5, links_params['next']['offset']
|
104
|
+
|
105
|
+
assert_equal 5, links_params['last']['limit']
|
106
|
+
assert_equal 45, links_params['last']['offset']
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_offset_links_page_params_large_data_set_before_start
|
110
|
+
params = ActionController::Parameters.new(
|
111
|
+
{
|
112
|
+
limit: 5,
|
113
|
+
offset: 2
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
paginator = OffsetPaginator.new(params)
|
118
|
+
links_params = paginator.links_page_params(record_count: 50)
|
119
|
+
|
120
|
+
assert_equal 4, links_params.size
|
121
|
+
|
122
|
+
assert_equal 5, links_params['first']['limit']
|
123
|
+
assert_equal 0, links_params['first']['offset']
|
124
|
+
|
125
|
+
assert_equal 5, links_params['previous']['limit']
|
126
|
+
assert_equal 0, links_params['previous']['offset']
|
127
|
+
|
128
|
+
assert_equal 5, links_params['next']['limit']
|
129
|
+
assert_equal 7, links_params['next']['offset']
|
130
|
+
|
131
|
+
assert_equal 5, links_params['last']['limit']
|
132
|
+
assert_equal 45, links_params['last']['offset']
|
133
|
+
end
|
134
|
+
|
135
|
+
def test_offset_links_page_params_large_data_set_middle
|
136
|
+
params = ActionController::Parameters.new(
|
137
|
+
{
|
138
|
+
limit: 5,
|
139
|
+
offset: 27
|
140
|
+
}
|
141
|
+
)
|
142
|
+
|
143
|
+
paginator = OffsetPaginator.new(params)
|
144
|
+
links_params = paginator.links_page_params(record_count: 50)
|
145
|
+
|
146
|
+
assert_equal 4, links_params.size
|
147
|
+
|
148
|
+
assert_equal 5, links_params['first']['limit']
|
149
|
+
assert_equal 0, links_params['first']['offset']
|
150
|
+
|
151
|
+
assert_equal 5, links_params['previous']['limit']
|
152
|
+
assert_equal 22, links_params['previous']['offset']
|
153
|
+
|
154
|
+
assert_equal 5, links_params['next']['limit']
|
155
|
+
assert_equal 32, links_params['next']['offset']
|
156
|
+
|
157
|
+
assert_equal 5, links_params['last']['limit']
|
158
|
+
assert_equal 45, links_params['last']['offset']
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_offset_links_page_params_large_data_set_end
|
162
|
+
params = ActionController::Parameters.new(
|
163
|
+
{
|
164
|
+
limit: 5,
|
165
|
+
offset: 45
|
166
|
+
}
|
167
|
+
)
|
168
|
+
|
169
|
+
paginator = OffsetPaginator.new(params)
|
170
|
+
links_params = paginator.links_page_params(record_count: 50)
|
171
|
+
|
172
|
+
assert_equal 3, links_params.size
|
173
|
+
|
174
|
+
assert_equal 5, links_params['first']['limit']
|
175
|
+
assert_equal 0, links_params['first']['offset']
|
176
|
+
|
177
|
+
assert_equal 5, links_params['previous']['limit']
|
178
|
+
assert_equal 40, links_params['previous']['offset']
|
179
|
+
|
180
|
+
assert_equal 5, links_params['last']['limit']
|
181
|
+
assert_equal 45, links_params['last']['offset']
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_offset_links_page_params_large_data_set_past_end
|
185
|
+
params = ActionController::Parameters.new(
|
186
|
+
{
|
187
|
+
limit: 5,
|
188
|
+
offset: 48
|
189
|
+
}
|
190
|
+
)
|
191
|
+
|
192
|
+
paginator = OffsetPaginator.new(params)
|
193
|
+
links_params = paginator.links_page_params(record_count: 50)
|
194
|
+
|
195
|
+
assert_equal 3, links_params.size
|
196
|
+
|
197
|
+
assert_equal 5, links_params['first']['limit']
|
198
|
+
assert_equal 0, links_params['first']['offset']
|
199
|
+
|
200
|
+
assert_equal 5, links_params['previous']['limit']
|
201
|
+
assert_equal 43, links_params['previous']['offset']
|
202
|
+
|
203
|
+
assert_equal 5, links_params['last']['limit']
|
204
|
+
assert_equal 45, links_params['last']['offset']
|
205
|
+
end
|
206
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
require 'jsonapi-resources'
|
3
|
+
|
4
|
+
class PagedPaginatorTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
def test_paged_default_page_params
|
7
|
+
params = ActionController::Parameters.new(
|
8
|
+
{
|
9
|
+
}
|
10
|
+
)
|
11
|
+
|
12
|
+
paginator = PagedPaginator.new(params)
|
13
|
+
|
14
|
+
assert_equal JSONAPI.configuration.default_page_size, paginator.size
|
15
|
+
assert_equal 1, paginator.number
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_paged_parse_page_params_default_page
|
19
|
+
params = ActionController::Parameters.new(
|
20
|
+
{
|
21
|
+
size: 20
|
22
|
+
}
|
23
|
+
)
|
24
|
+
|
25
|
+
paginator = PagedPaginator.new(params)
|
26
|
+
|
27
|
+
assert_equal 20, paginator.size
|
28
|
+
assert_equal 1, paginator.number
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_paged_parse_page_params
|
32
|
+
params = ActionController::Parameters.new(
|
33
|
+
{
|
34
|
+
size: 5,
|
35
|
+
number: 7
|
36
|
+
}
|
37
|
+
)
|
38
|
+
|
39
|
+
paginator = PagedPaginator.new(params)
|
40
|
+
|
41
|
+
assert_equal 5, paginator.size
|
42
|
+
assert_equal 7, paginator.number
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_paged_parse_page_params_size_too_large
|
46
|
+
params = ActionController::Parameters.new(
|
47
|
+
{
|
48
|
+
size: 50,
|
49
|
+
number: 1
|
50
|
+
}
|
51
|
+
)
|
52
|
+
|
53
|
+
assert_raises JSONAPI::Exceptions::InvalidPageValue do
|
54
|
+
PagedPaginator.new(params)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_paged_parse_page_params_not_allowed
|
59
|
+
params = ActionController::Parameters.new(
|
60
|
+
{
|
61
|
+
size: 50,
|
62
|
+
start: 1
|
63
|
+
}
|
64
|
+
)
|
65
|
+
|
66
|
+
assert_raises JSONAPI::Exceptions::PageParametersNotAllowed do
|
67
|
+
PagedPaginator.new(params)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_paged_parse_page_params_start
|
72
|
+
params = ActionController::Parameters.new(
|
73
|
+
{
|
74
|
+
size: 5,
|
75
|
+
number: 1
|
76
|
+
}
|
77
|
+
)
|
78
|
+
|
79
|
+
paginator = PagedPaginator.new(params)
|
80
|
+
|
81
|
+
assert_equal 5, paginator.size
|
82
|
+
assert_equal 1, paginator.number
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def test_paged_links_page_params_large_data_set_start_full_pages
|
87
|
+
params = ActionController::Parameters.new(
|
88
|
+
{
|
89
|
+
size: 5,
|
90
|
+
number: 1
|
91
|
+
}
|
92
|
+
)
|
93
|
+
|
94
|
+
paginator = PagedPaginator.new(params)
|
95
|
+
links_params = paginator.links_page_params(record_count: 50)
|
96
|
+
|
97
|
+
assert_equal 3, links_params.size
|
98
|
+
|
99
|
+
assert_equal 5, links_params['first']['size']
|
100
|
+
assert_equal 1, links_params['first']['number']
|
101
|
+
|
102
|
+
assert_equal 5, links_params['next']['size']
|
103
|
+
assert_equal 2, links_params['next']['number']
|
104
|
+
|
105
|
+
assert_equal 5, links_params['last']['size']
|
106
|
+
assert_equal 10, links_params['last']['number']
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_paged_links_page_params_large_data_set_start_partial_last
|
110
|
+
params = ActionController::Parameters.new(
|
111
|
+
{
|
112
|
+
size: 5,
|
113
|
+
number: 1
|
114
|
+
}
|
115
|
+
)
|
116
|
+
|
117
|
+
paginator = PagedPaginator.new(params)
|
118
|
+
links_params = paginator.links_page_params(record_count: 51)
|
119
|
+
|
120
|
+
assert_equal 3, links_params.size
|
121
|
+
|
122
|
+
assert_equal 5, links_params['first']['size']
|
123
|
+
assert_equal 1, links_params['first']['number']
|
124
|
+
|
125
|
+
assert_equal 5, links_params['next']['size']
|
126
|
+
assert_equal 2, links_params['next']['number']
|
127
|
+
|
128
|
+
assert_equal 5, links_params['last']['size']
|
129
|
+
assert_equal 11, links_params['last']['number']
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_paged_links_page_params_large_data_set_middle
|
133
|
+
params = ActionController::Parameters.new(
|
134
|
+
{
|
135
|
+
size: 5,
|
136
|
+
number: 4
|
137
|
+
}
|
138
|
+
)
|
139
|
+
|
140
|
+
paginator = PagedPaginator.new(params)
|
141
|
+
links_params = paginator.links_page_params(record_count: 50)
|
142
|
+
|
143
|
+
assert_equal 4, links_params.size
|
144
|
+
|
145
|
+
assert_equal 5, links_params['first']['size']
|
146
|
+
assert_equal 1, links_params['first']['number']
|
147
|
+
|
148
|
+
assert_equal 5, links_params['previous']['size']
|
149
|
+
assert_equal 3, links_params['previous']['number']
|
150
|
+
|
151
|
+
assert_equal 5, links_params['next']['size']
|
152
|
+
assert_equal 5, links_params['next']['number']
|
153
|
+
|
154
|
+
assert_equal 5, links_params['last']['size']
|
155
|
+
assert_equal 10, links_params['last']['number']
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_paged_links_page_params_large_data_set_end
|
159
|
+
params = ActionController::Parameters.new(
|
160
|
+
{
|
161
|
+
size: 5,
|
162
|
+
number: 10
|
163
|
+
}
|
164
|
+
)
|
165
|
+
|
166
|
+
paginator = PagedPaginator.new(params)
|
167
|
+
links_params = paginator.links_page_params(record_count: 50)
|
168
|
+
|
169
|
+
assert_equal 3, links_params.size
|
170
|
+
|
171
|
+
assert_equal 5, links_params['first']['size']
|
172
|
+
assert_equal 1, links_params['first']['number']
|
173
|
+
|
174
|
+
assert_equal 5, links_params['previous']['size']
|
175
|
+
assert_equal 9, links_params['previous']['number']
|
176
|
+
|
177
|
+
assert_equal 5, links_params['last']['size']
|
178
|
+
assert_equal 10, links_params['last']['number']
|
179
|
+
end
|
180
|
+
|
181
|
+
def test_paged_links_page_params_large_data_set_past_end
|
182
|
+
params = ActionController::Parameters.new(
|
183
|
+
{
|
184
|
+
size: 5,
|
185
|
+
number: 11
|
186
|
+
}
|
187
|
+
)
|
188
|
+
|
189
|
+
paginator = PagedPaginator.new(params)
|
190
|
+
links_params = paginator.links_page_params(record_count: 50)
|
191
|
+
|
192
|
+
assert_equal 3, links_params.size
|
193
|
+
|
194
|
+
assert_equal 5, links_params['first']['size']
|
195
|
+
assert_equal 1, links_params['first']['number']
|
196
|
+
|
197
|
+
assert_equal 5, links_params['previous']['size']
|
198
|
+
assert_equal 10, links_params['previous']['number']
|
199
|
+
|
200
|
+
assert_equal 5, links_params['last']['size']
|
201
|
+
assert_equal 10, links_params['last']['number']
|
202
|
+
end
|
203
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-resources
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Gebhardt
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-06-
|
12
|
+
date: 2015-06-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -194,6 +194,8 @@ files:
|
|
194
194
|
- test/test_helper.rb
|
195
195
|
- test/unit/jsonapi_request/jsonapi_request_test.rb
|
196
196
|
- test/unit/operation/operations_processor_test.rb
|
197
|
+
- test/unit/pagination/offset_paginator_test.rb
|
198
|
+
- test/unit/pagination/paged_paginator_test.rb
|
197
199
|
- test/unit/resource/resource_test.rb
|
198
200
|
- test/unit/serializer/include_directives_test.rb
|
199
201
|
- test/unit/serializer/response_document_test.rb
|
@@ -254,6 +256,8 @@ test_files:
|
|
254
256
|
- test/test_helper.rb
|
255
257
|
- test/unit/jsonapi_request/jsonapi_request_test.rb
|
256
258
|
- test/unit/operation/operations_processor_test.rb
|
259
|
+
- test/unit/pagination/offset_paginator_test.rb
|
260
|
+
- test/unit/pagination/paged_paginator_test.rb
|
257
261
|
- test/unit/resource/resource_test.rb
|
258
262
|
- test/unit/serializer/include_directives_test.rb
|
259
263
|
- test/unit/serializer/response_document_test.rb
|