roar 1.0.0 → 1.0.1
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/CHANGES.markdown +4 -0
- data/Gemfile +2 -2
- data/README.markdown +27 -4
- data/examples/example.rb +49 -55
- data/gemfiles/Gemfile.representable-2.1 +1 -1
- data/lib/roar/client.rb +3 -1
- data/lib/roar/json/json_api.rb +3 -3
- data/lib/roar/version.rb +1 -1
- data/roar.gemspec +2 -2
- data/test/decorator_client_test.rb +64 -0
- data/test/json_api_test.rb +310 -295
- metadata +6 -5
data/lib/roar/json/json_api.rb
CHANGED
@@ -22,7 +22,7 @@ module Roar
|
|
22
22
|
singular = self # e.g. Song::Representer
|
23
23
|
|
24
24
|
# this basically does Module.new { include Hash::Collection .. }
|
25
|
-
build_inline(nil, [
|
25
|
+
build_inline(nil, [Representable::Hash::Collection, Document::Collection, Roar::JSON], "", {}) do
|
26
26
|
items extend: singular, :parse_strategy => :sync
|
27
27
|
|
28
28
|
representable_attrs[:resource_representer] = singular.representable_attrs[:resource_representer]
|
@@ -36,7 +36,7 @@ module Roar
|
|
36
36
|
module Singular
|
37
37
|
def to_hash(options={})
|
38
38
|
# per resource:
|
39
|
-
super(:exclude => [:links]).tap do |hash|
|
39
|
+
super(options.merge(:exclude => [:links])).tap do |hash|
|
40
40
|
hash["links"] = hash.delete("_links") if hash["_links"]
|
41
41
|
end
|
42
42
|
end
|
@@ -81,7 +81,7 @@ module Roar
|
|
81
81
|
|
82
82
|
# Per-model links.
|
83
83
|
def links(&block)
|
84
|
-
nested(:_links, &block)
|
84
|
+
nested(:_links, :inherit => true, &block)
|
85
85
|
end
|
86
86
|
|
87
87
|
# TODO: always create _links.
|
data/lib/roar/version.rb
CHANGED
data/roar.gemspec
CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.authors = ["Nick Sutterer"]
|
9
9
|
s.email = ["apotonick@gmail.com"]
|
10
10
|
s.homepage = "http://rubygems.org/gems/roar"
|
11
|
-
s.summary = %q{
|
12
|
-
s.description = %q{
|
11
|
+
s.summary = %q{Parse and render REST API documents using representers.}
|
12
|
+
s.description = %q{Object-oriented representers help you defining nested REST API documents which can then be rendered and parsed using one and the same concept.}
|
13
13
|
s.license = 'MIT'
|
14
14
|
|
15
15
|
s.files = `git ls-files`.split("\n")
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'roar/decorator'
|
3
|
+
require 'roar/client'
|
4
|
+
|
5
|
+
class DecoratorClientTest < MiniTest::Spec
|
6
|
+
class Crew
|
7
|
+
attr_accessor :moniker, :company
|
8
|
+
end
|
9
|
+
|
10
|
+
class CrewDecorator < Roar::Decorator
|
11
|
+
include Roar::JSON
|
12
|
+
include Roar::Hypermedia
|
13
|
+
|
14
|
+
property :moniker, as: :name
|
15
|
+
property :company, as: :label
|
16
|
+
|
17
|
+
link(:self) do
|
18
|
+
"http://bands/#{represented.moniker}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CrewClient < CrewDecorator
|
23
|
+
include Roar::Client
|
24
|
+
end
|
25
|
+
|
26
|
+
before do
|
27
|
+
@crew = Crew.new
|
28
|
+
@client = CrewClient.new(@crew)
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'HttpVerbs integration' do
|
32
|
+
describe '#get' do
|
33
|
+
it 'updates instance with incoming representation' do
|
34
|
+
@client.get(uri: 'http://localhost:4567/bands/slayer', as: 'application/json')
|
35
|
+
@crew.moniker.must_equal 'Slayer'
|
36
|
+
@crew.company.must_equal 'Canadian Maple'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#post' do
|
41
|
+
it 'creates a new resource with the given values' do
|
42
|
+
@crew.moniker = 'Strung Out'
|
43
|
+
@crew.company.must_be_nil
|
44
|
+
|
45
|
+
@client.post(uri: 'http://localhost:4567/bands', as: 'application/xml')
|
46
|
+
@crew.moniker.must_equal 'STRUNG OUT'
|
47
|
+
@crew.company.must_be_nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe '#to_hash' do
|
53
|
+
it 'suppresses rendering links' do
|
54
|
+
@crew.moniker = 'Silence'
|
55
|
+
@client.to_json.must_equal %{{\"name\":\"Silence\",\"links\":[]}}
|
56
|
+
end
|
57
|
+
|
58
|
+
# since this is considered dangerous, we test the mutuable options.
|
59
|
+
it "adds links: false to options" do
|
60
|
+
@client.to_hash(options = {})
|
61
|
+
options.must_equal({:links => false})
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/test/json_api_test.rb
CHANGED
@@ -2,394 +2,409 @@ require 'test_helper'
|
|
2
2
|
require 'roar/json/json_api'
|
3
3
|
require 'json'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# minimal resource, singular
|
22
|
-
module MinimalSingular
|
23
|
-
include Roar::JSON::JSONAPI
|
24
|
-
type :songs
|
25
|
-
|
26
|
-
property :id
|
27
|
-
end
|
28
|
-
|
29
|
-
class MinimalSingularDecorator < Roar::Decorator
|
30
|
-
include Roar::JSON::JSONAPI
|
31
|
-
type :songs
|
5
|
+
require "representable/version"
|
6
|
+
if Gem::Version.new(Representable::VERSION) >= Gem::Version.new("2.1.4") # TODO: remove check once we bump representable dependency.
|
7
|
+
class JSONAPITest < MiniTest::Spec
|
8
|
+
let(:song) {
|
9
|
+
s = OpenStruct.new(
|
10
|
+
bla: "halo",
|
11
|
+
id: "1",
|
12
|
+
title: 'Computadores Fazem Arte',
|
13
|
+
album: OpenStruct.new(id: 9, title: "Hackers"),
|
14
|
+
:album_id => "9",
|
15
|
+
:musician_ids => ["1","2"],
|
16
|
+
:composer_id => "10",
|
17
|
+
:listener_ids => ["8"],
|
18
|
+
musicians: [OpenStruct.new(id: 1, name: "Eddie Van Halen"), OpenStruct.new(id: 2, name: "Greg Howe")]
|
19
|
+
)
|
32
20
|
|
33
|
-
|
34
|
-
end
|
21
|
+
}
|
35
22
|
|
36
|
-
|
37
|
-
|
38
|
-
|
23
|
+
# minimal resource, singular
|
24
|
+
module MinimalSingular
|
25
|
+
include Roar::JSON::JSONAPI
|
26
|
+
type :songs
|
39
27
|
|
40
|
-
|
41
|
-
it { subject.from_json("{\"songs\":{\"id\":\"2\"}}").id.must_equal "2" }
|
28
|
+
property :id
|
42
29
|
end
|
43
|
-
end
|
44
|
-
|
45
30
|
|
31
|
+
class MinimalSingularDecorator < Roar::Decorator
|
32
|
+
include Roar::JSON::JSONAPI
|
33
|
+
type :songs
|
46
34
|
|
47
|
-
|
48
|
-
|
49
|
-
type :songs
|
35
|
+
property :id
|
36
|
+
end
|
50
37
|
|
51
|
-
|
52
|
-
|
38
|
+
[MinimalSingular, MinimalSingularDecorator].each do |representer|
|
39
|
+
describe "minimal singular with #{representer}" do
|
40
|
+
subject { representer.prepare(song) }
|
53
41
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
collection :musician_ids, :as => :musicians
|
42
|
+
it { subject.to_json.must_equal "{\"songs\":{\"id\":\"1\"}}" }
|
43
|
+
it { subject.from_json("{\"songs\":{\"id\":\"2\"}}").id.must_equal "2" }
|
44
|
+
end
|
58
45
|
end
|
59
|
-
has_one :composer
|
60
|
-
has_many :listeners
|
61
46
|
|
47
|
+
module Singular
|
48
|
+
include Roar::JSON::JSONAPI
|
49
|
+
type :songs
|
62
50
|
|
63
|
-
|
64
|
-
|
65
|
-
{
|
66
|
-
type: "album",
|
67
|
-
href: "http://example.com/albums/{songs.album}"
|
68
|
-
}
|
69
|
-
end
|
51
|
+
property :id
|
52
|
+
property :title, if: lambda { |args| args[:omit_title] != true }
|
70
53
|
|
71
|
-
|
72
|
-
|
73
|
-
property :
|
54
|
+
# local per-model "id" links
|
55
|
+
links do
|
56
|
+
property :album_id, :as => :album
|
57
|
+
collection :musician_ids, :as => :musicians
|
74
58
|
end
|
59
|
+
has_one :composer
|
60
|
+
has_many :listeners
|
75
61
|
|
76
|
-
collection :musicians do
|
77
|
-
property :name
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
62
|
|
82
|
-
|
83
|
-
|
84
|
-
|
63
|
+
# global document links.
|
64
|
+
link "songs.album" do
|
65
|
+
{
|
66
|
+
type: "album",
|
67
|
+
href: "http://example.com/albums/{songs.album}"
|
68
|
+
}
|
69
|
+
end
|
85
70
|
|
86
|
-
|
87
|
-
|
71
|
+
compound do
|
72
|
+
property :album do
|
73
|
+
property :title
|
74
|
+
end
|
88
75
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
76
|
+
collection :musicians do
|
77
|
+
property :name
|
78
|
+
end
|
79
|
+
end
|
93
80
|
end
|
94
|
-
has_one :composer
|
95
|
-
has_many :listeners
|
96
81
|
|
82
|
+
class SingularDecorator < Roar::Decorator
|
83
|
+
include Roar::JSON::JSONAPI
|
84
|
+
type :songs
|
97
85
|
|
98
|
-
|
99
|
-
|
100
|
-
{
|
101
|
-
type: "album",
|
102
|
-
href: "http://example.com/albums/{songs.album}"
|
103
|
-
}
|
104
|
-
end
|
105
|
-
|
106
|
-
compound do
|
107
|
-
property :album do
|
108
|
-
property :title
|
109
|
-
end
|
86
|
+
property :id
|
87
|
+
property :title, if: lambda { |args| args[:omit_title] != true }
|
110
88
|
|
111
|
-
|
112
|
-
|
89
|
+
# NOTE: it is important to call has_one, then links, then has_many to assert that they all write
|
90
|
+
#to the same _links property and do NOT override things.
|
91
|
+
has_one :composer
|
92
|
+
# local per-model "id" links
|
93
|
+
links do
|
94
|
+
property :album_id, :as => :album
|
95
|
+
collection :musician_ids, :as => :musicians
|
113
96
|
end
|
114
|
-
|
115
|
-
end
|
97
|
+
has_many :listeners
|
116
98
|
|
117
|
-
[Singular, SingularDecorator].each do |representer|
|
118
|
-
describe "singular with #{representer}" do
|
119
|
-
subject { song.extend(Singular) }
|
120
99
|
|
121
|
-
|
100
|
+
# global document links.
|
101
|
+
link "songs.album" do
|
122
102
|
{
|
123
|
-
"
|
124
|
-
|
125
|
-
"title" => "Computadores Fazem Arte",
|
126
|
-
"links" => {
|
127
|
-
"album" => "9",
|
128
|
-
"musicians" => [ "1", "2" ],
|
129
|
-
"composer"=>"10",
|
130
|
-
"listeners"=>["8"]
|
131
|
-
}
|
132
|
-
},
|
133
|
-
"links" => {
|
134
|
-
"songs.album"=> {
|
135
|
-
"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
|
136
|
-
}
|
137
|
-
},
|
138
|
-
"linked" => {
|
139
|
-
"album"=> [{"title"=>"Hackers"}],
|
140
|
-
"musicians"=> [
|
141
|
-
{"name"=>"Eddie Van Halen"},
|
142
|
-
{"name"=>"Greg Howe"}
|
143
|
-
]
|
144
|
-
}
|
103
|
+
type: "album",
|
104
|
+
href: "http://example.com/albums/{songs.album}"
|
145
105
|
}
|
146
106
|
end
|
147
107
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
108
|
+
compound do
|
109
|
+
property :album do
|
110
|
+
property :title
|
111
|
+
end
|
152
112
|
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
end
|
157
|
-
|
158
|
-
# #from_json
|
159
|
-
it do
|
160
|
-
song = OpenStruct.new.extend(Singular)
|
161
|
-
song.from_json(
|
162
|
-
JSON.generate(
|
163
|
-
{
|
164
|
-
"songs" => {
|
165
|
-
"id" => "1",
|
166
|
-
"title" => "Computadores Fazem Arte",
|
167
|
-
"links" => {
|
168
|
-
"album" => "9",
|
169
|
-
"musicians" => [ "1", "2" ],
|
170
|
-
"composer"=>"10",
|
171
|
-
"listeners"=>["8"]
|
172
|
-
}
|
173
|
-
},
|
174
|
-
"links" => {
|
175
|
-
"songs.album"=> {
|
176
|
-
"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
|
177
|
-
}
|
178
|
-
}
|
179
|
-
}
|
180
|
-
)
|
181
|
-
)
|
182
|
-
|
183
|
-
song.id.must_equal "1"
|
184
|
-
song.title.must_equal "Computadores Fazem Arte"
|
185
|
-
song.album_id.must_equal "9"
|
186
|
-
song.musician_ids.must_equal ["1", "2"]
|
187
|
-
song.composer_id.must_equal "10"
|
188
|
-
song.listener_ids.must_equal ["8"]
|
113
|
+
collection :musicians do
|
114
|
+
property :name
|
115
|
+
end
|
189
116
|
end
|
190
117
|
end
|
191
|
-
end
|
192
|
-
|
193
118
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
subject { Singular.for_collection.prepare([song, song]) }
|
119
|
+
[Singular, SingularDecorator].each do |representer|
|
120
|
+
describe "singular with #{representer}" do
|
121
|
+
subject { song.extend(Singular) }
|
198
122
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
{
|
123
|
+
let (:document) do
|
124
|
+
{
|
125
|
+
"songs" => {
|
203
126
|
"id" => "1",
|
204
127
|
"title" => "Computadores Fazem Arte",
|
205
128
|
"links" => {
|
206
129
|
"album" => "9",
|
207
130
|
"musicians" => [ "1", "2" ],
|
208
131
|
"composer"=>"10",
|
209
|
-
|
132
|
+
"listeners"=>["8"]
|
210
133
|
}
|
211
|
-
},
|
212
|
-
|
213
|
-
"
|
214
|
-
|
215
|
-
"album" => "9",
|
216
|
-
"musicians" => [ "1", "2" ],
|
217
|
-
"composer"=>"10",
|
218
|
-
"listeners"=>["8"]
|
134
|
+
},
|
135
|
+
"links" => {
|
136
|
+
"songs.album"=> {
|
137
|
+
"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
|
219
138
|
}
|
220
|
-
}
|
221
|
-
],
|
222
|
-
"links" => {
|
223
|
-
"songs.album" => {
|
224
|
-
"href" => "http://example.com/albums/{songs.album}",
|
225
|
-
"type" => "album" # DISCUSS: does that have to be albums ?
|
226
139
|
},
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
140
|
+
"linked" => {
|
141
|
+
"album"=> [{"title"=>"Hackers"}],
|
142
|
+
"musicians"=> [
|
143
|
+
{"name"=>"Eddie Van Halen"},
|
144
|
+
{"name"=>"Greg Howe"}
|
145
|
+
]
|
146
|
+
}
|
231
147
|
}
|
232
|
-
|
233
|
-
|
148
|
+
end
|
149
|
+
|
150
|
+
# to_hash
|
151
|
+
it do
|
152
|
+
subject.to_hash.must_equal document
|
153
|
+
end
|
154
|
+
|
155
|
+
# to_hash(options)
|
156
|
+
it do
|
157
|
+
subject.to_hash(omit_title: true)['songs'].wont_include('title')
|
158
|
+
end
|
159
|
+
|
160
|
+
# #to_json
|
161
|
+
it do
|
162
|
+
subject.to_json.must_equal JSON.generate(document)
|
163
|
+
end
|
164
|
+
|
165
|
+
# #from_json
|
166
|
+
it do
|
167
|
+
song = OpenStruct.new.extend(Singular)
|
168
|
+
song.from_json(
|
169
|
+
JSON.generate(
|
170
|
+
{
|
171
|
+
"songs" => {
|
172
|
+
"id" => "1",
|
173
|
+
"title" => "Computadores Fazem Arte",
|
174
|
+
"links" => {
|
175
|
+
"album" => "9",
|
176
|
+
"musicians" => [ "1", "2" ],
|
177
|
+
"composer"=>"10",
|
178
|
+
"listeners"=>["8"]
|
179
|
+
}
|
180
|
+
},
|
181
|
+
"links" => {
|
182
|
+
"songs.album"=> {
|
183
|
+
"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
|
184
|
+
}
|
185
|
+
}
|
186
|
+
}
|
187
|
+
)
|
188
|
+
)
|
234
189
|
|
235
|
-
|
236
|
-
|
237
|
-
|
190
|
+
song.id.must_equal "1"
|
191
|
+
song.title.must_equal "Computadores Fazem Arte"
|
192
|
+
song.album_id.must_equal "9"
|
193
|
+
song.musician_ids.must_equal ["1", "2"]
|
194
|
+
song.composer_id.must_equal "10"
|
195
|
+
song.listener_ids.must_equal ["8"]
|
196
|
+
end
|
238
197
|
end
|
239
|
-
|
240
|
-
# #to_json
|
241
|
-
it { subject.to_json.must_equal JSON.generate(document) }
|
242
198
|
end
|
243
199
|
|
244
200
|
|
245
|
-
#
|
246
|
-
|
247
|
-
|
248
|
-
|
201
|
+
# collection with links
|
202
|
+
[Singular, SingularDecorator].each do |representer|
|
203
|
+
describe "collection with links and compound with #{representer}" do
|
204
|
+
subject { representer.for_collection.prepare([song, song]) }
|
205
|
+
|
206
|
+
let (:document) do
|
249
207
|
{
|
250
208
|
"songs" => [
|
251
209
|
{
|
252
210
|
"id" => "1",
|
253
211
|
"title" => "Computadores Fazem Arte",
|
254
212
|
"links" => {
|
213
|
+
"composer"=>"10",
|
255
214
|
"album" => "9",
|
256
215
|
"musicians" => [ "1", "2" ],
|
257
|
-
"composer"=>"10",
|
258
216
|
"listeners"=>["8"]
|
259
|
-
}
|
260
|
-
},
|
261
|
-
|
262
|
-
"
|
263
|
-
"title" => "Talking To Remind Me",
|
217
|
+
}
|
218
|
+
}, {
|
219
|
+
"id" => "1",
|
220
|
+
"title" => "Computadores Fazem Arte",
|
264
221
|
"links" => {
|
265
|
-
"
|
266
|
-
"
|
267
|
-
"
|
268
|
-
"listeners"=>["
|
222
|
+
"composer"=>"10",
|
223
|
+
"album" => "9",
|
224
|
+
"musicians" => [ "1", "2" ],
|
225
|
+
"listeners"=>["8"]
|
269
226
|
}
|
270
|
-
}
|
227
|
+
}
|
271
228
|
],
|
272
229
|
"links" => {
|
273
|
-
"songs.album"=> {
|
274
|
-
"href"=>"http://example.com/albums/{songs.album}",
|
275
|
-
|
230
|
+
"songs.album" => {
|
231
|
+
"href" => "http://example.com/albums/{songs.album}",
|
232
|
+
"type" => "album" # DISCUSS: does that have to be albums ?
|
233
|
+
},
|
234
|
+
},
|
235
|
+
"linked"=>{
|
236
|
+
"album" =>[{"title"=>"Hackers"}], # only once!
|
237
|
+
"musicians"=>[{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]
|
276
238
|
}
|
277
239
|
}
|
240
|
+
end
|
241
|
+
|
242
|
+
# to_hash
|
243
|
+
it do
|
244
|
+
subject.to_hash.must_equal document
|
245
|
+
end
|
246
|
+
|
247
|
+
# to_hash(options)
|
248
|
+
it do
|
249
|
+
subject.to_hash(omit_title: true)['songs'].each do |song|
|
250
|
+
song.wont_include('title')
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# #to_json
|
255
|
+
it { subject.to_json.must_match /linked/ } # hash ordering changes, and i don't care why.
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
# from_json
|
260
|
+
it do
|
261
|
+
song1, song2 = Singular.for_collection.prepare([OpenStruct.new, OpenStruct.new]).from_json(
|
262
|
+
JSON.generate(
|
263
|
+
{
|
264
|
+
"songs" => [
|
265
|
+
{
|
266
|
+
"id" => "1",
|
267
|
+
"title" => "Computadores Fazem Arte",
|
268
|
+
"links" => {
|
269
|
+
"album" => "9",
|
270
|
+
"musicians" => [ "1", "2" ],
|
271
|
+
"composer"=>"10",
|
272
|
+
"listeners"=>["8"]
|
273
|
+
},
|
274
|
+
},
|
275
|
+
{
|
276
|
+
"id" => "2",
|
277
|
+
"title" => "Talking To Remind Me",
|
278
|
+
"links" => {
|
279
|
+
"album" => "1",
|
280
|
+
"musicians" => [ "3", "4" ],
|
281
|
+
"composer"=>"2",
|
282
|
+
"listeners"=>["6"]
|
283
|
+
}
|
284
|
+
},
|
285
|
+
],
|
286
|
+
"links" => {
|
287
|
+
"songs.album"=> {
|
288
|
+
"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
|
289
|
+
}
|
290
|
+
}
|
291
|
+
}
|
292
|
+
)
|
278
293
|
)
|
279
|
-
)
|
280
294
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
295
|
+
song1.id.must_equal "1"
|
296
|
+
song1.title.must_equal "Computadores Fazem Arte"
|
297
|
+
song1.album_id.must_equal "9"
|
298
|
+
song1.musician_ids.must_equal ["1", "2"]
|
299
|
+
song1.composer_id.must_equal "10"
|
300
|
+
song1.listener_ids.must_equal ["8"]
|
301
|
+
|
302
|
+
song2.id.must_equal "2"
|
303
|
+
song2.title.must_equal "Talking To Remind Me"
|
304
|
+
song2.album_id.must_equal "1"
|
305
|
+
song2.musician_ids.must_equal ["3", "4"]
|
306
|
+
song2.composer_id.must_equal "2"
|
307
|
+
song2.listener_ids.must_equal ["6"]
|
308
|
+
end
|
294
309
|
end
|
295
|
-
end
|
296
310
|
|
297
311
|
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
312
|
+
class CollectionWithoutCompound < self
|
313
|
+
module Representer
|
314
|
+
include Roar::JSON::JSONAPI
|
315
|
+
type :songs
|
302
316
|
|
303
|
-
|
304
|
-
|
317
|
+
property :id
|
318
|
+
property :title
|
305
319
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
320
|
+
# local per-model "id" links
|
321
|
+
links do
|
322
|
+
property :album_id, :as => :album
|
323
|
+
collection :musician_ids, :as => :musicians
|
324
|
+
end
|
325
|
+
has_one :composer
|
326
|
+
has_many :listeners
|
313
327
|
|
314
328
|
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
329
|
+
# global document links.
|
330
|
+
link "songs.album" do
|
331
|
+
{
|
332
|
+
type: "album",
|
333
|
+
href: "http://example.com/albums/{songs.album}"
|
334
|
+
}
|
335
|
+
end
|
321
336
|
end
|
322
|
-
end
|
323
337
|
|
324
|
-
|
338
|
+
subject { [song, song].extend(Singular.for_collection) }
|
325
339
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
340
|
+
# to_json
|
341
|
+
it do
|
342
|
+
subject.extend(Representer.for_collection).to_hash.must_equal(
|
343
|
+
{
|
344
|
+
"songs"=>[{"id"=>"1", "title"=>"Computadores Fazem Arte", "links"=>{"album"=>"9", "musicians"=>["1", "2"], "composer"=>"10", "listeners"=>["8"]}}, {"id"=>"1", "title"=>"Computadores Fazem Arte", "links"=>{"album"=>"9", "musicians"=>["1", "2"], "composer"=>"10", "listeners"=>["8"]}}],
|
345
|
+
"links"=>{"songs.album"=>{"href"=>"http://example.com/albums/{songs.album}", "type"=>"album"}
|
346
|
+
}
|
332
347
|
}
|
333
|
-
|
334
|
-
|
348
|
+
)
|
349
|
+
end
|
335
350
|
end
|
336
|
-
end
|
337
351
|
|
338
352
|
|
339
|
-
|
340
|
-
|
341
|
-
|
353
|
+
class ExplicitMeta < self
|
354
|
+
module Representer
|
355
|
+
include Roar::JSON::JSONAPI
|
342
356
|
|
343
|
-
|
344
|
-
|
357
|
+
type :songs
|
358
|
+
property :id
|
345
359
|
|
346
|
-
|
347
|
-
|
360
|
+
meta do
|
361
|
+
property :page
|
362
|
+
end
|
348
363
|
end
|
349
|
-
end
|
350
364
|
|
351
|
-
|
352
|
-
|
353
|
-
|
365
|
+
module Page
|
366
|
+
def page
|
367
|
+
2
|
368
|
+
end
|
354
369
|
end
|
355
|
-
end
|
356
370
|
|
357
|
-
|
371
|
+
let (:song) { Struct.new(:id).new(1) }
|
358
372
|
|
359
|
-
|
373
|
+
subject { [song, song].extend(Representer.for_collection).extend(Page) }
|
360
374
|
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
375
|
+
# to_json
|
376
|
+
it do
|
377
|
+
subject.to_hash.must_equal(
|
378
|
+
{
|
379
|
+
"songs"=>[{"id"=>1}, {"id"=>1}],
|
380
|
+
"meta" =>{"page"=>2}
|
381
|
+
}
|
382
|
+
)
|
383
|
+
end
|
369
384
|
end
|
370
|
-
end
|
371
385
|
|
372
386
|
|
373
|
-
|
374
|
-
|
375
|
-
|
387
|
+
class ImplicitMeta < self
|
388
|
+
module Representer
|
389
|
+
include Roar::JSON::JSONAPI
|
376
390
|
|
377
|
-
|
378
|
-
|
379
|
-
|
391
|
+
type :songs
|
392
|
+
property :id
|
393
|
+
end
|
380
394
|
|
381
|
-
|
395
|
+
let (:song) { Struct.new(:id).new(1) }
|
382
396
|
|
383
|
-
|
397
|
+
subject { [song, song].extend(Representer.for_collection) }
|
384
398
|
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
399
|
+
# to_json
|
400
|
+
it do
|
401
|
+
subject.to_hash("meta" => {"page" => 2}).must_equal(
|
402
|
+
{
|
403
|
+
"songs"=>[{"id"=>1}, {"id"=>1}],
|
404
|
+
"meta" =>{"page"=>2}
|
405
|
+
}
|
406
|
+
)
|
407
|
+
end
|
393
408
|
end
|
394
409
|
end
|
395
|
-
end
|
410
|
+
end
|