roar 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -6
  3. data/CHANGES.markdown +75 -58
  4. data/CONTRIBUTING.md +31 -0
  5. data/Gemfile +12 -2
  6. data/ISSUE_TEMPLATE.md +20 -0
  7. data/LICENSE +1 -1
  8. data/README.markdown +126 -250
  9. data/Rakefile +3 -1
  10. data/examples/example.rb +0 -0
  11. data/examples/example_server.rb +0 -0
  12. data/lib/roar.rb +3 -3
  13. data/lib/roar/client.rb +8 -3
  14. data/lib/roar/decorator.rb +2 -2
  15. data/lib/roar/http_verbs.rb +0 -16
  16. data/lib/roar/hypermedia.rb +30 -56
  17. data/lib/roar/json.rb +5 -5
  18. data/lib/roar/json/collection.rb +10 -2
  19. data/lib/roar/json/hal.rb +72 -82
  20. data/lib/roar/version.rb +1 -1
  21. data/lib/roar/xml.rb +1 -1
  22. data/roar.gemspec +6 -6
  23. data/test/client_test.rb +1 -1
  24. data/test/coercion_feature_test.rb +7 -2
  25. data/test/decorator_test.rb +17 -7
  26. data/test/hal_json_test.rb +98 -106
  27. data/test/hypermedia_feature_test.rb +13 -31
  28. data/test/hypermedia_test.rb +26 -92
  29. data/test/{decorator_client_test.rb → integration/decorator_client_test.rb} +5 -4
  30. data/test/{faraday_http_transport_test.rb → integration/faraday_http_transport_test.rb} +1 -0
  31. data/test/{http_verbs_test.rb → integration/http_verbs_test.rb} +3 -2
  32. data/test/integration/json_collection_test.rb +35 -0
  33. data/test/{net_http_transport_test.rb → integration/net_http_transport_test.rb} +1 -0
  34. data/test/integration/runner.rb +2 -3
  35. data/test/integration/server.rb +6 -0
  36. data/test/json_representer_test.rb +2 -29
  37. data/test/lonely_test.rb +1 -2
  38. data/test/ssl_client_certs_test.rb +1 -1
  39. data/test/test_helper.rb +21 -3
  40. data/test/xml_representer_test.rb +6 -5
  41. metadata +22 -36
  42. data/gemfiles/Gemfile.representable-1.7 +0 -6
  43. data/gemfiles/Gemfile.representable-1.8 +0 -6
  44. data/gemfiles/Gemfile.representable-2.0 +0 -5
  45. data/gemfiles/Gemfile.representable-2.1 +0 -5
  46. data/gemfiles/Gemfile.representable-head +0 -6
  47. data/lib/roar/json/collection_json.rb +0 -208
  48. data/lib/roar/json/json_api.rb +0 -233
  49. data/test/collection_json_test.rb +0 -132
  50. data/test/hal_links_test.rb +0 -31
  51. data/test/json_api_test.rb +0 -451
  52. data/test/lib/runner.rb +0 -134
@@ -1,233 +0,0 @@
1
- require 'roar/json'
2
- require 'roar/decorator'
3
-
4
- module Roar
5
- module JSON
6
- module JSONAPI
7
- def self.included(base)
8
- base.class_eval do
9
- include Representable::JSON
10
- include Roar::JSON::JSONAPI::Singular
11
- include Roar::JSON::JSONAPI::Resource
12
- include Roar::JSON::JSONAPI::Document
13
-
14
- extend ForCollection
15
-
16
- representable_attrs[:resource_representer] = Class.new(Resource::Representer)
17
-
18
- private
19
- def create_representation_with(doc, options, format)
20
- super(doc, options.merge(:only_body => true), format)
21
- end
22
- end
23
- end
24
-
25
- module ForCollection
26
- def for_collection # same API as representable. TODO: we could use ::collection_representer! here.
27
- singular = self # e.g. Song::Representer
28
-
29
- # this basically does Module.new { include Hash::Collection .. }
30
- build_inline(nil, [Representable::Hash::Collection, Document::Collection, Roar::JSON], "", {}) do
31
- items extend: singular, :parse_strategy => :sync
32
-
33
- representable_attrs[:resource_representer] = singular.representable_attrs[:resource_representer]
34
- representable_attrs[:meta_representer] = singular.representable_attrs[:meta_representer] # DISCUSS: do we need that?
35
- representable_attrs[:_wrap] = singular.representable_attrs[:_wrap]
36
- end
37
- end
38
- end
39
-
40
-
41
- module Singular
42
- def to_hash(options={})
43
- # per resource:
44
- super(options.merge(:exclude => [:links])).tap do |hash|
45
- hash["links"] = hash.delete("_links") if hash["_links"]
46
- end
47
- end
48
-
49
- def from_hash(hash, options={})
50
- hash["_links"] = hash["links"]
51
- super
52
- end
53
- end
54
-
55
-
56
- module Resource
57
- # ::link is delegated to Representer which handles the hypermedia (rendering
58
- # and parsing links).
59
- class Representer < Roar::Decorator
60
- include Roar::JSON
61
- include Roar::Hypermedia
62
-
63
- def self.links_definition_options
64
- {
65
- :extend => LinkCollectionRepresenter,
66
- :exec_context => :decorator
67
- }
68
- end
69
- end
70
-
71
- def self.included(base)
72
- base.extend Declarative # inject our ::link.
73
- end
74
-
75
- # New API for JSON-API representers.
76
- module Declarative
77
- def type(name=nil)
78
- return super unless name # original name.
79
- representable_attrs[:_wrap] = name.to_s
80
- end
81
-
82
- # Define global document links for the links: directive.
83
- def link(*args, &block)
84
- representable_attrs[:resource_representer].link(*args, &block)
85
- end
86
-
87
- # Per-model links.
88
- def links(&block)
89
- nested(:_links, :inherit => true, &block)
90
- end
91
-
92
- # TODO: always create _links.
93
- def has_one(name)
94
- property :_links, :inherit => true, :use_decorator => true do # simply extend the Decorator _links.
95
- property "#{name}_id", :as => name
96
- end
97
- end
98
-
99
- def has_many(name)
100
- property :_links, :inherit => true, :use_decorator => true do # simply extend the Decorator _links.
101
- collection "#{name.to_s.sub(/s$/, "")}_ids", :as => name
102
- end
103
- end
104
-
105
- def compound(&block)
106
- nested(:linked, &block)
107
- end
108
-
109
- def meta(&block)
110
- representable_attrs[:meta_representer] = Class.new(Roar::Decorator, &block)
111
- end
112
- end
113
- end
114
-
115
-
116
- # TODO: don't use Document for singular+wrap AND singular in collection (this way, we can get rid of the only_body)
117
- module Document
118
- def to_hash(options={})
119
- # per resource:
120
- res = super # render single resource or collection.
121
- return res if options[:only_body]
122
- # this is the only "dirty" part: this module is always included in the Singular document representer, when used in collection, we don't want it to do the extra work. this mechanism here might be changed soon.
123
-
124
- to_document(res, options)
125
- end
126
-
127
- def from_hash(hash, options={})
128
-
129
- return super(hash, options) if options[:only_body] # singular
130
-
131
- super(from_document(hash)) # singular
132
- end
133
-
134
- private
135
- def to_document(res, options)
136
- links = render_links
137
- meta = render_meta(options)
138
- # FIXME: provide two different #to_document
139
-
140
- if res.is_a?(Array)
141
- compound = collection_compound!(res, {})
142
- else
143
- compound = compile_compound!(res.delete("linked"), {})
144
- end
145
-
146
- {representable_attrs[:_wrap] => res}.tap do |doc|
147
- doc.merge!(links)
148
- doc.merge!(meta)
149
- doc.merge!("linked" => compound) if compound && compound.size > 0 # FIXME: make that like the above line.
150
- end
151
- end
152
-
153
- def from_document(hash)
154
- hash[representable_attrs[:_wrap]]
155
- end
156
-
157
- # Compiles the linked: section for compound objects in the document.
158
- def collection_compound!(collection, compound)
159
- collection.each { |res|
160
- kv = res.delete("linked") or next
161
-
162
- compile_compound!(kv, compound)
163
- }
164
-
165
- compound
166
- end
167
-
168
- # Go through {"album"=>{"title"=>"Hackers"}, "musicians"=>[{"name"=>"Eddie Van Halen"}, ..]} from linked:
169
- # and wrap every item in an array.
170
- def compile_compound!(linked, compound)
171
- return unless linked
172
-
173
- linked.each { |k,v| # {"album"=>{"title"=>"Hackers"}, "musicians"=>[{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]}
174
- compound[k] ||= []
175
-
176
- if v.is_a?(::Hash) # {"title"=>"Hackers"}
177
- compound[k] << v
178
- else
179
- compound[k].push(*v) # [{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]
180
- end
181
-
182
- compound[k] = compound[k].uniq
183
- }
184
-
185
- compound
186
- end
187
-
188
- def render_links
189
- representable_attrs[:resource_representer].new(represented).to_hash # creates links: section.
190
- end
191
-
192
- def render_meta(options)
193
- # TODO: this will call collection.page etc, directly on the collection. we could allow using a "meta"
194
- # object to hold this data.
195
- # `meta call_meta: true` or something
196
- return {"meta" => options["meta"]} if options["meta"]
197
- return {} unless representer = representable_attrs[:meta_representer]
198
- {"meta" => representer.new(represented).extend(Representable::Hash).to_hash}
199
- end
200
-
201
-
202
- module Collection
203
- include Document
204
-
205
- def to_hash(options={})
206
- res = super(options.merge(:only_body => true))
207
- to_document(res, options)
208
- end
209
-
210
- def from_hash(hash, options={})
211
- hash = from_document(hash)
212
- super(hash, options.merge(:only_body => true))
213
- end
214
- end
215
- end
216
-
217
-
218
- module LinkRepresenter
219
- include Roar::JSON
220
-
221
- property :href
222
- property :type
223
- end
224
-
225
- require 'representable/json/hash'
226
- module LinkCollectionRepresenter
227
- include Representable::JSON::Hash
228
-
229
- values :extend => LinkRepresenter # TODO: parsing.
230
- end
231
- end
232
- end
233
- end
@@ -1,132 +0,0 @@
1
- require 'test_helper'
2
- require 'roar/json/collection_json'
3
-
4
- if RUBY_ENGINE != "rbx"
5
- class CollectionJsonTest < MiniTest::Spec
6
- let(:song) { OpenStruct.new(:title => "scarifice", :length => 43) }
7
-
8
- representer_for([Roar::JSON::CollectionJSON]) do
9
- version "1.0"
10
- href { "//songs/" }
11
-
12
- link(:feed) { "//songs/feed" }
13
-
14
- items(:class => Song) do
15
- href { "//songs/scarifice" }
16
-
17
- property :title, :prompt => "Song title"
18
- property :length, :prompt => "Song length"
19
-
20
- link(:download) { "//songs/scarifice.mp3" }
21
- link(:stats) { "//songs/scarifice/stats" }
22
- end
23
-
24
- template do
25
- property :title, :prompt => "Song title"
26
- property :length, :prompt => "Song length"
27
- end
28
-
29
- queries do
30
- link :search do
31
- {:href => "//search", :data => [{:name => "q", :value => ""}]}
32
- end
33
- end
34
- end
35
-
36
- describe "#to_json" do
37
- it "renders document" do
38
- [song].extend(rpr).to_hash.must_equal(
39
- {
40
- "collection"=>{
41
- "version"=>"1.0",
42
- "href"=>"//songs/",
43
-
44
- "template"=>{
45
- :data=>[
46
- {:name=>"title", :value=>nil},
47
- {:name=>"length", :value=>nil}
48
- ]
49
- },
50
-
51
- "queries"=>[
52
- {"rel"=>"search", "href"=>"//search",
53
- "data"=>[
54
- {:name=>"q", :value=>""}
55
- ]
56
- }
57
- ],
58
-
59
- "items"=>[
60
- {
61
- "links"=>[
62
- {"rel"=>"download", "href"=>"//songs/scarifice.mp3"},
63
- {"rel"=>"stats", "href"=>"//songs/scarifice/stats"}
64
- ],
65
- "href"=>"//songs/scarifice",
66
- :data=>[
67
- {:name=>"title", :value=>"scarifice"},
68
- {:name=>"length", :value=>43}
69
- ]
70
- }
71
- ],
72
-
73
- "links"=>[
74
- {"rel"=>"feed", "href"=>"//songs/feed"}
75
- ]
76
- }
77
- })# %{{"collection":{"version":"1.0","href":"//songs/","items":[{"href":"//songs/scarifice","links":[{"rel":"download","href":"//songs/scarifice.mp3"},{"rel":"stats","href":"//songs/scarifice/stats"}],"data":[{"name":"title","value":"scarifice"},{"name":"length","value":43}]}],"template":{"data":[{"name":"title","value":null},{"name":"length","value":null}]},"queries":[{"rel":"search","href":"//search","data":[{"name":"q","value":""}]}],"links":[{"rel":"feed","href":"//songs/feed"}]}}}
78
- end
79
- end
80
-
81
- describe "#from_json" do
82
- subject { [].extend(rpr).from_json [song].extend(rpr).to_json }
83
-
84
- it "provides #version" do
85
- subject.version.must_equal "1.0"
86
- end
87
-
88
- it "provides #href" do
89
- subject.href.must_equal link(:href => "//songs/")
90
- end
91
-
92
- it "provides #template" do
93
- # DISCUSS: this might return a Template instance, soon.
94
- subject.template.must_equal([
95
- {"name"=>"title", "value"=>nil},
96
- {"name"=>"length", "value"=>nil}])
97
- end
98
-
99
- it "provides #queries" do
100
- # DISCUSS: this might return CollectionJSON::Hyperlink instances that support some kind of substitution operation for the :data attribute.
101
- # FIXME: this is currently _not_ parsed!
102
- subject.queries.must_equal([link(:rel => :search, :href=>"//search", :data=>[{:name=>"q", :value=>""}])])
103
- end
104
-
105
- it "provides #items" do
106
- subject.items.must_equal([Song.new(:title => "scarifice", :length => "43")])
107
- song = subject.items.first
108
- song.title.must_equal "scarifice"
109
- song.length.must_equal 43
110
- song.links.must_equal("download" => link({:rel=>"download", :href=>"//songs/scarifice.mp3"}), "stats" => link({:rel=>"stats", :href=>"//songs/scarifice/stats"}))
111
- song.href.must_equal link(:href => "//songs/scarifice")
112
- end
113
-
114
- it "provides #links" do
115
- subject.links.must_equal({"feed" => link(:rel => "feed", :href => "//songs/feed")})
116
- end
117
- end
118
-
119
- describe "template_representer#from_json" do
120
- it "parses object" do
121
- song = OpenStruct.new.extend(rpr.template_representer).from_hash(
122
- "template"=>{
123
- "data"=>[
124
- {"name"=>"title", "value"=>"Black Star"},
125
- {"name"=>"length", "value"=>"4.53"}
126
- ]
127
- })
128
- song.title.must_equal "Black Star"
129
- end
130
- end
131
- end
132
- end
@@ -1,31 +0,0 @@
1
- require 'ostruct'
2
- require 'test_helper'
3
- require 'roar/json/hal'
4
-
5
-
6
- class HalLinkTest < MiniTest::Spec
7
- let(:rpr) do
8
- Module.new do
9
- include Roar::JSON
10
- include Roar::JSON::HAL::Links
11
- link :self do
12
- "//songs"
13
- end
14
- end
15
- end
16
-
17
- subject { Object.new.extend(rpr) }
18
-
19
- describe "#to_json" do
20
- it "uses 'links' key" do
21
- subject.to_json.must_equal "{\"links\":{\"self\":{\"href\":\"//songs\"}}}"
22
- end
23
- end
24
-
25
- describe "#from_json" do
26
- it "uses 'links' key" do
27
- subject.from_json("{\"links\":{\"self\":{\"href\":\"//lifer\"}}}").links.values.must_equal [link("href" => "//lifer", "rel" => "self")]
28
- end
29
- end
30
- end
31
-
@@ -1,451 +0,0 @@
1
- require 'test_helper'
2
- require 'roar/json/json_api'
3
- require 'json'
4
-
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
- )
20
-
21
- }
22
-
23
- # minimal resource, singular
24
- module MinimalSingular
25
- include Roar::JSON::JSONAPI
26
- type :songs
27
-
28
- property :id
29
- end
30
-
31
- class MinimalSingularDecorator < Roar::Decorator
32
- include Roar::JSON::JSONAPI
33
- type :songs
34
-
35
- property :id
36
- end
37
-
38
- [MinimalSingular, MinimalSingularDecorator].each do |representer|
39
- describe "minimal singular with #{representer}" do
40
- subject { representer.prepare(song) }
41
-
42
- it { subject.to_json.must_equal "{\"songs\":{\"id\":\"1\"}}" }
43
- it { subject.from_json("{\"songs\":{\"id\":\"2\"}}").id.must_equal "2" }
44
- end
45
- end
46
-
47
- module Singular
48
- include Roar::JSON::JSONAPI
49
- type :songs
50
-
51
- property :id
52
- property :title, if: lambda { |args| args[:omit_title] != true }
53
-
54
- # local per-model "id" links
55
- links do
56
- property :album_id, :as => :album
57
- collection :musician_ids, :as => :musicians
58
- end
59
- has_one :composer
60
- has_many :listeners
61
-
62
-
63
- # global document links.
64
- link "songs.album" do
65
- {
66
- type: "album",
67
- href: "http://example.com/albums/{songs.album}"
68
- }
69
- end
70
-
71
- compound do
72
- property :album do
73
- property :title
74
- end
75
-
76
- collection :musicians do
77
- property :name
78
- end
79
- end
80
- end
81
-
82
- class SingularDecorator < Roar::Decorator
83
- include Roar::JSON::JSONAPI
84
- type :songs
85
-
86
- property :id
87
- property :title, if: lambda { |args| args[:omit_title] != true }
88
-
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
96
- end
97
- has_many :listeners
98
-
99
-
100
- # global document links.
101
- link "songs.album" do
102
- {
103
- type: "album",
104
- href: "http://example.com/albums/{songs.album}"
105
- }
106
- end
107
-
108
- compound do
109
- property :album do
110
- property :title
111
- end
112
-
113
- collection :musicians do
114
- property :name
115
- end
116
- end
117
- end
118
-
119
- [Singular, SingularDecorator].each do |representer|
120
- describe "singular with #{representer}" do
121
- subject { song.extend(Singular) }
122
-
123
- let (:document) do
124
- {
125
- "songs" => {
126
- "id" => "1",
127
- "title" => "Computadores Fazem Arte",
128
- "links" => {
129
- "album" => "9",
130
- "musicians" => [ "1", "2" ],
131
- "composer"=>"10",
132
- "listeners"=>["8"]
133
- }
134
- },
135
- "links" => {
136
- "songs.album"=> {
137
- "href"=>"http://example.com/albums/{songs.album}", "type"=>"album"
138
- }
139
- },
140
- "linked" => {
141
- "album"=> [{"title"=>"Hackers"}],
142
- "musicians"=> [
143
- {"name"=>"Eddie Van Halen"},
144
- {"name"=>"Greg Howe"}
145
- ]
146
- }
147
- }
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
- )
189
-
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
197
- end
198
- end
199
-
200
-
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
207
- {
208
- "songs" => [
209
- {
210
- "id" => "1",
211
- "title" => "Computadores Fazem Arte",
212
- "links" => {
213
- "composer"=>"10",
214
- "album" => "9",
215
- "musicians" => [ "1", "2" ],
216
- "listeners"=>["8"]
217
- }
218
- }, {
219
- "id" => "1",
220
- "title" => "Computadores Fazem Arte",
221
- "links" => {
222
- "composer"=>"10",
223
- "album" => "9",
224
- "musicians" => [ "1", "2" ],
225
- "listeners"=>["8"]
226
- }
227
- }
228
- ],
229
- "links" => {
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"}]
238
- }
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
- )
293
- )
294
-
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
309
- end
310
-
311
-
312
- class CollectionWithoutCompound < self
313
- module Representer
314
- include Roar::JSON::JSONAPI
315
- type :songs
316
-
317
- property :id
318
- property :title
319
-
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
327
-
328
-
329
- # global document links.
330
- link "songs.album" do
331
- {
332
- type: "album",
333
- href: "http://example.com/albums/{songs.album}"
334
- }
335
- end
336
- end
337
-
338
- subject { [song, song].extend(Singular.for_collection) }
339
-
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
- }
347
- }
348
- )
349
- end
350
- end
351
-
352
- class CompoundCollectionUsingExtend < self
353
- module SongRepresenter
354
- include Roar::JSON::JSONAPI
355
-
356
- type :songs
357
- property :id
358
- property :title
359
- end
360
-
361
- module AlbumRepresenter
362
- include Roar::JSON::JSONAPI
363
-
364
- type :albums
365
- property :id
366
- compound do
367
- collection :songs, extend: SongRepresenter
368
- end
369
- end
370
-
371
- let(:songs) do
372
- struct = Struct.new(:id, :title)
373
- [struct.new(1, 'Stand Up'), struct.new(2, 'Audition Mantra')]
374
- end
375
-
376
- let(:album) { Struct.new(:id, :songs).new(1, songs) }
377
-
378
- subject { album.extend(AlbumRepresenter) }
379
-
380
- # to_hash
381
- it do
382
- subject.to_hash.must_equal({
383
- 'albums' => { 'id' => 1 },
384
- 'linked' => {
385
- 'songs' => [
386
- {'id' => 1, 'title' => 'Stand Up'},
387
- {'id' => 2, 'title' => 'Audition Mantra'}
388
- ]
389
- }
390
- })
391
- end
392
- end
393
-
394
- class ExplicitMeta < self
395
- module Representer
396
- include Roar::JSON::JSONAPI
397
-
398
- type :songs
399
- property :id
400
-
401
- meta do
402
- property :page
403
- end
404
- end
405
-
406
- module Page
407
- def page
408
- 2
409
- end
410
- end
411
-
412
- let (:song) { Struct.new(:id).new(1) }
413
-
414
- subject { [song, song].extend(Representer.for_collection).extend(Page) }
415
-
416
- # to_json
417
- it do
418
- subject.to_hash.must_equal(
419
- {
420
- "songs"=>[{"id"=>1}, {"id"=>1}],
421
- "meta" =>{"page"=>2}
422
- }
423
- )
424
- end
425
- end
426
-
427
-
428
- class ImplicitMeta < self
429
- module Representer
430
- include Roar::JSON::JSONAPI
431
-
432
- type :songs
433
- property :id
434
- end
435
-
436
- let (:song) { Struct.new(:id).new(1) }
437
-
438
- subject { [song, song].extend(Representer.for_collection) }
439
-
440
- # to_json
441
- it do
442
- subject.to_hash("meta" => {"page" => 2}).must_equal(
443
- {
444
- "songs"=>[{"id"=>1}, {"id"=>1}],
445
- "meta" =>{"page"=>2}
446
- }
447
- )
448
- end
449
- end
450
- end
451
- end