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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ec04f95554ca59d1117ffa0452646252a5a156c5
4
- data.tar.gz: 897fd3e12eceed6338421e6e5b7166afd8279947
3
+ metadata.gz: c328df382d804f5ae2d625216ac2febd26b952ac
4
+ data.tar.gz: b13829c6b72e5d4a8fb76fcafcac900916c5d8f6
5
5
  SHA512:
6
- metadata.gz: 2aa0e5fc2e3c030898bd2287804bb62752523f6f9e6aad6a01c14954d9d8a09dee587117a54cd7463cb9cefedeb5ca2d91bb00548e703b5a99f57eb15e91a97b
7
- data.tar.gz: 1136e92931251cce1ad8f652d56ee7555d0da2cbb93c3c4b44b7d33053b9460357b23bf84a9b178cb702e73942d92d9688e61d10ad0cd5e4e4bd80994e2d0a35
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.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
- ## Response Macros
25
+ ## How does it work?
26
26
 
27
- ### jsonapi_render
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
- Takes ActiveRecord/Hash objects and generates JSON API-compliant responses.
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`: ActiveRecord or Hash object to be rendered as JSON document;
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
- Examples:
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
- ### jsonapi_format
87
+ **jsonapi_render_errors**
72
88
 
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.
89
+ It takes arguments and generates a JSON API-compliant error response.
74
90
 
75
- Note: because of semantic reasons `JSONAPI::Utils#jsonapi_serialize` was renamed being now just an alias to `JSONAPI::Utils#jsonapi_format`.
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
- result = do_some_magic(jsonapi_format(User.all))
80
- render json: result
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
- - It receives the same options as `jsonapi_render`.
139
+ - First: ActiveRecord object, Hash or Array of Hashes;
140
+ - Last: Hash of options (same as `JSONAPI::Utils#jsonapi_render`).
86
141
 
87
- ## Usage
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
- Let's say we have a Rails app for a super simple blog.
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
- And a base controller to include the features from `jsonapi-resources` and `jsonapi-utils`:
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
- For this example, let's get focused only on read actions. After including `JSONAPI::Utils` we can use the `jsonapi_render` method
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
- jsonapi_render json: User.all
255
+ users = User.all
256
+ jsonapi_render json: users
166
257
  end
167
258
 
168
259
  # GET /users/:id
169
260
  def show
170
- jsonapi_render json: @user
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 load_user
176
- @user = User.find(params[:id])
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, only: [:index, :show]
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
- posts = @user.posts.enabled
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: @post
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 load_user
203
- @user = User.find(params[:user_id])
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 load_post
207
- @post = @user.posts.find(params[:id])
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
- ```ruby
219
- rescue_from ActiveRecord::RecordNotFound, with: :jsonapi_render_not_found
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, let's define an initializer such as:
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
@@ -6,12 +6,12 @@ require 'jsonapi/utils/response'
6
6
 
7
7
  module JSONAPI
8
8
  module Utils
9
- include JSONAPI::Utils::Request
10
- include JSONAPI::Utils::Response
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 :setup_request, :check_request
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
@@ -19,7 +19,7 @@ module JSONAPI
19
19
  JSONAPI::Utils::Support::Error.sanitize(errors).uniq
20
20
  end
21
21
 
22
- protected
22
+ private
23
23
 
24
24
  def build_response_document(records, options)
25
25
  results = JSONAPI::OperationResults.new
@@ -12,7 +12,7 @@ module JSONAPI
12
12
  include ::JSONAPI::Utils::Support::Pagination
13
13
  include ::JSONAPI::Utils::Support::Sort
14
14
 
15
- protected
15
+ private
16
16
 
17
17
  def correct_media_type
18
18
  if response.body.size > 0
@@ -1,5 +1,5 @@
1
1
  module JSONAPI
2
2
  module Utils
3
- VERSION = '0.4.5'
3
+ VERSION = '0.4.6'
4
4
  end
5
5
  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.5
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-06-06 00:00:00.000000000 Z
12
+ date: 2016-07-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: jsonapi-resources