jsonapi-utils 0.4.2 → 0.4.3
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/.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
|
[](https://codeclimate.com/github/b2beauty/jsonapi-utils)
|
4
4
|
[](https://badge.fury.io/rb/jsonapi-utils)
|
5
|
+
[](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:
|