jsonapi-utils 0.4.5 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +165 -77
- data/lib/jsonapi/utils.rb +3 -3
- data/lib/jsonapi/utils/request.rb +31 -1
- data/lib/jsonapi/utils/response/formatters.rb +1 -1
- data/lib/jsonapi/utils/response/support.rb +1 -1
- data/lib/jsonapi/utils/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c328df382d804f5ae2d625216ac2febd26b952ac
|
4
|
+
data.tar.gz: b13829c6b72e5d4a8fb76fcafcac900916c5d8f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 60a974e53b5e40f500bc8a99cba456d21ca7c9c960ff040e91622f35bf40dcae9d6101fa38e93491d79fa96f7ca6e362d0d3857329c1841a06b541903bea2f27
|
7
|
+
data.tar.gz: fc039cfdecd100db6b4d316e5b232734e9b96ec9e77ad8578398e9a542a17828a233a58ee39ea28fbfe673f50f43de783b3fec5fe350145022749662d7c408bf
|
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.5'
|
17
17
|
```
|
18
18
|
|
19
19
|
And then execute:
|
@@ -22,13 +22,29 @@ And then execute:
|
|
22
22
|
$ bundle
|
23
23
|
```
|
24
24
|
|
25
|
-
##
|
25
|
+
## How does it work?
|
26
26
|
|
27
|
-
|
27
|
+
One of the main motivations behind `JSONAPI::Utils` is to keep things explicit in your controller actions so that developers can easily understand and maintain code. With this principle in mind, JU doesn't care about your controller operations and it deals only with the request and response layers thus letting the developer decides how to actually operate the actions (service objects, interactors or something).
|
28
28
|
|
29
|
-
|
29
|
+
In both layers (request and response) JU communicates with some `JSONAPI::Resources`' objects in order to validate requests and render responses properly.
|
30
|
+
|
31
|
+
## Usage
|
32
|
+
|
33
|
+
### Response
|
34
|
+
|
35
|
+
#### Renders
|
36
|
+
|
37
|
+
JU brings two main renders to the game, working pretty much the same way as Rails' `ActionController#render` method:
|
38
|
+
|
39
|
+
- jsonapi_render
|
40
|
+
- jsonapi_render_errors
|
41
|
+
|
42
|
+
**jsonapi_render**
|
43
|
+
|
44
|
+
It takes the arguments and generates a JSON API-compliant response.
|
30
45
|
|
31
46
|
```ruby
|
47
|
+
# app/controllers/users_controller.rb
|
32
48
|
# GET /users
|
33
49
|
def index
|
34
50
|
jsonapi_render json: User.all
|
@@ -42,14 +58,14 @@ end
|
|
42
58
|
|
43
59
|
Arguments:
|
44
60
|
|
45
|
-
- `json`:
|
61
|
+
- `json`: object to be rendered as a JSON document: ActiveRecord object, Hash or Array of Hashes;
|
46
62
|
- `status`: HTTP status code (Integer or Symbol). If ommited a status code will be automatically infered;
|
47
63
|
- `options`:
|
48
64
|
- `resource`: explicitly points the resource to be used in the serialization. By default, JU will select resources by inferencing from controller's name.
|
49
65
|
- `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.
|
50
66
|
- `model`: sets the model reference in cases when `json` is a Hash or a collection of Hashes.
|
51
67
|
|
52
|
-
|
68
|
+
Other examples:
|
53
69
|
|
54
70
|
```ruby
|
55
71
|
# Specify a particular HTTP status code
|
@@ -68,25 +84,103 @@ jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model:
|
|
68
84
|
jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }
|
69
85
|
```
|
70
86
|
|
71
|
-
|
87
|
+
**jsonapi_render_errors**
|
72
88
|
|
73
|
-
|
89
|
+
It takes arguments and generates a JSON API-compliant error response.
|
74
90
|
|
75
|
-
|
91
|
+
```ruby
|
92
|
+
# app/controllers/users_controller.rb
|
93
|
+
# POST /users
|
94
|
+
def create
|
95
|
+
user = User.new(user_params)
|
96
|
+
if user.save
|
97
|
+
jsonapi_render json: user, status: :created
|
98
|
+
else
|
99
|
+
jsonapi_render_errors json: user, status: :unprocessable_entity
|
100
|
+
end
|
101
|
+
end
|
102
|
+
```
|
103
|
+
|
104
|
+
Arguments:
|
105
|
+
- Exception
|
106
|
+
- `json`: object to be rendered as a JSON document: ActiveRecord, Exception, Array of Hashes or any object which implements the `errors` method;
|
107
|
+
- `status`: HTTP status code (Integer or Symbol). If ommited a status code will be automatically infered from the error body.
|
108
|
+
|
109
|
+
Other examples:
|
76
110
|
|
77
111
|
```ruby
|
112
|
+
# Render errors from a custom exception:
|
113
|
+
jsonapi_render_errors Exceptions::MyCustomError.new(user)
|
114
|
+
|
115
|
+
# Render errors from an Array of Hashes:
|
116
|
+
errors = [{ id: 'validation', title: 'Something went wrong', code: '100' }]
|
117
|
+
jsonapi_render_errors json: errors, status: :unprocessable_entity
|
118
|
+
```
|
119
|
+
|
120
|
+
#### Formatters
|
121
|
+
|
122
|
+
In the backstage those are the guys which actually parse ActiveRecord/Hash objects and build a new Hash compliant with JSON API. They can be called anywhere in controllers being very useful if you need to generate the response body and do some work with it before actually rendering the response.
|
123
|
+
|
124
|
+
Note: the resulting Hash from those methods can not be passed as argument to `JSONAPI::Utils#jsonapi_render` or `JSONAPI::Utils#jsonapi_render_error`, instead it needs to be rendered by the usual `ActionController#render`.
|
125
|
+
|
126
|
+
**jsonapi_format**
|
127
|
+
|
128
|
+
*Because of semantic reasons `JSONAPI::Utils#jsonapi_serialize` was renamed being now just an alias to `JSONAPI::Utils#jsonapi_format`.*
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
# app/controllers/users_controller.rb
|
78
132
|
def index
|
79
|
-
|
80
|
-
render json:
|
133
|
+
body = jsonapi_format(User.all)
|
134
|
+
render json: do_some_magic_with(body)
|
81
135
|
end
|
82
136
|
```
|
83
137
|
|
84
138
|
Arguments:
|
85
|
-
-
|
139
|
+
- First: ActiveRecord object, Hash or Array of Hashes;
|
140
|
+
- Last: Hash of options (same as `JSONAPI::Utils#jsonapi_render`).
|
86
141
|
|
87
|
-
|
142
|
+
### Request
|
143
|
+
|
144
|
+
Before your controller's action gets executed JU will validate request against JSON API specifications and eventual query string params against the resource's definitions. If something goes wrong with the request JU will render an error response like this:
|
145
|
+
|
146
|
+
```json
|
147
|
+
HTTP/1.1 400 Bad Request
|
148
|
+
Content-Type: application/vnd.api+json
|
149
|
+
|
150
|
+
{
|
151
|
+
"errors": [
|
152
|
+
{
|
153
|
+
"title": "Invalid resource",
|
154
|
+
"detail": "foo is not a valid resource.",
|
155
|
+
"code": "101",
|
156
|
+
"status": "400"
|
157
|
+
},
|
158
|
+
{
|
159
|
+
"title": "Invalid resource",
|
160
|
+
"detail": "foobar is not a valid resource.",
|
161
|
+
"code": "101",
|
162
|
+
"status": "400"
|
163
|
+
},
|
164
|
+
{
|
165
|
+
"title": "Invalid field",
|
166
|
+
"detail": "bar is not a valid relationship of users",
|
167
|
+
"code": "112",
|
168
|
+
"status": "400"
|
169
|
+
}
|
170
|
+
]
|
171
|
+
}
|
172
|
+
```
|
173
|
+
|
174
|
+
## Full example
|
175
|
+
|
176
|
+
In order to start working with JU after installing the gem you simply need to do the following:
|
88
177
|
|
89
|
-
|
178
|
+
1. Include the gem (`include JSONAPI::Utils`) in the target controller or in a `BaseController`;
|
179
|
+
2. Define the resources for your models;
|
180
|
+
3. Define routes;
|
181
|
+
4. Use JU's render methods.
|
182
|
+
|
183
|
+
Time for a full example, let's say we have a Rails application for a super simple blog:
|
90
184
|
|
91
185
|
### Models
|
92
186
|
|
@@ -141,7 +235,7 @@ Rails.application.routes.draw do
|
|
141
235
|
end
|
142
236
|
```
|
143
237
|
|
144
|
-
|
238
|
+
In our base controller we need to include the `JSONAPI::Utils` module and define some default rendering:
|
145
239
|
|
146
240
|
```ruby
|
147
241
|
# app/controllers/base_controller.rb
|
@@ -152,107 +246,101 @@ class BaseController < JSONAPI::ResourceController
|
|
152
246
|
end
|
153
247
|
```
|
154
248
|
|
155
|
-
|
156
|
-
in order to generate responses which follow the JSON API's standards.
|
249
|
+
Finally, having inhirited `JSONAPI::Utils` methods from the `BaseController` we could write our actions as the following:
|
157
250
|
|
158
251
|
```ruby
|
159
252
|
# app/controllers/users_controller.rb
|
160
|
-
class UsersController < BaseController
|
161
|
-
before_action :load_user, only: [:show]
|
162
|
-
|
163
253
|
# GET /users
|
164
254
|
def index
|
165
|
-
|
255
|
+
users = User.all
|
256
|
+
jsonapi_render json: users
|
166
257
|
end
|
167
258
|
|
168
259
|
# GET /users/:id
|
169
260
|
def show
|
170
|
-
|
261
|
+
user = User.find(params[:id])
|
262
|
+
jsonapi_render json: user
|
263
|
+
end
|
264
|
+
|
265
|
+
# POST /users
|
266
|
+
def create
|
267
|
+
user = User.new(user_params)
|
268
|
+
if user.save
|
269
|
+
jsonapi_render json: user, status: :created
|
270
|
+
else
|
271
|
+
jsonapi_render_errors json: user, status: :unprocessable_entity
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# PATCH /users/:id
|
276
|
+
def update
|
277
|
+
user = User.find(params[:id])
|
278
|
+
if user.update(user_params)
|
279
|
+
jsonapi_render json: user
|
280
|
+
else
|
281
|
+
jsonapi_render_errors json: user, status: :unprocessable_entity
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# DELETE /users/:id
|
286
|
+
def destroy
|
287
|
+
User.find(params[:id]).destroy
|
288
|
+
head :no_content
|
171
289
|
end
|
172
290
|
|
173
291
|
private
|
174
292
|
|
175
|
-
def
|
176
|
-
|
293
|
+
def user_params
|
294
|
+
params.require(:data).require(:attributes).permit(:first_name, :last_name, :admin)
|
177
295
|
end
|
178
|
-
end
|
179
296
|
```
|
180
297
|
|
181
298
|
And:
|
182
299
|
|
183
300
|
```ruby
|
184
|
-
# app/controllers/posts_controller.rb
|
185
301
|
class PostsController < BaseController
|
186
|
-
before_action :load_user,
|
187
|
-
before_action :load_post, only: [:show]
|
302
|
+
before_action :load_user, except: :create
|
188
303
|
|
189
304
|
# GET /users/:user_id/posts
|
190
305
|
def index
|
191
|
-
|
192
|
-
jsonapi_render json: posts, options: { count: posts.count }
|
306
|
+
jsonapi_render json: @user.posts, options: { count: 100 }
|
193
307
|
end
|
194
308
|
|
195
309
|
# GET /users/:user_id/posts/:id
|
196
310
|
def show
|
197
|
-
jsonapi_render json: @
|
311
|
+
jsonapi_render json: @user.posts.find(params[:id])
|
312
|
+
end
|
313
|
+
|
314
|
+
# POST /users
|
315
|
+
def create
|
316
|
+
post = Post.new(post_params)
|
317
|
+
if post.save
|
318
|
+
jsonapi_render json: post, status: :created
|
319
|
+
else
|
320
|
+
jsonapi_render_errors json: post, status: :unprocessable_entity
|
321
|
+
end
|
198
322
|
end
|
199
323
|
|
200
324
|
private
|
201
325
|
|
202
|
-
def
|
203
|
-
|
326
|
+
def post_params
|
327
|
+
params.require(:data).require(:attributes).permit(:title, :body)
|
328
|
+
.merge(user_id: author_params[:id])
|
204
329
|
end
|
205
330
|
|
206
|
-
def
|
207
|
-
|
331
|
+
def author_params
|
332
|
+
params.require(:relationships).require(:author).require(:data).permit(:id)
|
208
333
|
end
|
209
|
-
end
|
210
|
-
```
|
211
|
-
|
212
|
-
### Errors
|
213
|
-
|
214
|
-
#### Not found
|
215
|
-
|
216
|
-
As you might have seen in BaseController this line will handle all errors related to not found resources:
|
217
334
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
The `jsonapi_render_not_found` method will produce the following error payload:
|
223
|
-
|
224
|
-
```json
|
225
|
-
HTTP/1.1 404 Not found
|
226
|
-
Content-Type: application/vnd.api+json
|
227
|
-
|
228
|
-
{
|
229
|
-
"errors": [
|
230
|
-
{
|
231
|
-
"title": "Record not found",
|
232
|
-
"detail": "The record identified by 3 could not be found.",
|
233
|
-
"code": "404",
|
234
|
-
"status": "404"
|
235
|
-
}
|
236
|
-
]
|
237
|
-
}
|
238
|
-
```
|
239
|
-
|
240
|
-
In case you prefer rendering not found resources with null data and `200 OK` status code, you can use `jsonapi_render_not_found_with_null` to produce:
|
241
|
-
|
242
|
-
```json
|
243
|
-
HTTP/1.1 200 OK
|
244
|
-
Content-Type: application/vnd.api+json
|
245
|
-
|
246
|
-
{
|
247
|
-
"data": null
|
248
|
-
}
|
335
|
+
def load_user
|
336
|
+
@user = User.find(params[:user_id])
|
337
|
+
end
|
338
|
+
end
|
249
339
|
```
|
250
340
|
|
251
|
-
If you need to create custom error message, check [this](https://github.com/cerebris/jsonapi-resources#error-codes).
|
252
|
-
|
253
341
|
### Initializer
|
254
342
|
|
255
|
-
In order to enable a proper pagination, record count etc,
|
343
|
+
In order to enable a proper pagination, record count etc, an initializer could be defined such as:
|
256
344
|
|
257
345
|
```ruby
|
258
346
|
# config/initializers/jsonapi_resources.rb
|
data/lib/jsonapi/utils.rb
CHANGED
@@ -6,12 +6,12 @@ require 'jsonapi/utils/response'
|
|
6
6
|
|
7
7
|
module JSONAPI
|
8
8
|
module Utils
|
9
|
-
include
|
10
|
-
include
|
9
|
+
include Request
|
10
|
+
include Response
|
11
11
|
|
12
12
|
def self.included(base)
|
13
13
|
if base.respond_to?(:before_action)
|
14
|
-
base.before_action :
|
14
|
+
base.before_action :jsonapi_request_handling
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -1,10 +1,15 @@
|
|
1
1
|
module JSONAPI
|
2
2
|
module Utils
|
3
3
|
module Request
|
4
|
+
def jsonapi_request_handling
|
5
|
+
setup_request
|
6
|
+
check_request
|
7
|
+
end
|
8
|
+
|
4
9
|
def setup_request
|
5
10
|
@request ||=
|
6
11
|
JSONAPI::Request.new(
|
7
|
-
params,
|
12
|
+
params.dup,
|
8
13
|
context: context,
|
9
14
|
key_formatter: key_formatter,
|
10
15
|
server_error_callbacks: (self.class.server_error_callbacks || [])
|
@@ -14,6 +19,31 @@ module JSONAPI
|
|
14
19
|
def check_request
|
15
20
|
@request.errors.blank? || jsonapi_render_errors(json: @request)
|
16
21
|
end
|
22
|
+
|
23
|
+
def resource_params
|
24
|
+
build_params_for(:resource)
|
25
|
+
end
|
26
|
+
|
27
|
+
def relationship_params
|
28
|
+
build_params_for(:relationship)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_params_for(param_type)
|
34
|
+
return {} if @request.operations.empty?
|
35
|
+
|
36
|
+
keys = %i(attributes to_one to_many)
|
37
|
+
operation = @request.operations.find { |e| e.data.keys & keys == keys }
|
38
|
+
|
39
|
+
if operation.nil?
|
40
|
+
{}
|
41
|
+
elsif param_type == :relationship
|
42
|
+
operation.data.values_at(:to_one, :to_many).compact.reduce(&:merge)
|
43
|
+
else
|
44
|
+
operation.data[:attributes]
|
45
|
+
end
|
46
|
+
end
|
17
47
|
end
|
18
48
|
end
|
19
49
|
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.6
|
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-07-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: jsonapi-resources
|