jbuilder-json_api 0.0.1 → 1.0.0
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 +31 -14
- data/lib/jbuilder/json_api.rb +86 -76
- data/lib/jbuilder/json_api/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc772003535f958b77e4eca0eca1f9d7daa901bf
|
4
|
+
data.tar.gz: aef3eb54b4d987dc446dc548cd679af965fe692f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5153c6bda708041667eef1daf765a93ed8f148518fe0f0904d2fbcd969468ad4ecb83219710502433f1c81da32b61c5c9eb48d19c1ee03ecfbd3e90f3e2cbf31
|
7
|
+
data.tar.gz: 4b23517ae45d402b430595420d6845d96f460027700116f2dbbc21177bee1de5f0498d177d4fe34ef66e5a9dfa7aacd33754f6e11e6ec786022a77bac83cc581
|
data/README.md
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
# Jbuilder::JsonApi | [](https://badge.fury.io/rb/jbuilder-json_api)  [](https://travis-ci.org/vladfaust/jbuilder-json_api) [](https://gemnasium.com/vladfaust/jbuilder-json_api)
|
2
2
|
|
3
3
|
Adds a `json.api_format!(resources)` method to quickly represent a resource or collection in a valid [JSON API](http://jsonapi.org/) format without any new superclasses or weird setups. Set'n'go! :rocket:
|
4
4
|
|
5
5
|
## Motivation
|
6
6
|
|
7
|
-
Official JSON API [implementations page](http://jsonapi.org/implementations/#server-libraries-ruby) shows us a variety of different serializers and other heavy-weight stuff. I' in love with [Jbuilder](https://github.com/rails/jbuilder), as it allows to format json responses with ease. Therefore I wanted to connect Jbuilder and JsonApi.org specs.
|
7
|
+
Official JSON API [implementations page](http://jsonapi.org/implementations/#server-libraries-ruby) shows us a variety of different serializers and other heavy-weight stuff. I'm in love with [Jbuilder](https://github.com/rails/jbuilder), as it allows to format json responses with ease. Therefore I wanted to connect Jbuilder and JsonApi.org specs.
|
8
8
|
|
9
9
|
I'd like to notice that there already is one gem called [jbuilder-jsonapi](https://github.com/csexton/jbuilder-jsonapi) by [csexton](https://github.com/csexton), but it adds a links helper only. It's not enough for me! :facepunch:
|
10
10
|
|
11
|
+
As a result, I've created a **very** lightweight & flexible solution - all you need is Jbuilder and this gem. Then you should delete everything within your `*.json.jbuilder` files and replace it with below recommendations (just one line! :flushed:). After you are free to customize parsed attributes and relationships with three tiny methods.
|
12
|
+
|
11
13
|
## Installation
|
12
14
|
|
13
15
|
Add this line to your application's Gemfile:
|
@@ -29,10 +31,13 @@ Or install it yourself as:
|
|
29
31
|
Replace any content within any `*.json.jbuilder` file with the code below:
|
30
32
|
```ruby
|
31
33
|
# Common example
|
32
|
-
json.api_format! @resources, @errors, meta
|
34
|
+
json.api_format! @resources, @errors, meta, options
|
35
|
+
|
36
|
+
# Items example
|
37
|
+
json.api_format! @items, @errors, nil, access_level: :admin
|
33
38
|
|
34
|
-
#
|
35
|
-
json.api_format! @
|
39
|
+
# A simple items example
|
40
|
+
json.api_format! @items
|
36
41
|
```
|
37
42
|
You can also render formatted JSON straight from controller actions:
|
38
43
|
```ruby
|
@@ -41,24 +46,24 @@ respond_to do |f|
|
|
41
46
|
f.html { render nothing: true, status: :bad_request }
|
42
47
|
end
|
43
48
|
```
|
44
|
-
Each resource instance, as well as the included one, will be invoked with `json_api_attrs` & `
|
49
|
+
Each resource instance, as well as the included one, will be invoked with `json_api_attrs (options)`, `json_api_relations (options)` & `json_api_meta (options)` methods. These methods **MAY** be implemented within each model. `api_format!` method will try to get an object's permitted attributes (**remember, you are free do define authentication logic yourself!**) and relations and meta information via those three methods.
|
45
50
|
|
46
51
|
Here is an example of implementation:
|
47
52
|
```ruby
|
48
|
-
# Item model
|
53
|
+
# Inside Item model
|
49
54
|
|
50
|
-
def
|
55
|
+
def json_api_attrs (options = {})
|
51
56
|
attrs = []
|
52
|
-
attrs += %w(name description price buyoutable item_type category) if %i(user admin).include?access_level
|
53
|
-
attrs += %w(real_price in_stock) if access_level == :admin
|
57
|
+
attrs += %w(name description price buyoutable item_type category) if %i(user admin).include?options[:access_level]
|
58
|
+
attrs += %w(real_price in_stock) if options[:access_level] == :admin
|
54
59
|
attrs
|
55
60
|
end
|
56
61
|
|
57
|
-
def json_api_relations (
|
62
|
+
def json_api_relations (options = {})
|
58
63
|
%w(category orders)
|
59
64
|
end
|
60
65
|
```
|
61
|
-
**Note** that the gem will call methods pulled
|
66
|
+
**Note** that the gem will call methods pulled via `json_api_relations and _attrs`. As for the above example, methods like `:name`, `:description`, `:real_price`, `:orders` will be invoked for an Item instance. And yes, relations are fetched properly and recursively if the object responds to `orders`.
|
62
67
|
|
63
68
|
## Development
|
64
69
|
|
@@ -73,5 +78,17 @@ Bug reports and pull requests are welcome on GitHub at [https://github.com/vladf
|
|
73
78
|
## ToDo
|
74
79
|
|
75
80
|
- [ ] Maybe add `Content-Type: application/vnd.api+json`. This spec is ignored right now :smirk:
|
76
|
-
- [ ] Add links tests
|
77
|
-
- [ ] Somehow implement `[fields]` parameter
|
81
|
+
- [ ] Add links tests and improve them. Links now work only within views (where `@context` is present).
|
82
|
+
- [ ] Somehow implement `[fields]` parameter
|
83
|
+
|
84
|
+
## Versions
|
85
|
+
|
86
|
+
#### 0.0.1 -> 1.0.0
|
87
|
+
|
88
|
+
**Breaking:**
|
89
|
+
- [x] Now any value can be forwarded to resources' methods via last `options` argument.
|
90
|
+
- [x] Added third argument `meta`, which is used to show meta information in the context of request
|
91
|
+
|
92
|
+
**Not breaking:**
|
93
|
+
- [x] Added support for `json_api_meta (options)` method.
|
94
|
+
- [x] Any internal error is now properly handled.
|
data/lib/jbuilder/json_api.rb
CHANGED
@@ -5,84 +5,91 @@ module JsonAPI
|
|
5
5
|
# Returns a valid-formatted JSON which follows JSON-API specifications:
|
6
6
|
# http://jsonapi.org/
|
7
7
|
#
|
8
|
-
# Arguments
|
9
|
-
#
|
10
|
-
#
|
8
|
+
# ==== Arguments
|
9
|
+
# * +resources+ - list of resources to render (may be even one or nil);
|
10
|
+
# * +errors+ - array of hashes in the below format:
|
11
11
|
# [{ status: 422, detail: 'This error occurs because...' }, {...}]
|
12
|
+
# * +meta+ - a hash representing any meta (additional) information.
|
12
13
|
#
|
13
|
-
# Options
|
14
|
-
#
|
15
|
-
#
|
14
|
+
# ==== Options
|
15
|
+
# Any information can be passed as +options+ argument; resources' class methods
|
16
|
+
# +json_api_attrs+, +json_api_relations+ and +json_api_meta+
|
17
|
+
# will be invoked with this argument.
|
16
18
|
#
|
17
|
-
def api_format! (resources = nil, errors = nil, options = {})
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
19
|
+
def api_format! (resources = nil, errors = nil, meta = nil, options = {})
|
20
|
+
begin
|
21
|
+
# Firstly, print meta
|
22
|
+
# http://jsonapi.org/format/#document-meta
|
23
|
+
#
|
24
|
+
if meta && !meta.empty?
|
25
|
+
meta meta
|
26
|
+
end
|
27
|
+
|
28
|
+
# Secondly, take care of errors. If there are any,
|
29
|
+
# no 'data' section should be represented.
|
30
|
+
# http://jsonapi.org/format/#document-top-level
|
31
|
+
#
|
32
|
+
# Read more at
|
33
|
+
# http://jsonapi.org/format/#errors
|
34
|
+
#
|
35
|
+
if errors && !errors.empty?
|
36
|
+
ignore_nil! true
|
37
|
+
errors errors do |error|
|
38
|
+
id error[:id]
|
39
|
+
status error[:status]
|
40
|
+
detail error[:detail]
|
41
|
+
code error[:code]
|
42
|
+
title error[:title]
|
43
|
+
|
44
|
+
if error[:source]
|
45
|
+
source do
|
46
|
+
pointer error[:source][:pointer]
|
47
|
+
paramater error[:source][:parameter]
|
48
|
+
end
|
49
|
+
end
|
27
50
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
# http://jsonapi.org/format/#errors
|
34
|
-
#
|
35
|
-
if errors && !errors.empty?
|
36
|
-
ignore_nil! (@ignore_nil.nil? ? true : @ignore_nil)
|
37
|
-
errors errors do |error|
|
38
|
-
id error[:id]
|
39
|
-
status error[:status]
|
40
|
-
detail error[:detail]
|
41
|
-
code error[:code]
|
42
|
-
title error[:title]
|
43
|
-
|
44
|
-
source do
|
45
|
-
pointer error[:pointer]
|
46
|
-
paramater error[:parameter]
|
51
|
+
if error[:links]
|
52
|
+
links do
|
53
|
+
about error[:links][:about]
|
54
|
+
end
|
55
|
+
end
|
47
56
|
end
|
57
|
+
return self
|
58
|
+
end
|
59
|
+
|
60
|
+
resources = [*resources]
|
48
61
|
|
62
|
+
# http://jsonapi.org/format/#document-links
|
63
|
+
#
|
64
|
+
if @context
|
49
65
|
links do
|
50
|
-
|
66
|
+
set! 'self', @context.request.path
|
51
67
|
end
|
52
68
|
end
|
53
|
-
return self
|
54
|
-
end
|
55
|
-
|
56
|
-
resources = ::Kernel::Array resources
|
57
69
|
|
58
|
-
|
59
|
-
|
60
|
-
links do
|
61
|
-
begin
|
62
|
-
set! 'self', @context.request.path
|
63
|
-
rescue
|
64
|
-
# No @context given, cannot find path
|
70
|
+
data do
|
71
|
+
resources.blank? ? array! : _api_resource_objects(resources, options)
|
65
72
|
end
|
66
|
-
end
|
67
73
|
|
68
|
-
|
69
|
-
resources.
|
70
|
-
|
74
|
+
included = []
|
75
|
+
resources.each do |resource|
|
76
|
+
next unless resource.respond_to?'json_api_relations'
|
77
|
+
resource.json_api_relations(options).each do |relationship|
|
78
|
+
included += [*(resource.send(relationship))]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
included.uniq!
|
71
82
|
|
72
|
-
|
73
|
-
|
74
|
-
next unless resource.respond_to?'json_api_relations'
|
75
|
-
resource.json_api_relations(options[:access_level]).each do |relationship|
|
76
|
-
included += ::Kernel::Array(resource.send(relationship))
|
83
|
+
included do
|
84
|
+
_api_resource_objects(included, options, resources) unless included.blank?
|
77
85
|
end
|
78
|
-
end
|
79
|
-
included.uniq!
|
80
86
|
|
81
|
-
|
82
|
-
|
87
|
+
self
|
88
|
+
rescue Exception => e
|
89
|
+
@attributes = {}
|
90
|
+
message = Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.3.0') ? e.original_message : e.message
|
91
|
+
return api_format! nil, [{ status: 500, title: e.class.to_s, detail: message }]
|
83
92
|
end
|
84
|
-
|
85
|
-
self
|
86
93
|
end
|
87
94
|
|
88
95
|
private
|
@@ -90,7 +97,7 @@ module JsonAPI
|
|
90
97
|
# Formats a resources array properly
|
91
98
|
# http://jsonapi.org/format/#document-resource-objects
|
92
99
|
#
|
93
|
-
def _api_resource_objects (resources,
|
100
|
+
def _api_resource_objects (resources, options, parent_resources = nil)
|
94
101
|
resources.each do |resource|
|
95
102
|
child! do
|
96
103
|
type resource.class.name.demodulize.to_s.downcase
|
@@ -100,7 +107,7 @@ module JsonAPI
|
|
100
107
|
#
|
101
108
|
if resource.respond_to?'json_api_attrs'
|
102
109
|
attributes do
|
103
|
-
resource.json_api_attrs(
|
110
|
+
resource.json_api_attrs(options).each do |attribute|
|
104
111
|
set! attribute, resource.send(attribute)
|
105
112
|
end
|
106
113
|
end
|
@@ -109,21 +116,19 @@ module JsonAPI
|
|
109
116
|
# http://jsonapi.org/format/#document-resource-object-relationships
|
110
117
|
#
|
111
118
|
if resource.respond_to?'json_api_relations'
|
112
|
-
unless resource.json_api_relations(
|
119
|
+
unless resource.json_api_relations(options).blank?
|
113
120
|
relationships do
|
114
|
-
resource.json_api_relations(
|
121
|
+
resource.json_api_relations(options).each do |relationship|
|
115
122
|
set! relationship do
|
116
|
-
|
117
|
-
|
123
|
+
if @context
|
124
|
+
links do
|
118
125
|
related @context.send("#{ relationship.pluralize }_path")
|
119
|
-
|
120
|
-
# No @context given, cannot find path
|
126
|
+
# TODO add a link to the relationship itself
|
121
127
|
end
|
122
|
-
# TODO add a link to the relationship itself
|
123
128
|
end
|
124
129
|
|
125
130
|
data do
|
126
|
-
|
131
|
+
[*(resource.send(relationship))].each do |relationship_instance|
|
127
132
|
# Relationships shouldn't ever link to the parent resource
|
128
133
|
#
|
129
134
|
next if !parent_resources.nil? && parent_resources.include?(relationship_instance)
|
@@ -139,11 +144,16 @@ module JsonAPI
|
|
139
144
|
end
|
140
145
|
end
|
141
146
|
|
142
|
-
|
143
|
-
|
147
|
+
if resource.respond_to?'json_api_meta'
|
148
|
+
# We don't want to see 'meta': null
|
149
|
+
ignore_nil! true
|
150
|
+
meta resource.json_api_meta(options)
|
151
|
+
ignore_nil! @ignore_nil
|
152
|
+
end
|
153
|
+
|
154
|
+
if @context
|
155
|
+
links do
|
144
156
|
set! 'self', @context.send("#{ resource.class.name.demodulize.to_s.downcase }_path", resource)
|
145
|
-
rescue
|
146
|
-
# No @context given, cannot find path
|
147
157
|
end
|
148
158
|
end
|
149
159
|
end
|