jsonapi-resources 0.4.0 → 0.4.1
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 +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
|