Skip to content

Commit 5b4b14d

Browse files
committed
Merge pull request #83 from cerebris/jsonapi_rc2
Jsonapi rc2
2 parents a888b57 + c203907 commit 5b4b14d

21 files changed

Lines changed: 2802 additions & 1242 deletions

README.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,70 @@ class AuthorResource < JSONAPI::Resource
330330
end
331331
```
332332

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+
333397
#### Callbacks
334398

335399
`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
517581
INVALID_INCLUDE = 112
518582
RELATION_EXISTS = 113
519583
INVALID_SORT_PARAM = 114
520-
584+
INVALID_LINKS_OBJECT = 115
585+
TYPE_MISMATCH = 116
586+
INVALID_PAGE_OBJECT = 117
587+
INVALID_PAGE_VALUE = 118
521588
RECORD_NOT_FOUND = 404
522589
LOCKED = 423
523590
end

Rakefile

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
11
#!/usr/bin/env rake
22
require "bundler/gem_tasks"
33
require "rake/testtask"
4+
require './test/test_helper.rb'
45

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
117

128
task default: :test
139

@@ -21,4 +17,4 @@ namespace :test do
2117
sh cmd.join(' ')
2218
end
2319
end
24-
end
20+
end

lib/jsonapi-resources.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'jsonapi/resource'
22
require 'jsonapi/resources/version'
33
require 'jsonapi/configuration'
4+
require 'jsonapi/paginator'
45
require 'jsonapi/formatter'
56
require 'jsonapi/routing_ext'
67
require 'jsonapi/mime_types'

lib/jsonapi/configuration.rb

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,29 @@
22

33
module JSONAPI
44
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
613

714
def initialize
815
#:underscored_key, :camelized_key, :dasherized_key, or custom
9-
self.json_key_format = :underscored_key
16+
self.json_key_format = :dasherized_key
1017

1118
#:underscored_route, :camelized_route, :dasherized_route, or custom
12-
self.route_format = :underscored_route
19+
self.route_format = :dasherized_route
1320

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
1528
end
1629

1730
def json_key_format=(format)
@@ -27,6 +40,18 @@ def route_format=(format)
2740
def allowed_request_params=(allowed_request_params)
2841
@allowed_request_params = allowed_request_params
2942
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
3055
end
3156

3257
class << self

lib/jsonapi/error_codes.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ module JSONAPI
1212
KEY_NOT_INCLUDED_IN_URL = 110
1313
INVALID_INCLUDE = 112
1414
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
1721
RECORD_NOT_FOUND = 404
1822
UNSUPPORTED_MEDIA_TYPE = 415
1923
LOCKED = 423

lib/jsonapi/exceptions.rb

Lines changed: 92 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,18 +58,6 @@ def errors
5858
end
5959
end
6060

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-
7361
class FilterNotAllowed < Error
7462
attr_accessor :filter
7563
def initialize(filter)
@@ -114,6 +102,34 @@ def errors
114102
end
115103
end
116104

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+
117133
class InvalidField < Error
118134
attr_accessor :field, :type
119135
def initialize(type, field)
@@ -144,18 +160,33 @@ def errors
144160
end
145161
end
146162

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)
150166
@resource = resource
151-
@sort_param = sort_param
167+
@sort_criteria = sort_criteria
152168
end
153169

154170
def errors
155-
[JSONAPI::Error.new(code: JSONAPI::INVALID_SORT_PARAM,
171+
[JSONAPI::Error.new(code: JSONAPI::INVALID_SORT_CRITERIA,
156172
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 -)")]
159190
end
160191
end
161192

@@ -257,5 +288,47 @@ def errors
257288
end
258289
end
259290

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
260333
end
261334
end

lib/jsonapi/operation.rb

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -68,24 +68,6 @@ def apply(context)
6868
end
6969
end
7070

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-
8971
class ReplaceHasOneAssociationOperation < Operation
9072
attr_reader :resource_id, :association_type, :key_value
9173

0 commit comments

Comments
 (0)