jsonapi-utils 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/README.md +65 -24
- data/Rakefile +6 -4
- data/jsonapi-utils.gemspec +11 -8
- data/lib/jsonapi/utils.rb +56 -43
- data/lib/jsonapi/utils/exceptions.rb +0 -1
- data/lib/jsonapi/utils/version.rb +1 -1
- metadata +36 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c5c764a38bce9c5b3858bb2cde1a345ca31f6a23
|
4
|
+
data.tar.gz: 8c12bdfdf4c949eda015c8a46534a1e1c3cf333b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 202613eae09e04c60f0ccb83c250c5c934667899af4a359c4e98fa146f05db16d9dd1185505081f023ab6edda8e07a8d30617c3ba2c84ba2bc6aa4a490c24485
|
7
|
+
data.tar.gz: fc7edd5cd32d6281ad6d639b5d0756c0a80b81ca6b3bcc622c8495015b627e967cbd476088da8e2acb2f215eea8b1bc3534cd36bc90b34e7bf623b0ff4d9739c
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -2,16 +2,18 @@
|
|
2
2
|
|
3
3
|
[![Code Climate](https://codeclimate.com/github/b2beauty/jsonapi-utils/badges/gpa.svg)](https://codeclimate.com/github/b2beauty/jsonapi-utils)
|
4
4
|
[![Gem Version](https://badge.fury.io/rb/jsonapi-utils.svg)](https://badge.fury.io/rb/jsonapi-utils)
|
5
|
+
[![Build Status](https://travis-ci.org/b2beauty/jsonapi-utils.svg?branch=master)](https://travis-ci.org/b2beauty/jsonapi-utils)
|
5
6
|
|
6
|
-
|
7
|
+
Simple yet powerful way to have your Rails API compliant with [JSON API](http://jsonapi.org).
|
8
|
+
|
9
|
+
JSONAPI::Utils (JU) was built on top of [JSONAPI::Resources](https://github.com/cerebris/jsonapi-resources) taking advantage of its resource-driven style and bringing a Rails way to build modern APIs with no or less learning curve.
|
7
10
|
|
8
11
|
## Installation
|
9
12
|
|
10
13
|
Add these lines to your application's Gemfile:
|
11
14
|
|
12
15
|
```ruby
|
13
|
-
gem 'jsonapi-
|
14
|
-
gem 'jsonapi-utils'
|
16
|
+
gem 'jsonapi-utils', '~> 0.4.3'
|
15
17
|
```
|
16
18
|
|
17
19
|
And then execute:
|
@@ -20,34 +22,53 @@ And then execute:
|
|
20
22
|
$ bundle
|
21
23
|
```
|
22
24
|
|
23
|
-
##
|
24
|
-
|
25
|
-
* `jsonapi_render`: it works like ActionController's `render` method, receiving model objects and
|
26
|
-
rendering them into JSON API's data format.
|
25
|
+
## Macros
|
27
26
|
|
28
|
-
|
29
|
-
It can be called anywhere in controllers, concerns etc.
|
27
|
+
### jsonapi_render
|
30
28
|
|
31
|
-
|
29
|
+
Validates the request and renders ActiveRecord/Hash objects into JSON API-compliant responses.
|
32
30
|
|
33
|
-
|
31
|
+
Arguments:
|
34
32
|
|
35
|
-
|
36
|
-
|
33
|
+
- `json`: ActiveRecord or Hash object to be rendered as JSON document;
|
34
|
+
- `status`: HTTP status code (Integer or Symbol). If ommited a status code will be automatically infered;
|
35
|
+
- `options`:
|
36
|
+
- `resource`: explicitly points the resource to be used in the serialization. By default, JU will select resources by inferencing from controller's name.
|
37
|
+
- `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
|
+
- `model`: sets the model reference in cases when `json` is a Hash or a collection of Hashes.
|
37
39
|
|
38
40
|
```ruby
|
39
|
-
#
|
40
|
-
jsonapi_render json:
|
41
|
+
# Single resource rendering
|
42
|
+
jsonapi_render json: User.find(params[:id])
|
43
|
+
|
44
|
+
# Collection rendering
|
45
|
+
jsonapi_render json: User.all
|
46
|
+
|
47
|
+
# Forcing a different resource
|
48
|
+
jsonapi_render json: User.all, options: { resource: V2::UserResource }
|
49
|
+
|
50
|
+
# Using a specific count
|
51
|
+
jsonapi_render json: User.some_weird_scope, options: { count: User.some_weird_scope_count }
|
52
|
+
|
53
|
+
# Hash rendering
|
54
|
+
jsonapi_render json: { data: { id: 1, first_name: 'Tiago' } }, options: { model: User }
|
55
|
+
|
56
|
+
# Collection of Hashes rendering
|
57
|
+
jsonapi_render json: { data: [{ id: 1, first_name: 'Tiago' }, { id: 2, first_name: 'Doug' }] }, options: { model: User }
|
41
58
|
```
|
42
59
|
|
43
|
-
|
44
|
-
|
60
|
+
### jsonapi_serialize
|
61
|
+
|
62
|
+
In the backstage that's the method that actually parsers ActiveRecord/Hash objects and builds a new Hash compliant with JSON API. It can be called anywhere in controllers, concerns etc.
|
45
63
|
|
46
64
|
```ruby
|
47
|
-
|
48
|
-
|
65
|
+
collection = jsonapi_serialize(User.all)
|
66
|
+
render json: do_some_magic_with(collection)
|
49
67
|
```
|
50
68
|
|
69
|
+
Arguments:
|
70
|
+
- It receives a second optional argument with the same options for `jsonapi_render`.
|
71
|
+
|
51
72
|
## Usage
|
52
73
|
|
53
74
|
Let's say we have a Rails app for a super simple blog.
|
@@ -342,7 +363,7 @@ Content-Type: application/vnd.api+json
|
|
342
363
|
Request:
|
343
364
|
|
344
365
|
```
|
345
|
-
GET /users?include=posts&fields[users]=
|
366
|
+
GET /users?include=posts&fields[users]=first_name,last_name,posts&fields[posts]=title&sort=first_name,last_name&page[number]=1&page[size]=1 HTTP/1.1
|
346
367
|
Accept: application/vnd.api+json
|
347
368
|
```
|
348
369
|
|
@@ -354,6 +375,26 @@ Content-Type: application/vnd.api+json
|
|
354
375
|
|
355
376
|
{
|
356
377
|
"data": [
|
378
|
+
{
|
379
|
+
"id": "2",
|
380
|
+
"type": "users",
|
381
|
+
"links": {
|
382
|
+
"self": "http://api.myblog.com/users/2"
|
383
|
+
},
|
384
|
+
"attributes": {
|
385
|
+
"first_name": "Douglas",
|
386
|
+
"last_name": "André"
|
387
|
+
},
|
388
|
+
"relationships": {
|
389
|
+
"posts": {
|
390
|
+
"links": {
|
391
|
+
"self": "http://api.myblog.com/users/2/relationships/posts",
|
392
|
+
"related": "http://api.myblog.com/users/2/posts"
|
393
|
+
},
|
394
|
+
"data": []
|
395
|
+
}
|
396
|
+
}
|
397
|
+
},
|
357
398
|
{
|
358
399
|
"id": "1",
|
359
400
|
"type": "users",
|
@@ -361,7 +402,8 @@ Content-Type: application/vnd.api+json
|
|
361
402
|
"self": "http://api.myblog.com/users/1"
|
362
403
|
},
|
363
404
|
"attributes": {
|
364
|
-
"
|
405
|
+
"first_name": "Tiago",
|
406
|
+
"last_name": "Guedes"
|
365
407
|
},
|
366
408
|
"relationships": {
|
367
409
|
"posts": {
|
@@ -395,9 +437,8 @@ Content-Type: application/vnd.api+json
|
|
395
437
|
"record_count": 2
|
396
438
|
},
|
397
439
|
"links": {
|
398
|
-
"first": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=
|
399
|
-
"
|
400
|
-
"last": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=full_name%2Cposts&include=posts&page%5Bnumber%5D=2&page%5Bsize%5D=1"
|
440
|
+
"first": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name",
|
441
|
+
"last": "http://api.myblog.com/users?fields%5Bposts%5D=title&fields%5Busers%5D=first_name%2Clast_name%2Cposts&include=posts&page%5Blimit%5D=2&page%5Boffset%5D=0&sort=first_name%2Clast_name"
|
401
442
|
}
|
402
443
|
}
|
403
444
|
```
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
4
|
+
RSpec::Core::RakeTask.new(:spec) do |test|
|
5
|
+
test.pattern = 'spec/controllers/{user,post}s_controller.rb'
|
6
|
+
end
|
5
7
|
|
6
|
-
task :
|
8
|
+
task default: :spec
|
data/jsonapi-utils.gemspec
CHANGED
@@ -4,26 +4,29 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
4
|
require 'jsonapi/utils/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
|
-
spec.name =
|
7
|
+
spec.name = 'jsonapi-utils'
|
8
8
|
spec.version = JSONAPI::Utils::VERSION
|
9
|
-
spec.authors = [
|
10
|
-
spec.email = [
|
9
|
+
spec.authors = ['Tiago Guedes', 'Douglas André']
|
10
|
+
spec.email = ['tiagopog@gmail.com', 'douglas@beautydate.com.br']
|
11
11
|
|
12
12
|
spec.summary = %q{JSON::Utils is a simple way to get a full-featured JSON API serialization for your controller's responses.}
|
13
13
|
spec.description = %q{A Rails way to get your API's data serialized through JSON API's specs (http://jsosapi.org)}
|
14
|
-
spec.homepage =
|
15
|
-
spec.license =
|
14
|
+
spec.homepage = 'https://github.com/b2beauty/jsonapi-utils'
|
15
|
+
spec.license = 'MIT'
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
-
spec.bindir =
|
18
|
+
spec.bindir = 'exe'
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ['lib']
|
21
21
|
|
22
|
+
spec.add_runtime_dependency 'jsonapi-resources', '~> 0.7.0'
|
23
|
+
|
22
24
|
spec.add_development_dependency 'bundler', '~> 1.10'
|
23
25
|
spec.add_development_dependency 'rake', '~> 10.0'
|
24
|
-
spec.add_development_dependency 'rspec-rails'
|
26
|
+
spec.add_development_dependency 'rspec-rails', '~> 3.1'
|
25
27
|
spec.add_development_dependency 'sqlite3'
|
26
28
|
spec.add_development_dependency 'factory_girl', '~> 4.5'
|
27
29
|
spec.add_development_dependency 'jsonapi-resources', '~> 0.7.0'
|
28
|
-
spec.add_development_dependency 'rails', '
|
30
|
+
spec.add_development_dependency 'rails', '~> 4.2'
|
31
|
+
spec.add_development_dependency 'pry'
|
29
32
|
end
|
data/lib/jsonapi/utils.rb
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
+
require 'jsonapi-resources'
|
1
2
|
require 'jsonapi/utils/version'
|
2
|
-
require 'active_support/concern'
|
3
3
|
require 'jsonapi/utils/exceptions'
|
4
4
|
|
5
5
|
module JSONAPI
|
6
6
|
module Utils
|
7
|
-
|
8
|
-
|
9
|
-
include do
|
10
|
-
helper_method :jsonapi_serialize
|
7
|
+
def self.included(base)
|
8
|
+
base.helper_method :jsonapi_serialize
|
11
9
|
end
|
12
10
|
|
13
|
-
def jsonapi_render(options)
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
def jsonapi_render(json:, status: nil, options: {})
|
12
|
+
setup_request
|
13
|
+
|
14
|
+
if @request.errors.present?
|
15
|
+
render_errors(@request.errors)
|
17
16
|
else
|
18
|
-
|
17
|
+
body = jsonapi_serialize(json, options)
|
18
|
+
render json: body, status: status || @_response_document.status
|
19
19
|
end
|
20
20
|
rescue => e
|
21
|
-
raise e unless e.class.name.starts_with?('JSONAPI::Exceptions')
|
22
21
|
handle_exceptions(e)
|
23
22
|
ensure
|
24
|
-
|
23
|
+
if response.body.size > 0
|
24
|
+
response.headers['Content-Type'] = JSONAPI::MEDIA_TYPE
|
25
|
+
end
|
25
26
|
end
|
26
27
|
|
27
28
|
def jsonapi_render_errors(exception)
|
@@ -42,8 +43,7 @@ module JSONAPI
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def jsonapi_render_not_found(exception)
|
45
|
-
|
46
|
-
id = exception.message.match(/=(\d+)/)[1]
|
46
|
+
id = exception.message.match(/=([\w-]+)/).try(:[], 1) || '(no identifier)'
|
47
47
|
jsonapi_render_errors(JSONAPI::Exceptions::RecordNotFound.new(id))
|
48
48
|
end
|
49
49
|
|
@@ -53,12 +53,41 @@ module JSONAPI
|
|
53
53
|
|
54
54
|
def jsonapi_serialize(records, options = {})
|
55
55
|
setup_request
|
56
|
-
|
56
|
+
|
57
|
+
if records.is_a?(Hash)
|
58
|
+
hash = records.with_indifferent_access
|
59
|
+
records = hash_to_active_record(hash[:data], options[:model])
|
60
|
+
end
|
57
61
|
|
58
62
|
fix_request_options(params, records)
|
63
|
+
build_response_document(records, options).contents
|
64
|
+
end
|
65
|
+
|
66
|
+
def filter_params
|
67
|
+
@_filter_params ||=
|
68
|
+
if params[:filter].is_a?(Hash)
|
69
|
+
params[:filter].keys.each_with_object({}) do |resource, hash|
|
70
|
+
hash[resource] = params[:filter][resource]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def sort_params
|
76
|
+
@_sort_params ||=
|
77
|
+
if params[:sort].present?
|
78
|
+
params[:sort].split(',').each_with_object({}) do |criteria, hash|
|
79
|
+
order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
|
80
|
+
hash[field] = order == '-' ? :desc : :asc
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
def build_response_document(records, options)
|
88
|
+
results = JSONAPI::OperationResults.new
|
59
89
|
|
60
90
|
if records.respond_to?(:to_ary)
|
61
|
-
records = fix_when_hash(records, options) if needs_to_be_fixed?(records)
|
62
91
|
@_records = build_collection(records, options)
|
63
92
|
results.add_result(JSONAPI::ResourcesOperationResult.new(:ok, @_records, result_options(records, options)))
|
64
93
|
else
|
@@ -66,10 +95,9 @@ module JSONAPI
|
|
66
95
|
results.add_result(JSONAPI::ResourceOperationResult.new(:ok, @_record))
|
67
96
|
end
|
68
97
|
|
69
|
-
create_response_document(results)
|
98
|
+
@_response_document = create_response_document(results)
|
70
99
|
end
|
71
100
|
|
72
|
-
private
|
73
101
|
|
74
102
|
def fix_request_options(params, records)
|
75
103
|
return if request.method !~ /get/i ||
|
@@ -79,10 +107,6 @@ module JSONAPI
|
|
79
107
|
@request.send("setup_#{action}_action", params)
|
80
108
|
end
|
81
109
|
|
82
|
-
def needs_to_be_fixed?(records)
|
83
|
-
records.is_a?(Array) && records.all? { |e| e.is_a?(Hash) }
|
84
|
-
end
|
85
|
-
|
86
110
|
def result_options(records, options)
|
87
111
|
{}.tap do |data|
|
88
112
|
if JSONAPI.configuration.default_paginator != :none &&
|
@@ -147,13 +171,6 @@ module JSONAPI
|
|
147
171
|
(options[:filter].nil? || options[:filter])
|
148
172
|
end
|
149
173
|
|
150
|
-
def filter_params
|
151
|
-
@_filter_params ||=
|
152
|
-
params[:filter].keys.each_with_object({}) do |resource, hash|
|
153
|
-
hash[resource] = params[:filter][resource]
|
154
|
-
end
|
155
|
-
end
|
156
|
-
|
157
174
|
def apply_pagination(records, options = {})
|
158
175
|
return records unless apply_pagination?(options)
|
159
176
|
pagination = set_pagination(options)
|
@@ -184,14 +201,6 @@ module JSONAPI
|
|
184
201
|
end
|
185
202
|
end
|
186
203
|
|
187
|
-
def sort_params
|
188
|
-
@_sort_params ||=
|
189
|
-
params[:sort].split(',').each_with_object({}) do |criteria, hash|
|
190
|
-
order, field = criteria.match(/(\-?)(\w+)/i)[1..2]
|
191
|
-
hash[field] = order == '-' ? :desc : :asc
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
204
|
def set_pagination(options)
|
196
205
|
page_params = ActionController::Parameters.new(@request.params[:page])
|
197
206
|
if JSONAPI.configuration.default_paginator == :paged
|
@@ -214,13 +223,17 @@ module JSONAPI
|
|
214
223
|
(options[:paginate].nil? || options[:paginate])
|
215
224
|
end
|
216
225
|
|
217
|
-
def
|
218
|
-
return
|
219
|
-
|
226
|
+
def hash_to_active_record(data, model)
|
227
|
+
return data if model.nil?
|
228
|
+
coerced = [data].flatten.map { |hash| model.new(hash) }
|
229
|
+
data.is_a?(Array) ? coerced : coerced.first
|
220
230
|
rescue ActiveRecord::UnknownAttributeError
|
221
|
-
|
222
|
-
|
223
|
-
|
231
|
+
if data.is_a?(Array)
|
232
|
+
ids = data.map { |e| e[:id] }
|
233
|
+
model.where(id: ids)
|
234
|
+
else
|
235
|
+
model.find_by(id: data[:id])
|
236
|
+
end
|
224
237
|
end
|
225
238
|
|
226
239
|
def count_records(records, options)
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Guedes
|
@@ -9,8 +9,22 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2016-04-
|
12
|
+
date: 2016-04-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: jsonapi-resources
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 0.7.0
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 0.7.0
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: bundler
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -43,16 +57,16 @@ dependencies:
|
|
43
57
|
name: rspec-rails
|
44
58
|
requirement: !ruby/object:Gem::Requirement
|
45
59
|
requirements:
|
46
|
-
- - "
|
60
|
+
- - "~>"
|
47
61
|
- !ruby/object:Gem::Version
|
48
|
-
version: '
|
62
|
+
version: '3.1'
|
49
63
|
type: :development
|
50
64
|
prerelease: false
|
51
65
|
version_requirements: !ruby/object:Gem::Requirement
|
52
66
|
requirements:
|
53
|
-
- - "
|
67
|
+
- - "~>"
|
54
68
|
- !ruby/object:Gem::Version
|
55
|
-
version: '
|
69
|
+
version: '3.1'
|
56
70
|
- !ruby/object:Gem::Dependency
|
57
71
|
name: sqlite3
|
58
72
|
requirement: !ruby/object:Gem::Requirement
|
@@ -97,18 +111,32 @@ dependencies:
|
|
97
111
|
version: 0.7.0
|
98
112
|
- !ruby/object:Gem::Dependency
|
99
113
|
name: rails
|
114
|
+
requirement: !ruby/object:Gem::Requirement
|
115
|
+
requirements:
|
116
|
+
- - "~>"
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '4.2'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - "~>"
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '4.2'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: pry
|
100
128
|
requirement: !ruby/object:Gem::Requirement
|
101
129
|
requirements:
|
102
130
|
- - ">="
|
103
131
|
- !ruby/object:Gem::Version
|
104
|
-
version: '
|
132
|
+
version: '0'
|
105
133
|
type: :development
|
106
134
|
prerelease: false
|
107
135
|
version_requirements: !ruby/object:Gem::Requirement
|
108
136
|
requirements:
|
109
137
|
- - ">="
|
110
138
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
139
|
+
version: '0'
|
112
140
|
description: A Rails way to get your API's data serialized through JSON API's specs
|
113
141
|
(http://jsosapi.org)
|
114
142
|
email:
|