roar-jsonapi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +37 -0
- data/.travis.yml +14 -0
- data/.yardopts +5 -0
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +11 -0
- data/ISSUE_TEMPLATE.md +20 -0
- data/LICENSE +20 -0
- data/README.markdown +127 -0
- data/Rakefile +12 -0
- data/lib/roar/json/json_api.rb +156 -0
- data/lib/roar/json/json_api/declarative.rb +196 -0
- data/lib/roar/json/json_api/defaults.rb +25 -0
- data/lib/roar/json/json_api/document.rb +104 -0
- data/lib/roar/json/json_api/for_collection.rb +35 -0
- data/lib/roar/json/json_api/member_name.rb +57 -0
- data/lib/roar/json/json_api/meta.rb +56 -0
- data/lib/roar/json/json_api/options.rb +98 -0
- data/lib/roar/json/json_api/version.rb +7 -0
- data/roar-jsonapi.gemspec +25 -0
- data/test/jsonapi/collection_render_test.rb +399 -0
- data/test/jsonapi/fieldsets_options_test.rb +161 -0
- data/test/jsonapi/fieldsets_test.rb +293 -0
- data/test/jsonapi/member_name_test.rb +91 -0
- data/test/jsonapi/post_test.rb +78 -0
- data/test/jsonapi/render_test.rb +281 -0
- data/test/jsonapi/representer.rb +112 -0
- data/test/jsonapi/resource_linkage_test.rb +88 -0
- data/test/test_helper.rb +42 -0
- metadata +132 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ece64905f608afe23b1ff500b22cc2735773ff4d
|
4
|
+
data.tar.gz: 8b9b74e066c310d7dfcc1fb615ec050df9e825ea
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f9617c6cbe337a9d0533f67426fd6b8ce83420e7873f783acea631e7227a39e59097dd11b4a80916d694b2d5771dd9a47da2e7212c12096960eaf72e7058ce3b
|
7
|
+
data.tar.gz: 320c347be830d71313d237cb66460dfa1ef63ae0492c73d6064f3e5cba0bf50497a1c51769c249ae9a7047681063d659d81e1b0f97aa2fe9e0f2ed797fd86d20
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 1.9
|
3
|
+
|
4
|
+
Style/AlignHash:
|
5
|
+
EnforcedHashRocketStyle: table
|
6
|
+
EnforcedColonStyle: table
|
7
|
+
|
8
|
+
Style/AndOr:
|
9
|
+
EnforcedStyle: conditionals
|
10
|
+
|
11
|
+
Style/BlockDelimiters:
|
12
|
+
Enabled: true
|
13
|
+
EnforcedStyle: semantic
|
14
|
+
IgnoredMethods:
|
15
|
+
- lambda
|
16
|
+
- proc
|
17
|
+
- it
|
18
|
+
- link
|
19
|
+
|
20
|
+
Style/Lambda:
|
21
|
+
Enabled: false
|
22
|
+
EnforcedStyle: literal
|
23
|
+
|
24
|
+
Style/LambdaCall:
|
25
|
+
EnforcedStyle: braces
|
26
|
+
|
27
|
+
Metrics/LineLength:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Metrics/MethodLength:
|
31
|
+
Max: 15
|
32
|
+
|
33
|
+
Style/PredicateName:
|
34
|
+
NameWhitelist:
|
35
|
+
- is_a?
|
36
|
+
- has_one
|
37
|
+
- has_many
|
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
## How to contribute to Roar
|
2
|
+
|
3
|
+
#### **Did you find a bug?**
|
4
|
+
|
5
|
+
* **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/trailblazer/roar-jsonapi/issues).
|
6
|
+
|
7
|
+
* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/trailblazer/roar-jsonapi/issues/new). Be sure to follow the issue template.
|
8
|
+
|
9
|
+
#### **Did you write a patch that fixes a bug?**
|
10
|
+
|
11
|
+
* Open a new GitHub pull request with the patch.
|
12
|
+
|
13
|
+
* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable.
|
14
|
+
|
15
|
+
* All code in pull requests is assumed to be MIT licensed. Do not submit a pull request if that isn't the case.
|
16
|
+
|
17
|
+
#### **Do you intend to add a new feature or change an existing one?**
|
18
|
+
|
19
|
+
* Suggest your change in the [Trailblazer Gitter Room](https://gitter.im/trailblazer/chat) and start writing code.
|
20
|
+
|
21
|
+
* Do not open an issue on GitHub until you have collected positive feedback about the change. GitHub issues are primarily intended for bug reports and fixes.
|
22
|
+
|
23
|
+
#### **Do you have questions using Roar?**
|
24
|
+
|
25
|
+
* Ask any questions about how to use Roar in the [Trailblazer Gitter Room](https://gitter.im/trailblazer/chat). Github issues are restricted to bug reports and fixes.
|
26
|
+
|
27
|
+
* GitHub Issues should not be used as a help forum and any such issues will be closed.
|
28
|
+
|
29
|
+
#### **Do you want to contribute to the Roar documentation?**
|
30
|
+
|
31
|
+
* Roar documentation is provided via the [Trailblazer site](http://trailblazer.to/gems/roar/) and not the repository readme. Please add your contributions to the [Trailblazer site repository](https://github.com/trailblazer/trailblazer.github.io)
|
data/Gemfile
ADDED
data/ISSUE_TEMPLATE.md
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Note: If you have a question about Roar, would like help using
|
2
|
+
Roar, want to request a feature, or do anything else other than
|
3
|
+
submit a bug report, please use the Trailblazer gitter channel.
|
4
|
+
|
5
|
+
### Complete Description of Issue
|
6
|
+
|
7
|
+
|
8
|
+
### Steps to reproduce
|
9
|
+
|
10
|
+
|
11
|
+
### Expected behavior
|
12
|
+
Tell us what should happen
|
13
|
+
|
14
|
+
### Actual behavior
|
15
|
+
Tell us what happens instead
|
16
|
+
|
17
|
+
### System configuration
|
18
|
+
**Roar version**:
|
19
|
+
|
20
|
+
### Full Backtrace of Exception (if any)
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 - 2017 Nick Sutterer and the roar contributors
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
# Roar JSON API
|
2
|
+
|
3
|
+
_Resource-Oriented Architectures in Ruby._
|
4
|
+
|
5
|
+
[![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat)
|
6
|
+
[![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/)
|
7
|
+
[![Build Status](https://travis-ci.org/trailblazer/roar-jsonapi.svg?branch=master)](https://travis-ci.org/trailblazer/roar-jsonapi)
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/roar-jsonapi.svg)](http://badge.fury.io/rb/roar-jsonapi)
|
9
|
+
|
10
|
+
Roar JSON API provides support for [JSON API](http://jsonapi.org/), a specification for building APIs in JSON. It can render _and_ parse singular and collection documents.
|
11
|
+
|
12
|
+
### Resource
|
13
|
+
|
14
|
+
A minimal representation of a Resource can be defined as follows:
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
require 'roar/json/json_api'
|
18
|
+
|
19
|
+
class SongsRepresenter < Roar::Decorator
|
20
|
+
include Roar::JSON::JSONAPI.resource :songs
|
21
|
+
|
22
|
+
attributes do
|
23
|
+
property :title
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
Properties (or attributes) of the represented model are defined within an
|
29
|
+
`attributes` block.
|
30
|
+
|
31
|
+
An `id` property will automatically defined when using Roar JSON API.
|
32
|
+
|
33
|
+
### Relationships
|
34
|
+
|
35
|
+
To define relationships, use `::has_one` or `::has_many` with either an inline
|
36
|
+
or a standalone representer (specified with the `extend:` or `decorates:` option).
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
class SongsRepresenter < Roar::Decorator
|
40
|
+
include Roar::JSON::JSONAPI.resource :songs
|
41
|
+
|
42
|
+
has_one :album do
|
43
|
+
property :title
|
44
|
+
end
|
45
|
+
|
46
|
+
has_many :musicians, extend: MusicianRepresenter
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Meta information
|
51
|
+
|
52
|
+
Meta information can be included into rendered singular and collection documents in two ways.
|
53
|
+
|
54
|
+
You can define meta information on your collection object and then let Roar compile it.
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
class SongsRepresenter < Roar::Decorator
|
58
|
+
include Roar::JSON::JSONAPI.resource :songs
|
59
|
+
|
60
|
+
meta toplevel: true do
|
61
|
+
property :page
|
62
|
+
property :total
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
Your collection object must expose the respective methods.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
collection.page #=> 1
|
71
|
+
collection.total #=> 12
|
72
|
+
```
|
73
|
+
|
74
|
+
This will render the `{"meta": {"page": 1, "total": 12}}` hash into the JSON API document.
|
75
|
+
|
76
|
+
Alternatively, you can provide meta information as a hash when rendering. Any values also defined on your object will be overriden.
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
collection.to_json(meta: {page: params["page"], total: collection.size})
|
80
|
+
```
|
81
|
+
|
82
|
+
Both methods work for singular documents too.
|
83
|
+
|
84
|
+
```ruby
|
85
|
+
class SongsRepresenter < Roar::Decorator
|
86
|
+
include Roar::JSON::JSONAPI.resource :songs
|
87
|
+
|
88
|
+
meta do
|
89
|
+
property :label
|
90
|
+
property :format
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
```ruby
|
96
|
+
song.to_json(meta: { label: 'EMI' })
|
97
|
+
```
|
98
|
+
|
99
|
+
If you need more functionality (and parsing), please let us know.
|
100
|
+
|
101
|
+
### Usage
|
102
|
+
|
103
|
+
As JSON API per definition can represent singular models and collections you have two entry points.
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
SongsRepresenter.prepare(Song.find(1)).to_json
|
107
|
+
SongsRepresenter.prepare(Song.new).from_json("..")
|
108
|
+
```
|
109
|
+
|
110
|
+
Singular models can use the representer module directly.
|
111
|
+
|
112
|
+
```ruby
|
113
|
+
SongsRepresenter.for_collection.prepare([Song.find(1), Song.find(2)]).to_json
|
114
|
+
SongsRepresenter.for_collection.prepare([Song.new, Song.new]).from_json("..")
|
115
|
+
```
|
116
|
+
|
117
|
+
|
118
|
+
Parsing currently works great with singular documents - for collections, we are still working out how to encode the application semantics. Feel free to help.
|
119
|
+
|
120
|
+
## Support
|
121
|
+
|
122
|
+
Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
|
123
|
+
We also have a [mailing list](https://groups.google.com/forum/?fromgroups#!forum/roar-talk), yiha!
|
124
|
+
|
125
|
+
## License
|
126
|
+
|
127
|
+
Roar is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'roar/json'
|
2
|
+
require 'roar/decorator'
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
require 'roar/json/json_api/member_name'
|
6
|
+
|
7
|
+
require 'roar/json/json_api/defaults'
|
8
|
+
require 'roar/json/json_api/meta'
|
9
|
+
require 'roar/json/json_api/declarative'
|
10
|
+
require 'roar/json/json_api/for_collection'
|
11
|
+
require 'roar/json/json_api/options'
|
12
|
+
require 'roar/json/json_api/document'
|
13
|
+
|
14
|
+
module Roar
|
15
|
+
module JSON
|
16
|
+
module JSONAPI
|
17
|
+
# Include to define a JSON API Resource and make API methods available to
|
18
|
+
# your `Roar::Decorator`.
|
19
|
+
#
|
20
|
+
# @api public
|
21
|
+
class Resource < Module
|
22
|
+
# @param [Symbol, String] type type name of this resource.
|
23
|
+
# @option options [Symbol] :id_key custom ID key for this resource.
|
24
|
+
def initialize(type, options = {})
|
25
|
+
@type = type
|
26
|
+
@id_key = options.fetch(:id_key, :id)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# Hook called when module is included
|
32
|
+
#
|
33
|
+
# @param [Class,Module] base
|
34
|
+
# the module or class including JSONAPI
|
35
|
+
#
|
36
|
+
# @return [undefined]
|
37
|
+
#
|
38
|
+
# @api private
|
39
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
40
|
+
def included(base)
|
41
|
+
base.send(:include, JSONAPI::Mixin)
|
42
|
+
base.type(@type)
|
43
|
+
base.property(@id_key, as: :id, render_filter: ->(input, _opts) {
|
44
|
+
input.to_s
|
45
|
+
})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Include to define a JSON API Resource and make API methods available to
|
50
|
+
# your `Roar::Decorator`.
|
51
|
+
#
|
52
|
+
# @example Basic Usage
|
53
|
+
# class SongsRepresenter < Roar::Decorator
|
54
|
+
# include Roar::JSON::JSONAPI.resource :songs
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# @example Custom ID key
|
58
|
+
# class SongsRepresenter < Roar::Decorator
|
59
|
+
# include Roar::JSON::JSONAPI.resource :songs, id_key: :song_id
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# @param (see Resource.initialize)
|
63
|
+
# @option options (see Resource.initialize)
|
64
|
+
#
|
65
|
+
# @see Mixin
|
66
|
+
# @api public
|
67
|
+
def self.resource(type, options = {})
|
68
|
+
Resource.new(type, options)
|
69
|
+
end
|
70
|
+
|
71
|
+
# Include to make API methods available to your `Roar::Decorator`.
|
72
|
+
#
|
73
|
+
# Unlike {Resource}, you must define a `type` (by calling
|
74
|
+
# {Declarative#type}) and `id` property separately.
|
75
|
+
#
|
76
|
+
# @example Basic Usage
|
77
|
+
# class SongsRepresenter < Roar::Decorator
|
78
|
+
# include Roar::JSON::JSONAPI::Mixin
|
79
|
+
#
|
80
|
+
# type :songs
|
81
|
+
# property :id
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# @see Resource
|
85
|
+
# @api semi-public
|
86
|
+
module Mixin
|
87
|
+
# Hook called when module is included
|
88
|
+
#
|
89
|
+
# @param [Class,Module] base
|
90
|
+
# the module or class including JSONAPI
|
91
|
+
#
|
92
|
+
# @return [undefined]
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
# @see http://www.ruby-doc.org/core/Module.html#method-i-included
|
96
|
+
def self.included(base)
|
97
|
+
base.class_eval do
|
98
|
+
feature Roar::JSON
|
99
|
+
feature Roar::Hypermedia
|
100
|
+
feature JSONAPI::Defaults, JSONAPI::Meta
|
101
|
+
extend JSONAPI::Declarative
|
102
|
+
extend JSONAPI::ForCollection
|
103
|
+
include JSONAPI::Document
|
104
|
+
self.representation_wrap = :data
|
105
|
+
|
106
|
+
nested :relationships do
|
107
|
+
end
|
108
|
+
|
109
|
+
nested :included do
|
110
|
+
def to_hash(*)
|
111
|
+
super.flat_map { |_, resource| resource }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# @api private
|
119
|
+
module Renderer
|
120
|
+
class Links
|
121
|
+
def call(res, _options)
|
122
|
+
tuples = (res.delete('links') || []).collect { |link|
|
123
|
+
[JSONAPI::MemberName.(link['rel']), link['href']]
|
124
|
+
}
|
125
|
+
|
126
|
+
::Hash[tuples] # NOTE: change to tuples.to_h when dropping < 2.1.
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# @api private
|
132
|
+
module Fragment
|
133
|
+
Included = ->(included, options) do
|
134
|
+
return unless included && included.any?
|
135
|
+
return if options[:included] == false
|
136
|
+
|
137
|
+
type_and_id_seen = Set.new
|
138
|
+
|
139
|
+
included = included.select { |object|
|
140
|
+
type_and_id_seen.add? [object['type'], object['id']]
|
141
|
+
}
|
142
|
+
|
143
|
+
included
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# @api private
|
149
|
+
module HashUtils
|
150
|
+
def store_if_any(hash, key, value)
|
151
|
+
hash[key] = value if value && value.any?
|
152
|
+
end
|
153
|
+
module_function :store_if_any
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|