Skip to content

Commit cdc795d

Browse files
committed
Merge pull request #2 from vladfaust/development
Version 1.0.0
2 parents c39e7b3 + 8768108 commit cdc795d

File tree

11 files changed

+176
-169
lines changed

11 files changed

+176
-169
lines changed

README.md

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Official JSON API [implementations page](http://jsonapi.org/implementations/#ser
88

99
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:
1010

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 two tiny methods.
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.
1212

1313
## Installation
1414

@@ -31,10 +31,13 @@ Or install it yourself as:
3131
Replace any content within any `*.json.jbuilder` file with the code below:
3232
```ruby
3333
# Common example
34-
json.api_format! @resources, @errors, meta: @meta, access_level: @user_access_level
34+
json.api_format! @resources, @errors, meta, options
3535

36-
# Articles w/o meta or access levels example
37-
json.api_format! @articles, @errors
36+
# Items example
37+
json.api_format! @items, @errors, nil, access_level: :admin
38+
39+
# A simple items example
40+
json.api_format! @items
3841
```
3942
You can also render formatted JSON straight from controller actions:
4043
```ruby
@@ -43,24 +46,24 @@ respond_to do |f|
4346
f.html { render nothing: true, status: :bad_request }
4447
end
4548
```
46-
Each resource instance, as well as the included one, will be invoked with `json_api_attrs` & `json_api_relations` methods. These methods **MAY** be implemented within each model. `api_format!` method will try to get an object's permitted (**you are free do define authentication logic yourself!**) attributes and relations via those two methods.
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.
4750

4851
Here is an example of implementation:
4952
```ruby
50-
# Item model
53+
# Inside Item model
5154

52-
def json_api_attrs (access_level = nil)
55+
def json_api_attrs (options = {})
5356
attrs = []
54-
attrs += %w(name description price buyoutable item_type category) if %i(user admin).include?access_level
55-
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
5659
attrs
5760
end
5861

59-
def json_api_relations (access_level = nil)
62+
def json_api_relations (options = {})
6063
%w(category orders)
6164
end
6265
```
63-
**Note** that the gem will call methods pulled with `json_api_relations and _attrs`. As for the above example, methods like `:name`, `:description`, `:orders` will be invoked for an Item instance. And yes, relations are fetched properly if an object responds to `orders`.
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`.
6467

6568
## Development
6669

@@ -75,5 +78,17 @@ Bug reports and pull requests are welcome on GitHub at [https://github.com/vladf
7578
## ToDo
7679

7780
- [ ] Maybe add `Content-Type: application/vnd.api+json`. This spec is ignored right now :smirk:
78-
- [ ] Add links tests
81+
- [ ] Add links tests and improve them. Links now work only within views (where `@context` is present).
7982
- [ ] 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.

lib/jbuilder/json_api.rb

Lines changed: 86 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,92 +5,99 @@ module JsonAPI
55
# Returns a valid-formatted JSON which follows JSON-API specifications:
66
# http://jsonapi.org/
77
#
8-
# Arguments:
9-
# :resources: - list of resources to render (may be even one or nil)
10-
# :errors: - array of errors in below format:
8+
# ==== Arguments
9+
# * +resources+ - list of resources to render (may be even one or nil);
10+
# * +errors+ - array of hashes in the below format:
1111
# [{ status: 422, detail: 'This error occurs because...' }, {...}]
12+
# * +meta+ - a hash representing any meta (additional) information.
1213
#
13-
# Options:
14-
# :access_level: - access level, e.g. nil, :user, :admin
15-
# :meta: - a hash representing meta (additional) information
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.
1618
#
17-
def api_format! (resources = nil, errors = nil, options = {})
18-
options.merge access_level: nil
19-
options.merge meta: nil
20-
21-
# Firstly, print meta
22-
# http://jsonapi.org/format/#document-meta
23-
#
24-
if options[:meta] && !options[:meta].empty?
25-
meta options[:meta]
26-
end
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
2750

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! (@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
4756
end
57+
return self
58+
end
59+
60+
resources = [*resources]
4861

62+
# http://jsonapi.org/format/#document-links
63+
#
64+
if @context
4965
links do
50-
about error[:about]
66+
set! 'self', @context.request.path
5167
end
5268
end
53-
return self
54-
end
55-
56-
resources = ::Kernel::Array resources
5769

58-
# http://jsonapi.org/format/#document-links
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)
6572
end
66-
end
6773

68-
data do
69-
resources.blank? ? array! : _api_resource_objects(resources, options[:access_level])
70-
end
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!
7182

72-
included = []
73-
resources.each do |resource|
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?
7785
end
78-
end
79-
included.uniq!
8086

81-
included do
82-
_api_resource_objects(included, options[:access_level], resources) unless included.blank?
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 }]
8392
end
84-
85-
self
8693
end
8794

8895
private
8996

9097
# Formats a resources array properly
9198
# http://jsonapi.org/format/#document-resource-objects
9299
#
93-
def _api_resource_objects (resources, access_level, parent_resources = nil)
100+
def _api_resource_objects (resources, options, parent_resources = nil)
94101
resources.each do |resource|
95102
child! do
96103
type resource.class.name.demodulize.to_s.downcase
@@ -100,7 +107,7 @@ def _api_resource_objects (resources, access_level, parent_resources = nil)
100107
#
101108
if resource.respond_to?'json_api_attrs'
102109
attributes do
103-
resource.json_api_attrs(access_level).each do |attribute|
110+
resource.json_api_attrs(options).each do |attribute|
104111
set! attribute, resource.send(attribute)
105112
end
106113
end
@@ -109,21 +116,19 @@ def _api_resource_objects (resources, access_level, parent_resources = nil)
109116
# http://jsonapi.org/format/#document-resource-object-relationships
110117
#
111118
if resource.respond_to?'json_api_relations'
112-
unless resource.json_api_relations(access_level).blank?
119+
unless resource.json_api_relations(options).blank?
113120
relationships do
114-
resource.json_api_relations(access_level).each do |relationship|
121+
resource.json_api_relations(options).each do |relationship|
115122
set! relationship do
116-
links do
117-
begin
123+
if @context
124+
links do
118125
related @context.send("#{ relationship.pluralize }_path")
119-
rescue
120-
# No @context given, cannot find path
126+
# TODO add a link to the relationship itself
121127
end
122-
# TODO add a link to the relationship itself
123128
end
124129

125130
data do
126-
::Kernel::Array(resource.send(relationship)).each do |relationship_instance|
131+
[*(resource.send(relationship))].each do |relationship_instance|
127132
# Relationships shouldn't ever link to the parent resource
128133
#
129134
next if !parent_resources.nil? && parent_resources.include?(relationship_instance)
@@ -139,11 +144,16 @@ def _api_resource_objects (resources, access_level, parent_resources = nil)
139144
end
140145
end
141146

142-
links do
143-
begin
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
144156
set! 'self', @context.send("#{ resource.class.name.demodulize.to_s.downcase }_path", resource)
145-
rescue
146-
# No @context given, cannot find path
147157
end
148158
end
149159
end

lib/jbuilder/json_api/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module JsonAPI
2-
VERSION = '0.0.1'
2+
VERSION = '1.0.0'
33
end

spec/jbuilder/examples/errors.json

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,15 @@
77
"code": 112,
88
"title": "Not found",
99
"source": {
10-
"pointer": null,
11-
"paramater": null
10+
"pointer": "http://posts_path"
1211
},
1312
"links": {
14-
"about": null
13+
"about": "https://en.wikipedia.org/wiki/HTTP_404"
1514
}
1615
},
1716
{
1817
"id": 2,
19-
"status": null,
20-
"detail": null,
21-
"code": null,
22-
"title": "Another error",
23-
"source": {
24-
"pointer": null,
25-
"paramater": null
26-
},
27-
"links": {
28-
"about": null
29-
}
18+
"title": "Another error"
3019
}
3120
]
3221
}

spec/jbuilder/examples/errors_meta.json

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,15 @@
1515
"code": 112,
1616
"title": "Not found",
1717
"source": {
18-
"pointer": null,
19-
"paramater": null
18+
"pointer": "http://posts_path"
2019
},
2120
"links": {
22-
"about": null
21+
"about": "https://en.wikipedia.org/wiki/HTTP_404"
2322
}
2423
},
2524
{
2625
"id": 2,
27-
"status": null,
28-
"detail": null,
29-
"code": null,
30-
"title": "Another error",
31-
"source": {
32-
"pointer": null,
33-
"paramater": null
34-
},
35-
"links": {
36-
"about": null
37-
}
26+
"title": "Another error"
3827
}
3928
]
4029
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"errors": [
3+
{
4+
"status": 500,
5+
"detail": "undefined method `id' for 42:Fixnum",
6+
"title": "NoMethodError"
7+
}
8+
]
9+
}

spec/jbuilder/examples/resources_admin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@
3737
}
3838
]
3939
}
40+
},
41+
"meta": {
42+
"blah": "Just another meta info for post #1"
4043
}
4144
},
4245
{
@@ -76,6 +79,9 @@
7679
}
7780
]
7881
}
82+
},
83+
"meta": {
84+
"blah": "Just another meta info for post #2"
7985
}
8086
}
8187
],

0 commit comments

Comments
 (0)