roar 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|