jsonapi-resources 0.1.1 → 0.2.0

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