jsonapi-utils 0.4.4 → 0.4.5
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 +28 -20
- data/lib/jsonapi/utils.rb +6 -248
- data/lib/jsonapi/utils/exceptions.rb +44 -17
- data/lib/jsonapi/utils/request.rb +19 -0
- data/lib/jsonapi/utils/response.rb +13 -0
- data/lib/jsonapi/utils/response/formatters.rb +89 -0
- data/lib/jsonapi/utils/response/renders.rb +41 -0
- data/lib/jsonapi/utils/response/support.rb +25 -0
- data/lib/jsonapi/utils/support/error.rb +25 -0
- data/lib/jsonapi/utils/support/filter.rb +29 -0
- data/lib/jsonapi/utils/support/pagination.rb +75 -0
- data/lib/jsonapi/utils/support/sort.rb +35 -0
- data/lib/jsonapi/utils/version.rb +1 -1
- metadata +11 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ec04f95554ca59d1117ffa0452646252a5a156c5
|
4
|
+
data.tar.gz: 897fd3e12eceed6338421e6e5b7166afd8279947
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2aa0e5fc2e3c030898bd2287804bb62752523f6f9e6aad6a01c14954d9d8a09dee587117a54cd7463cb9cefedeb5ca2d91bb00548e703b5a99f57eb15e91a97b
|
7
|
+
data.tar.gz: 1136e92931251cce1ad8f652d56ee7555d0da2cbb93c3c4b44b7d33053b9460357b23bf84a9b178cb702e73942d92d9688e61d10ad0cd5e4e4bd80994e2d0a35
|
data/README.md
CHANGED
@@ -13,7 +13,7 @@ JSONAPI::Utils (JU) was built on top of [JSONAPI::Resources](https://github.com/
|
|
13
13
|
Add these lines to your application's Gemfile:
|
14
14
|
|
15
15
|
```ruby
|
16
|
-
gem 'jsonapi-utils', '~> 0.4.
|
16
|
+
gem 'jsonapi-utils', '~> 0.4.4'
|
17
17
|
```
|
18
18
|
|
19
19
|
And then execute:
|
@@ -22,11 +22,23 @@ And then execute:
|
|
22
22
|
$ bundle
|
23
23
|
```
|
24
24
|
|
25
|
-
## Macros
|
25
|
+
## Response Macros
|
26
26
|
|
27
27
|
### jsonapi_render
|
28
28
|
|
29
|
-
|
29
|
+
Takes ActiveRecord/Hash objects and generates JSON API-compliant responses.
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
# GET /users
|
33
|
+
def index
|
34
|
+
jsonapi_render json: User.all
|
35
|
+
end
|
36
|
+
|
37
|
+
# GET /users/:id
|
38
|
+
def show
|
39
|
+
jsonapi_render json: User.find(params[:id])
|
40
|
+
end
|
41
|
+
```
|
30
42
|
|
31
43
|
Arguments:
|
32
44
|
|
@@ -37,13 +49,9 @@ Arguments:
|
|
37
49
|
- `count`: explicitly points the total count of records for the request in order to build a proper pagination. By default, JU will count the total number of records.
|
38
50
|
- `model`: sets the model reference in cases when `json` is a Hash or a collection of Hashes.
|
39
51
|
|
40
|
-
|
41
|
-
# Single resource rendering
|
42
|
-
jsonapi_render json: User.find(params[:id])
|
43
|
-
|
44
|
-
# Collection rendering
|
45
|
-
jsonapi_render json: User.all
|
52
|
+
Examples:
|
46
53
|
|
54
|
+
```ruby
|
47
55
|
# Specify a particular HTTP status code
|
48
56
|
jsonapi_render json: new_user, status: :created
|
49
57
|
|
@@ -60,17 +68,21 @@ jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model:
|
|
60
68
|
jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }
|
61
69
|
```
|
62
70
|
|
63
|
-
###
|
71
|
+
### jsonapi_format
|
64
72
|
|
65
|
-
In the backstage
|
73
|
+
In the backstage this is the method that actually parses ActiveRecord/Hash objects and builds a new Hash compliant with JSON API. It can be called anywhere in your controllers being very useful whenever you need to work with a JSON API "serialized" version of your object before rendering it.
|
74
|
+
|
75
|
+
Note: because of semantic reasons `JSONAPI::Utils#jsonapi_serialize` was renamed being now just an alias to `JSONAPI::Utils#jsonapi_format`.
|
66
76
|
|
67
77
|
```ruby
|
68
|
-
|
69
|
-
|
78
|
+
def index
|
79
|
+
result = do_some_magic(jsonapi_format(User.all))
|
80
|
+
render json: result
|
81
|
+
end
|
70
82
|
```
|
71
83
|
|
72
84
|
Arguments:
|
73
|
-
- It receives
|
85
|
+
- It receives the same options as `jsonapi_render`.
|
74
86
|
|
75
87
|
## Usage
|
76
88
|
|
@@ -218,12 +230,8 @@ Content-Type: application/vnd.api+json
|
|
218
230
|
{
|
219
231
|
"title": "Record not found",
|
220
232
|
"detail": "The record identified by 3 could not be found.",
|
221
|
-
"
|
222
|
-
"
|
223
|
-
"code": 404,
|
224
|
-
"source": null,
|
225
|
-
"links": null,
|
226
|
-
"status": "not_found"
|
233
|
+
"code": "404",
|
234
|
+
"status": "404"
|
227
235
|
}
|
228
236
|
]
|
229
237
|
}
|
data/lib/jsonapi/utils.rb
CHANGED
@@ -1,260 +1,18 @@
|
|
1
1
|
require 'jsonapi-resources'
|
2
2
|
require 'jsonapi/utils/version'
|
3
3
|
require 'jsonapi/utils/exceptions'
|
4
|
+
require 'jsonapi/utils/request'
|
5
|
+
require 'jsonapi/utils/response'
|
4
6
|
|
5
7
|
module JSONAPI
|
6
8
|
module Utils
|
9
|
+
include JSONAPI::Utils::Request
|
10
|
+
include JSONAPI::Utils::Response
|
11
|
+
|
7
12
|
def self.included(base)
|
8
|
-
if base.respond_to?(:
|
13
|
+
if base.respond_to?(:before_action)
|
9
14
|
base.before_action :setup_request, :check_request
|
10
|
-
base.helper_method :jsonapi_render, :jsonapi_serialize
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def jsonapi_render(json:, status: nil, options: {})
|
15
|
-
body = jsonapi_serialize(json, options)
|
16
|
-
render json: body, status: status || @_response_document.status
|
17
|
-
rescue => e
|
18
|
-
handle_exceptions(e)
|
19
|
-
ensure
|
20
|
-
if response.body.size > 0
|
21
|
-
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def jsonapi_render_errors(exception)
|
26
|
-
result = jsonapi_format_errors(exception)
|
27
|
-
errors = result.errors
|
28
|
-
render json: { errors: errors }, status: errors.first.status
|
29
|
-
end
|
30
|
-
|
31
|
-
def jsonapi_format_errors(exception)
|
32
|
-
JSONAPI::ErrorsOperationResult.new(exception.errors[0].code, exception.errors)
|
33
|
-
end
|
34
|
-
|
35
|
-
def jsonapi_render_internal_server_error
|
36
|
-
jsonapi_render_errors(::JSONAPI::Utils::Exceptions::InternalServerError.new)
|
37
|
-
end
|
38
|
-
|
39
|
-
def jsonapi_render_bad_request
|
40
|
-
jsonapi_render_errors(::JSONAPI::Utils::Exceptions::BadRequest.new)
|
41
|
-
end
|
42
|
-
|
43
|
-
def jsonapi_render_not_found(exception)
|
44
|
-
id = exception.message.match(/=([\w-]+)/).try(:[], 1) || '(no identifier)'
|
45
|
-
jsonapi_render_errors(JSONAPI::Exceptions::RecordNotFound.new(id))
|
46
|
-
end
|
47
|
-
|
48
|
-
def jsonapi_render_not_found_with_null
|
49
|
-
render json: { data: nil }, status: 200
|
50
|
-
end
|
51
|
-
|
52
|
-
def jsonapi_serialize(records, options = {})
|
53
|
-
if records.is_a?(Hash)
|
54
|
-
hash = records.with_indifferent_access
|
55
|
-
records = hash_to_active_record(hash[:data], options[:model])
|
56
|
-
end
|
57
|
-
|
58
|
-
fix_request_options(params, records)
|
59
|
-
build_response_document(records, options).contents
|
60
|
-
end
|
61
|
-
|
62
|
-
def filter_params
|
63
|
-
@_filter_params ||=
|
64
|
-
if params[:filter].is_a?(Hash)
|
65
|
-
params[:filter].keys.each_with_object({}) do |resource, hash|
|
66
|
-
hash[resource] = params[:filter][resource]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def sort_params
|
72
|
-
@_sort_params ||=
|
73
|
-
if params[:sort].present?
|
74
|
-
params[:sort].split(',').each_with_object({}) do |criteria, hash|
|
75
|
-
order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
|
76
|
-
hash[field] = order == '-' ? :desc : :asc
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
private
|
82
|
-
|
83
|
-
def build_response_document(records, options)
|
84
|
-
results = JSONAPI::OperationResults.new
|
85
|
-
|
86
|
-
if records.respond_to?(:to_ary)
|
87
|
-
@_records = build_collection(records, options)
|
88
|
-
results.add_result(JSONAPI::ResourcesOperationResult.new(:ok, @_records, result_options(records, options)))
|
89
|
-
else
|
90
|
-
@_record = turn_into_resource(records, options)
|
91
|
-
results.add_result(JSONAPI::ResourceOperationResult.new(:ok, @_record))
|
92
|
-
end
|
93
|
-
|
94
|
-
@_response_document = create_response_document(results)
|
95
|
-
end
|
96
|
-
|
97
|
-
|
98
|
-
def fix_request_options(params, records)
|
99
|
-
return if request.method !~ /get/i ||
|
100
|
-
params.nil? ||
|
101
|
-
%w(index show create update destroy).include?(params[:action])
|
102
|
-
action = records.respond_to?(:to_ary) ? 'index' : 'show'
|
103
|
-
@request.send("setup_#{action}_action", params)
|
104
|
-
end
|
105
|
-
|
106
|
-
def result_options(records, options)
|
107
|
-
{}.tap do |data|
|
108
|
-
if JSONAPI.configuration.default_paginator != :none &&
|
109
|
-
JSONAPI.configuration.top_level_links_include_pagination
|
110
|
-
data[:pagination_params] = pagination_params(records, options)
|
111
|
-
end
|
112
|
-
|
113
|
-
if JSONAPI.configuration.top_level_meta_include_record_count
|
114
|
-
data[:record_count] = count_records(records, options)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
def pagination_params(records, options)
|
120
|
-
@paginator ||= paginator(params)
|
121
|
-
if @paginator && JSONAPI.configuration.top_level_links_include_pagination
|
122
|
-
options = {}
|
123
|
-
@paginator.class.requires_record_count &&
|
124
|
-
options[:record_count] = count_records(records, options)
|
125
|
-
@paginator.links_page_params(options)
|
126
|
-
else
|
127
|
-
{}
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def paginator(params)
|
132
|
-
page_params = ActionController::Parameters.new(params[:page])
|
133
|
-
|
134
|
-
@paginator ||=
|
135
|
-
if JSONAPI.configuration.default_paginator == :paged
|
136
|
-
PagedPaginator.new(page_params)
|
137
|
-
elsif JSONAPI.configuration.default_paginator == :offset
|
138
|
-
OffsetPaginator.new(page_params)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def build_collection(records, options = {})
|
143
|
-
records = apply_filter(records, options)
|
144
|
-
records = apply_pagination(records, options)
|
145
|
-
records = apply_sort(records)
|
146
|
-
records.respond_to?(:to_ary) ? records.map { |record| turn_into_resource(record, options) } : []
|
147
|
-
end
|
148
|
-
|
149
|
-
def turn_into_resource(record, options = {})
|
150
|
-
if options[:resource]
|
151
|
-
options[:resource].to_s.constantize.new(record, context)
|
152
|
-
else
|
153
|
-
@request.resource_klass.new(record, context)
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
|
-
def apply_filter(records, options = {})
|
158
|
-
if apply_filter?(records, options)
|
159
|
-
records.where(filter_params)
|
160
|
-
else
|
161
|
-
records
|
162
15
|
end
|
163
16
|
end
|
164
|
-
|
165
|
-
def apply_filter?(records, options = {})
|
166
|
-
params[:filter].present? && records.respond_to?(:where) &&
|
167
|
-
(options[:filter].nil? || options[:filter])
|
168
|
-
end
|
169
|
-
|
170
|
-
def apply_pagination(records, options = {})
|
171
|
-
return records unless apply_pagination?(options)
|
172
|
-
pagination = set_pagination(options)
|
173
|
-
|
174
|
-
records =
|
175
|
-
if records.is_a?(Array)
|
176
|
-
records[pagination[:range]]
|
177
|
-
else
|
178
|
-
pagination[:paginator].apply(records, nil)
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
def apply_sort(records)
|
183
|
-
return records unless params[:sort].present?
|
184
|
-
|
185
|
-
if records.is_a?(Array)
|
186
|
-
records.sort { |a, b| comp = 0; eval(sort_criteria) }
|
187
|
-
elsif records.respond_to?(:order)
|
188
|
-
records.order(sort_params)
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
def sort_criteria
|
193
|
-
sort_params.reduce('') do |sum, hash|
|
194
|
-
foo = ["a[:#{hash[0]}]", "b[:#{hash[0]}]"]
|
195
|
-
foo.reverse! if hash[1] == :desc
|
196
|
-
sum + "comp = comp == 0 ? #{foo.join(' <=> ')} : comp; "
|
197
|
-
end
|
198
|
-
end
|
199
|
-
|
200
|
-
def set_pagination(options)
|
201
|
-
page_params = ActionController::Parameters.new(@request.params[:page])
|
202
|
-
if JSONAPI.configuration.default_paginator == :paged
|
203
|
-
@_paginator ||= PagedPaginator.new(page_params)
|
204
|
-
number = page_params['number'].to_i.nonzero? || 1
|
205
|
-
size = page_params['size'].to_i.nonzero? || JSONAPI.configuration.default_page_size
|
206
|
-
{ paginator: @_paginator, range: (number - 1) * size..number * size - 1 }
|
207
|
-
elsif JSONAPI.configuration.default_paginator == :offset
|
208
|
-
@_paginator ||= OffsetPaginator.new(page_params)
|
209
|
-
offset = page_params['offset'].to_i.nonzero? || 0
|
210
|
-
limit = page_params['limit'].to_i.nonzero? || JSONAPI.configuration.default_page_size
|
211
|
-
{ paginator: @_paginator, range: offset..offset + limit - 1 }
|
212
|
-
else
|
213
|
-
{}
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
def apply_pagination?(options)
|
218
|
-
JSONAPI.configuration.default_paginator != :none &&
|
219
|
-
(options[:paginate].nil? || options[:paginate])
|
220
|
-
end
|
221
|
-
|
222
|
-
def hash_to_active_record(data, model)
|
223
|
-
return data if model.nil?
|
224
|
-
coerced = [data].flatten.map { |hash| model.new(hash) }
|
225
|
-
data.is_a?(Array) ? coerced : coerced.first
|
226
|
-
rescue ActiveRecord::UnknownAttributeError
|
227
|
-
if data.is_a?(Array)
|
228
|
-
ids = data.map { |e| e[:id] }
|
229
|
-
model.where(id: ids)
|
230
|
-
else
|
231
|
-
model.find_by(id: data[:id])
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
def count_records(records, options)
|
236
|
-
if options[:count].present?
|
237
|
-
options[:count]
|
238
|
-
elsif records.is_a?(Array)
|
239
|
-
records.length
|
240
|
-
else
|
241
|
-
records = apply_filter(records, options) if params[:filter].present?
|
242
|
-
records.except(:group, :order).count("DISTINCT #{records.table.name}.id")
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def setup_request
|
247
|
-
@request ||=
|
248
|
-
JSONAPI::Request.new(
|
249
|
-
params,
|
250
|
-
context: context,
|
251
|
-
key_formatter: key_formatter,
|
252
|
-
server_error_callbacks: (self.class.server_error_callbacks || [])
|
253
|
-
)
|
254
|
-
end
|
255
|
-
|
256
|
-
def check_request
|
257
|
-
@request.errors.blank? || render_errors(@request.errors)
|
258
|
-
end
|
259
17
|
end
|
260
18
|
end
|
@@ -1,25 +1,52 @@
|
|
1
1
|
require 'jsonapi/utils/version'
|
2
2
|
|
3
|
-
module JSONAPI
|
4
|
-
|
5
|
-
|
3
|
+
module JSONAPI
|
4
|
+
module Utils
|
5
|
+
module Exceptions
|
6
|
+
class ActiveRecord < ::JSONAPI::Exceptions::Error
|
7
|
+
attr_accessor :object
|
6
8
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
9
|
+
def initialize(object)
|
10
|
+
@object = object
|
11
|
+
end
|
12
|
+
|
13
|
+
def errors
|
14
|
+
object.errors.keys.map do |key|
|
15
|
+
JSONAPI::Error.new(
|
16
|
+
code: JSONAPI::VALIDATION_ERROR,
|
17
|
+
status: :unprocessable_entity,
|
18
|
+
id: key,
|
19
|
+
title: object.errors.full_messages_for(key).first
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class BadRequest < ::JSONAPI::Exceptions::Error
|
26
|
+
def code; '400' end
|
27
|
+
|
28
|
+
def errors
|
29
|
+
[JSONAPI::Error.new(
|
30
|
+
code: code,
|
31
|
+
status: :bad_request,
|
32
|
+
title: 'Bad Request',
|
33
|
+
detail: 'This request is not supported.'
|
34
|
+
)]
|
35
|
+
end
|
36
|
+
end
|
14
37
|
|
15
|
-
|
16
|
-
|
38
|
+
class InternalServerError < ::JSONAPI::Exceptions::Error
|
39
|
+
def code; '500' end
|
17
40
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
41
|
+
def errors
|
42
|
+
[JSONAPI::Error.new(
|
43
|
+
code: code,
|
44
|
+
status: :internal_server_error,
|
45
|
+
title: 'Internal Server Error',
|
46
|
+
detail: 'An internal error ocurred while processing the request.'
|
47
|
+
)]
|
48
|
+
end
|
49
|
+
end
|
23
50
|
end
|
24
51
|
end
|
25
52
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Request
|
4
|
+
def setup_request
|
5
|
+
@request ||=
|
6
|
+
JSONAPI::Request.new(
|
7
|
+
params,
|
8
|
+
context: context,
|
9
|
+
key_formatter: key_formatter,
|
10
|
+
server_error_callbacks: (self.class.server_error_callbacks || [])
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_request
|
15
|
+
@request.errors.blank? || jsonapi_render_errors(json: @request)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'jsonapi/utils/response/formatters'
|
2
|
+
require 'jsonapi/utils/response/renders'
|
3
|
+
require 'jsonapi/utils/response/support'
|
4
|
+
|
5
|
+
module JSONAPI
|
6
|
+
module Utils
|
7
|
+
module Response
|
8
|
+
include Renders
|
9
|
+
include Formatters
|
10
|
+
include Support
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Response
|
4
|
+
module Formatters
|
5
|
+
def jsonapi_format(records, options = {})
|
6
|
+
if records.is_a?(Hash)
|
7
|
+
hash = records.with_indifferent_access
|
8
|
+
records = hash_to_active_record(hash[:data], options[:model])
|
9
|
+
end
|
10
|
+
fix_request_options(params, records)
|
11
|
+
build_response_document(records, options).contents
|
12
|
+
end
|
13
|
+
|
14
|
+
alias_method :jsonapi_serialize, :jsonapi_format
|
15
|
+
|
16
|
+
def jsonapi_format_errors(data)
|
17
|
+
data = JSONAPI::Utils::Exceptions::ActiveRecord.new(data) if data.is_a?(ActiveRecord::Base)
|
18
|
+
errors = data.respond_to?(:errors) ? data.errors : data
|
19
|
+
JSONAPI::Utils::Support::Error.sanitize(errors).uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def build_response_document(records, options)
|
25
|
+
results = JSONAPI::OperationResults.new
|
26
|
+
|
27
|
+
if records.respond_to?(:to_ary)
|
28
|
+
@_records = build_collection(records, options)
|
29
|
+
results.add_result(JSONAPI::ResourcesOperationResult.new(:ok, @_records, result_options(records, options)))
|
30
|
+
else
|
31
|
+
@_record = turn_into_resource(records, options)
|
32
|
+
results.add_result(JSONAPI::ResourceOperationResult.new(:ok, @_record))
|
33
|
+
end
|
34
|
+
|
35
|
+
@_response_document = create_response_document(results)
|
36
|
+
end
|
37
|
+
|
38
|
+
def fix_request_options(params, records)
|
39
|
+
return if request.method !~ /get/i ||
|
40
|
+
params.nil? ||
|
41
|
+
%w(index show create update destroy).include?(params[:action])
|
42
|
+
action = records.respond_to?(:to_ary) ? 'index' : 'show'
|
43
|
+
@request.send("setup_#{action}_action", params)
|
44
|
+
end
|
45
|
+
|
46
|
+
def result_options(records, options)
|
47
|
+
{}.tap do |data|
|
48
|
+
if JSONAPI.configuration.default_paginator != :none &&
|
49
|
+
JSONAPI.configuration.top_level_links_include_pagination
|
50
|
+
data[:pagination_params] = pagination_params(records, options)
|
51
|
+
end
|
52
|
+
|
53
|
+
if JSONAPI.configuration.top_level_meta_include_record_count
|
54
|
+
data[:record_count] = count_records(records, options)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def build_collection(records, options = {})
|
60
|
+
records = apply_filter(records, options)
|
61
|
+
records = apply_pagination(records, options)
|
62
|
+
records = apply_sort(records)
|
63
|
+
records.respond_to?(:to_ary) ? records.map { |record| turn_into_resource(record, options) } : []
|
64
|
+
end
|
65
|
+
|
66
|
+
def turn_into_resource(record, options = {})
|
67
|
+
if options[:resource]
|
68
|
+
options[:resource].to_s.constantize.new(record, context)
|
69
|
+
else
|
70
|
+
@request.resource_klass.new(record, context)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def hash_to_active_record(data, model)
|
75
|
+
return data if model.nil?
|
76
|
+
coerced = [data].flatten.map { |hash| model.new(hash) }
|
77
|
+
data.is_a?(Array) ? coerced : coerced.first
|
78
|
+
rescue ActiveRecord::UnknownAttributeError
|
79
|
+
if data.is_a?(Array)
|
80
|
+
ids = data.map { |e| e[:id] }
|
81
|
+
model.where(id: ids)
|
82
|
+
else
|
83
|
+
model.find_by(id: data[:id])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Response
|
4
|
+
module Renders
|
5
|
+
def jsonapi_render(json:, status: nil, options: {})
|
6
|
+
body = jsonapi_format(json, options)
|
7
|
+
render json: body, status: status || @_response_document.status
|
8
|
+
rescue => e
|
9
|
+
handle_exceptions(e)
|
10
|
+
ensure
|
11
|
+
correct_media_type
|
12
|
+
end
|
13
|
+
|
14
|
+
def jsonapi_render_errors(exception = nil, json: nil, status: nil)
|
15
|
+
body = jsonapi_format_errors(exception || json)
|
16
|
+
status = status || body.try(:first).try(:[], :status)
|
17
|
+
render json: { errors: body }, status: status
|
18
|
+
ensure
|
19
|
+
correct_media_type
|
20
|
+
end
|
21
|
+
|
22
|
+
def jsonapi_render_internal_server_error
|
23
|
+
jsonapi_render_errors(::JSONAPI::Utils::Exceptions::InternalServerError.new)
|
24
|
+
end
|
25
|
+
|
26
|
+
def jsonapi_render_bad_request
|
27
|
+
jsonapi_render_errors(::JSONAPI::Utils::Exceptions::BadRequest.new)
|
28
|
+
end
|
29
|
+
|
30
|
+
def jsonapi_render_not_found(exception)
|
31
|
+
id = exception.message.match(/=([\w-]+)/).try(:[], 1) || '(no identifier)'
|
32
|
+
jsonapi_render_errors(JSONAPI::Exceptions::RecordNotFound.new(id))
|
33
|
+
end
|
34
|
+
|
35
|
+
def jsonapi_render_not_found_with_null
|
36
|
+
render json: { data: nil }, status: 200
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'jsonapi/utils/support/filter'
|
2
|
+
require 'jsonapi/utils/support/pagination'
|
3
|
+
require 'jsonapi/utils/support/sort'
|
4
|
+
require 'jsonapi/utils/support/error'
|
5
|
+
|
6
|
+
module JSONAPI
|
7
|
+
module Utils
|
8
|
+
module Response
|
9
|
+
module Support
|
10
|
+
include ::JSONAPI::Utils::Support::Error
|
11
|
+
include ::JSONAPI::Utils::Support::Filter
|
12
|
+
include ::JSONAPI::Utils::Support::Pagination
|
13
|
+
include ::JSONAPI::Utils::Support::Sort
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def correct_media_type
|
18
|
+
if response.body.size > 0
|
19
|
+
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Support
|
4
|
+
module Error
|
5
|
+
MEMBERS = %i(title detail id code source links status meta)
|
6
|
+
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def sanitize(errors)
|
10
|
+
Array(errors).map do |error|
|
11
|
+
MEMBERS.reduce({}) do |sum, key|
|
12
|
+
value = error.try(key) || error.try(:[], key)
|
13
|
+
if value.nil?
|
14
|
+
sum
|
15
|
+
else
|
16
|
+
value = value.to_s if key == :code
|
17
|
+
sum.merge(key => value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Support
|
4
|
+
module Filter
|
5
|
+
def apply_filter(records, options = {})
|
6
|
+
if apply_filter?(records, options)
|
7
|
+
records.where(filter_params)
|
8
|
+
else
|
9
|
+
records
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def apply_filter?(records, options = {})
|
14
|
+
params[:filter].present? && records.respond_to?(:where) &&
|
15
|
+
(options[:filter].nil? || options[:filter])
|
16
|
+
end
|
17
|
+
|
18
|
+
def filter_params
|
19
|
+
@_filter_params ||=
|
20
|
+
if params[:filter].is_a?(Hash)
|
21
|
+
params[:filter].keys.each_with_object({}) do |resource, hash|
|
22
|
+
hash[resource] = params[:filter][resource]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Support
|
4
|
+
module Pagination
|
5
|
+
def apply_pagination(records, options = {})
|
6
|
+
return records unless apply_pagination?(options)
|
7
|
+
pagination = set_pagination(options)
|
8
|
+
|
9
|
+
records =
|
10
|
+
if records.is_a?(Array)
|
11
|
+
records[pagination[:range]]
|
12
|
+
else
|
13
|
+
pagination[:paginator].apply(records, nil)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def apply_pagination?(options)
|
18
|
+
JSONAPI.configuration.default_paginator != :none &&
|
19
|
+
(options[:paginate].nil? || options[:paginate])
|
20
|
+
end
|
21
|
+
|
22
|
+
def pagination_params(records, options)
|
23
|
+
@paginator ||= paginator(params)
|
24
|
+
if @paginator && JSONAPI.configuration.top_level_links_include_pagination
|
25
|
+
options = {}
|
26
|
+
@paginator.class.requires_record_count &&
|
27
|
+
options[:record_count] = count_records(records, options)
|
28
|
+
@paginator.links_page_params(options)
|
29
|
+
else
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def paginator(params)
|
35
|
+
page_params = ActionController::Parameters.new(params[:page])
|
36
|
+
|
37
|
+
@paginator ||=
|
38
|
+
if JSONAPI.configuration.default_paginator == :paged
|
39
|
+
PagedPaginator.new(page_params)
|
40
|
+
elsif JSONAPI.configuration.default_paginator == :offset
|
41
|
+
OffsetPaginator.new(page_params)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def set_pagination(options)
|
46
|
+
page_params = ActionController::Parameters.new(@request.params[:page])
|
47
|
+
if JSONAPI.configuration.default_paginator == :paged
|
48
|
+
@_paginator ||= PagedPaginator.new(page_params)
|
49
|
+
number = page_params['number'].to_i.nonzero? || 1
|
50
|
+
size = page_params['size'].to_i.nonzero? || JSONAPI.configuration.default_page_size
|
51
|
+
{ paginator: @_paginator, range: (number - 1) * size..number * size - 1 }
|
52
|
+
elsif JSONAPI.configuration.default_paginator == :offset
|
53
|
+
@_paginator ||= OffsetPaginator.new(page_params)
|
54
|
+
offset = page_params['offset'].to_i.nonzero? || 0
|
55
|
+
limit = page_params['limit'].to_i.nonzero? || JSONAPI.configuration.default_page_size
|
56
|
+
{ paginator: @_paginator, range: offset..offset + limit - 1 }
|
57
|
+
else
|
58
|
+
{}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def count_records(records, options)
|
63
|
+
if options[:count].present?
|
64
|
+
options[:count]
|
65
|
+
elsif records.is_a?(Array)
|
66
|
+
records.length
|
67
|
+
else
|
68
|
+
records = apply_filter(records, options) if params[:filter].present?
|
69
|
+
records.except(:group, :order).count("DISTINCT #{records.table.name}.id")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module JSONAPI
|
2
|
+
module Utils
|
3
|
+
module Support
|
4
|
+
module Sort
|
5
|
+
def apply_sort(records)
|
6
|
+
return records unless params[:sort].present?
|
7
|
+
|
8
|
+
if records.is_a?(Array)
|
9
|
+
records.sort { |a, b| comp = 0; eval(sort_criteria) }
|
10
|
+
elsif records.respond_to?(:order)
|
11
|
+
records.order(sort_params)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def sort_criteria
|
16
|
+
sort_params.reduce('') do |sum, hash|
|
17
|
+
foo = ["a[:#{hash[0]}]", "b[:#{hash[0]}]"]
|
18
|
+
foo.reverse! if hash[1] == :desc
|
19
|
+
sum + "comp = comp == 0 ? #{foo.join(' <=> ')} : comp; "
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def sort_params
|
24
|
+
@_sort_params ||=
|
25
|
+
if params[:sort].present?
|
26
|
+
params[:sort].split(',').each_with_object({}) do |criteria, hash|
|
27
|
+
order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
|
28
|
+
hash[field] = order == '-' ? :desc : :asc
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jsonapi-utils
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Guedes
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-
|
12
|
+
date: 2016-06-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: jsonapi-resources
|
@@ -139,6 +139,15 @@ files:
|
|
139
139
|
- bin/setup
|
140
140
|
- lib/jsonapi/utils.rb
|
141
141
|
- lib/jsonapi/utils/exceptions.rb
|
142
|
+
- lib/jsonapi/utils/request.rb
|
143
|
+
- lib/jsonapi/utils/response.rb
|
144
|
+
- lib/jsonapi/utils/response/formatters.rb
|
145
|
+
- lib/jsonapi/utils/response/renders.rb
|
146
|
+
- lib/jsonapi/utils/response/support.rb
|
147
|
+
- lib/jsonapi/utils/support/error.rb
|
148
|
+
- lib/jsonapi/utils/support/filter.rb
|
149
|
+
- lib/jsonapi/utils/support/pagination.rb
|
150
|
+
- lib/jsonapi/utils/support/sort.rb
|
142
151
|
- lib/jsonapi/utils/version.rb
|
143
152
|
homepage: https://github.com/b2beauty/jsonapi-utils
|
144
153
|
licenses:
|