roar 0.11.8 → 0.11.9
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.
- data/CHANGES.markdown +5 -0
- data/Gemfile +1 -1
- data/TODO.markdown +2 -0
- data/gemfiles/Gemfile.representable-1.3 +1 -1
- data/lib/roar/representer/feature/client.rb +4 -0
- data/lib/roar/representer/feature/hypermedia.rb +12 -7
- data/lib/roar/representer/json/collection_json.rb +208 -0
- data/lib/roar/representer/transport/net_http.rb +1 -0
- data/lib/roar/version.rb +1 -1
- data/test/client_test.rb +20 -14
- data/test/collection_json_test.rb +130 -0
- data/test/test_helper.rb +2 -1
- metadata +4 -2
data/CHANGES.markdown
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## 0.11.9
|
2
|
+
|
3
|
+
* When using `Feature::Client` hyperlinks are no longer rendered in POST and PUT since we pass `links: false`.
|
4
|
+
* `Transport::NetHttp` now sets both `Accept:` and `Content-type:` header since Rails services seems to get confused.
|
5
|
+
|
1
6
|
## 0.11.8
|
2
7
|
|
3
8
|
* Fixed `JSON::HAL::Links` so that it keys links with `links` and not `_links`. The latter is still done by `JSON::HAL`.
|
data/Gemfile
CHANGED
data/TODO.markdown
CHANGED
@@ -57,9 +57,7 @@ module Roar
|
|
57
57
|
def links_array=(ary)
|
58
58
|
# FIXME: move to LinkCollection
|
59
59
|
self.links= LinkCollection.new
|
60
|
-
ary.each
|
61
|
-
self.links[lnk.rel.to_s] = lnk
|
62
|
-
end
|
60
|
+
ary.each { |lnk| links.add(lnk) }
|
63
61
|
end
|
64
62
|
|
65
63
|
private
|
@@ -69,12 +67,19 @@ module Roar
|
|
69
67
|
|
70
68
|
# Setup hypermedia links by invoking their blocks. Usually called by #serialize.
|
71
69
|
def prepare_links!(*args)
|
72
|
-
|
70
|
+
# TODO: move this method to _links or something so it doesn't need to be called in #serialize.
|
71
|
+
compile_links_for(links_definition, *args).each do |lnk|
|
72
|
+
links.add(lnk) # TODO: move to LinkCollection.new.
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def compile_links_for(configs, *args)
|
77
|
+
configs.collect do |config|
|
73
78
|
options, block = config.first, config.last
|
74
79
|
href = run_link_block(block, *args) or next
|
75
80
|
|
76
|
-
|
77
|
-
end
|
81
|
+
prepare_link_for(href, options)
|
82
|
+
end.compact # FIXME: make this less ugly.
|
78
83
|
end
|
79
84
|
|
80
85
|
def prepare_link_for(href, options)
|
@@ -93,7 +98,7 @@ module Roar
|
|
93
98
|
self.fetch(rel.to_s, nil)
|
94
99
|
end
|
95
100
|
|
96
|
-
def add(link)
|
101
|
+
def add(link)
|
97
102
|
self[link.rel.to_s] = link
|
98
103
|
end
|
99
104
|
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
module Roar::Representer::JSON
|
2
|
+
# Implementation of the Collection+JSON media format, http://amundsen.com/media-types/collection/format/
|
3
|
+
#
|
4
|
+
# When clients want to add or update an item the collection's filled out template is POSTed or PUT. You can parse that using the
|
5
|
+
# +SongCollectionRepresenter::template_representer+ module.
|
6
|
+
#
|
7
|
+
# Song.new.extend(SongCollectionRepresenter.template_representer).from_json("{template: ...")
|
8
|
+
module CollectionJSON
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
include Roar::Representer
|
12
|
+
include Roar::Representer::JSON
|
13
|
+
include Roar::Representer::Feature::Hypermedia
|
14
|
+
|
15
|
+
extend ClassMethods
|
16
|
+
|
17
|
+
self.representation_wrap= :collection # FIXME: move outside of this block for inheritance!
|
18
|
+
|
19
|
+
property :__template, :as => :template, :extend => lambda {|*| representable_attrs.collection_representers[:template] }, :class => Array
|
20
|
+
def __template
|
21
|
+
OpenStruct.new # TODO: handle preset values.
|
22
|
+
end
|
23
|
+
|
24
|
+
collection :queries, :extend => Roar::Representer::JSON::HyperlinkRepresenter
|
25
|
+
def queries
|
26
|
+
compile_links_for(representable_attrs.collection_representers[:queries].representable_attrs.first)
|
27
|
+
end
|
28
|
+
def queries=(v)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def items
|
33
|
+
self
|
34
|
+
end
|
35
|
+
def items=(v)
|
36
|
+
replace(v)
|
37
|
+
end
|
38
|
+
|
39
|
+
property :__version, :as => :version
|
40
|
+
def __version
|
41
|
+
representable_attrs.collection_representers[:version]
|
42
|
+
end
|
43
|
+
|
44
|
+
property :__href, :as => :href
|
45
|
+
def __href
|
46
|
+
compile_links_for(representable_attrs.collection_representers[:href].representable_attrs.first).first.href
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
include ClientMethods
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
module ClassMethods
|
56
|
+
module PropertyWithRenderNil
|
57
|
+
def property(name, options={})
|
58
|
+
super(name, options.merge!(:render_nil => true))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# TODO: provide automatic copying from the ItemRepresenter here.
|
63
|
+
def template(&block)
|
64
|
+
mod = representable_attrs.collection_representers[:object_template] = Module.new do
|
65
|
+
include Roar::Representer::JSON
|
66
|
+
include Roar::Representer::JSON::CollectionJSON::DataMethods
|
67
|
+
|
68
|
+
extend PropertyWithRenderNil
|
69
|
+
|
70
|
+
module_exec(&block)
|
71
|
+
|
72
|
+
#self.representation_wrap = :template
|
73
|
+
def from_hash(hash, *args) # overridden in :template representer.
|
74
|
+
super(hash["template"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
representable_attrs.collection_representers[:template] = Module.new do
|
79
|
+
include Roar::Representer::JSON
|
80
|
+
include mod
|
81
|
+
|
82
|
+
#self.representation_wrap = false
|
83
|
+
|
84
|
+
# DISCUSS: currently we skip real deserialization here and just store the :data hash.
|
85
|
+
def from_hash(hash, *args)
|
86
|
+
replace(hash["data"])
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
def template_representer
|
91
|
+
representable_attrs.collection_representers[:object_template]
|
92
|
+
end
|
93
|
+
|
94
|
+
def queries(&block)
|
95
|
+
mod = representable_attrs.collection_representers[:queries] = Module.new do
|
96
|
+
include Roar::Representer::JSON
|
97
|
+
include Roar::Representer::Feature::Hypermedia
|
98
|
+
|
99
|
+
module_exec(&block)
|
100
|
+
|
101
|
+
def to_hash(*)
|
102
|
+
hash = super
|
103
|
+
hash["links"] # TODO: make it easier to render collection of links.
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def items(options={}, &block)
|
109
|
+
collection :items, { :extend => lambda {|*| representable_attrs.collection_representers[:items] } }.merge!(options)
|
110
|
+
|
111
|
+
mod = representable_attrs.collection_representers[:items] = Module.new do
|
112
|
+
include Roar::Representer::JSON
|
113
|
+
include Roar::Representer::Feature::Hypermedia
|
114
|
+
include Roar::Representer::JSON::CollectionJSON::DataMethods
|
115
|
+
extend SharedClassMethodsBullshit
|
116
|
+
|
117
|
+
module_exec(&block)
|
118
|
+
|
119
|
+
# TODO: share with main module!
|
120
|
+
property :__href, :as => :href
|
121
|
+
def __href
|
122
|
+
compile_links_for(representable_attrs.collection_representers[:href].representable_attrs.first).first.href
|
123
|
+
end
|
124
|
+
def __href=(v)
|
125
|
+
@__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
|
126
|
+
end
|
127
|
+
def href
|
128
|
+
@__href
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def version(v)
|
134
|
+
representable_attrs.collection_representers[:version] = v
|
135
|
+
end
|
136
|
+
|
137
|
+
module SharedClassMethodsBullshit
|
138
|
+
def href(&block)
|
139
|
+
mod = representable_attrs.collection_representers[:href] = Module.new do
|
140
|
+
include Roar::Representer::JSON
|
141
|
+
include Roar::Representer::Feature::Hypermedia
|
142
|
+
|
143
|
+
|
144
|
+
link(:href, &block)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def representable_attrs
|
149
|
+
super.tap do |attrs|
|
150
|
+
attrs.instance_eval do # FIXME: of course, this is WIP.
|
151
|
+
def collection_representers
|
152
|
+
@collection_representers ||= {}
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
include SharedClassMethodsBullshit
|
159
|
+
end
|
160
|
+
|
161
|
+
module DataMethods
|
162
|
+
def to_hash(*)
|
163
|
+
hash = super.tap do |hsh|
|
164
|
+
data = []
|
165
|
+
hsh.keys.each do |n|
|
166
|
+
next if ["href", "links"].include?(n)
|
167
|
+
|
168
|
+
v = hsh.delete(n.to_s)
|
169
|
+
data << {:name => n, :value => v} # TODO: get :prompt from Definition.
|
170
|
+
end
|
171
|
+
hsh[:data] = data
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def from_hash(hash, *args)
|
176
|
+
data = {}
|
177
|
+
hash.delete("data").collect do |item|
|
178
|
+
data[item["name"]] = item["value"]
|
179
|
+
end
|
180
|
+
super(hash.merge!(data))
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
module ClientMethods
|
185
|
+
def __version=(v)
|
186
|
+
@__version = v
|
187
|
+
end
|
188
|
+
def version
|
189
|
+
@__version
|
190
|
+
end
|
191
|
+
|
192
|
+
def __href=(v)
|
193
|
+
@__href = Roar::Representer::Feature::Hypermedia::Hyperlink.new(:href => v)
|
194
|
+
end
|
195
|
+
def href
|
196
|
+
@__href
|
197
|
+
end
|
198
|
+
|
199
|
+
def __template=(v)
|
200
|
+
@__template = v
|
201
|
+
end
|
202
|
+
# DISCUSS: this might return a Template instance, soon.
|
203
|
+
def template
|
204
|
+
@__template
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -35,6 +35,7 @@ module Roar
|
|
35
35
|
http = Net::HTTP.new(uri.host, uri.port)
|
36
36
|
req = what.new(uri.request_uri)
|
37
37
|
req.content_type = as
|
38
|
+
req["accept"] = as # TODO: test me. # DISCUSS: if Accept is not set, rails treats this request as as "text/html".
|
38
39
|
req.body = body if body
|
39
40
|
http.request(req)
|
40
41
|
end
|
data/lib/roar/version.rb
CHANGED
data/test/client_test.rb
CHANGED
@@ -2,23 +2,29 @@ require 'test_helper'
|
|
2
2
|
require 'roar/representer/feature/client'
|
3
3
|
|
4
4
|
class ClientTest < MiniTest::Spec
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
5
|
+
representer_for([Roar::Representer]) do
|
6
|
+
property :name
|
7
|
+
property :band
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:song) { Object.new.extend(rpr).extend(Roar::Representer::Feature::Client) }
|
11
|
+
|
12
|
+
it "adds accessors" do
|
13
|
+
song.name = "Social Suicide"
|
14
|
+
song.band = "Bad Religion"
|
15
|
+
assert_equal "Social Suicide", song.name
|
16
|
+
assert_equal "Bad Religion", song.band
|
17
|
+
end
|
12
18
|
|
13
|
-
|
19
|
+
describe "links" do
|
20
|
+
representer_for([Roar::Representer::JSON, Roar::Representer::Feature::Hypermedia]) do
|
21
|
+
property :name
|
22
|
+
link(:self) { never_call_me! }
|
14
23
|
end
|
15
24
|
|
16
|
-
it "
|
17
|
-
|
18
|
-
|
19
|
-
@song.band = "Bad Religion"
|
20
|
-
assert_equal "Social Suicide", @song.name
|
21
|
-
assert_equal "Bad Religion", @song.band
|
25
|
+
it "suppresses rendering" do
|
26
|
+
song.name = "Silenced"
|
27
|
+
song.to_json.must_equal %{{\"name\":\"Silenced\",\"links\":[]}}
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'roar/representer/json/collection_json'
|
3
|
+
|
4
|
+
class CollectionJsonTest < MiniTest::Spec
|
5
|
+
let(:song) { OpenStruct.new(:title => "scarifice", :length => 43) }
|
6
|
+
|
7
|
+
representer_for([Roar::Representer::JSON::CollectionJSON]) do
|
8
|
+
version "1.0"
|
9
|
+
href { "//songs/" }
|
10
|
+
|
11
|
+
link(:feed) { "//songs/feed" }
|
12
|
+
|
13
|
+
items(:class => Song) do
|
14
|
+
href { "//songs/scarifice" }
|
15
|
+
|
16
|
+
property :title, :prompt => "Song title"
|
17
|
+
property :length, :prompt => "Song length"
|
18
|
+
|
19
|
+
link(:download) { "//songs/scarifice.mp3" }
|
20
|
+
link(:stats) { "//songs/scarifice/stats" }
|
21
|
+
end
|
22
|
+
|
23
|
+
template do
|
24
|
+
property :title, :prompt => "Song title"
|
25
|
+
property :length, :prompt => "Song length"
|
26
|
+
end
|
27
|
+
|
28
|
+
queries do
|
29
|
+
link :search do
|
30
|
+
{:href => "//search", :data => [{:name => "q", :value => ""}]}
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#to_json" do
|
36
|
+
it "renders document" do
|
37
|
+
[song].extend(rpr).to_hash.must_equal(
|
38
|
+
{
|
39
|
+
:collection=>{
|
40
|
+
"version"=>"1.0",
|
41
|
+
"href"=>"//songs/",
|
42
|
+
|
43
|
+
"template"=>{
|
44
|
+
:data=>[
|
45
|
+
{:name=>"title", :value=>nil},
|
46
|
+
{:name=>"length", :value=>nil}
|
47
|
+
]
|
48
|
+
},
|
49
|
+
|
50
|
+
"queries"=>[
|
51
|
+
{:rel=>:search, :href=>"//search",
|
52
|
+
:data=>[
|
53
|
+
{:name=>"q", :value=>""}
|
54
|
+
]
|
55
|
+
}
|
56
|
+
],
|
57
|
+
|
58
|
+
"items"=>[
|
59
|
+
{
|
60
|
+
"links"=>[
|
61
|
+
{:rel=>:download, :href=>"//songs/scarifice.mp3"},
|
62
|
+
{:rel=>:stats, :href=>"//songs/scarifice/stats"}
|
63
|
+
],
|
64
|
+
"href"=>"//songs/scarifice",
|
65
|
+
:data=>[
|
66
|
+
{:name=>"title", :value=>"scarifice"},
|
67
|
+
{:name=>"length", :value=>43}
|
68
|
+
]
|
69
|
+
}
|
70
|
+
],
|
71
|
+
|
72
|
+
"links"=>[
|
73
|
+
{:rel=>:feed, :href=>"//songs/feed"}
|
74
|
+
]
|
75
|
+
}
|
76
|
+
})# %{{"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"}]}}}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "#from_json" do
|
81
|
+
subject { [].extend(rpr).from_json [song].extend(rpr).to_json }
|
82
|
+
|
83
|
+
it "provides #version" do
|
84
|
+
subject.version.must_equal "1.0"
|
85
|
+
end
|
86
|
+
|
87
|
+
it "provides #href" do
|
88
|
+
subject.href.must_equal link(:href => "//songs/")
|
89
|
+
end
|
90
|
+
|
91
|
+
it "provides #template" do
|
92
|
+
# DISCUSS: this might return a Template instance, soon.
|
93
|
+
subject.template.must_equal([
|
94
|
+
{"name"=>"title", "value"=>nil},
|
95
|
+
{"name"=>"length", "value"=>nil}])
|
96
|
+
end
|
97
|
+
|
98
|
+
it "provides #queries" do
|
99
|
+
# DISCUSS: this might return CollectionJSON::Hyperlink instances that support some kind of substitution operation for the :data attribute.
|
100
|
+
# FIXME: this is currently _not_ parsed!
|
101
|
+
subject.queries.must_equal([link(:rel => :search, :href=>"//search", :data=>[{:name=>"q", :value=>""}])])
|
102
|
+
end
|
103
|
+
|
104
|
+
it "provides #items" do
|
105
|
+
subject.items.must_equal([Song.new(:title => "scarifice", :length => "43")])
|
106
|
+
song = subject.items.first
|
107
|
+
song.title.must_equal "scarifice"
|
108
|
+
song.length.must_equal 43
|
109
|
+
song.links.must_equal("download" => link({:rel=>:download, :href=>"//songs/scarifice.mp3"}), "stats" => link({:rel=>:stats, :href=>"//songs/scarifice/stats"}))
|
110
|
+
song.href.must_equal link(:href => "//songs/scarifice")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "provides #links" do
|
114
|
+
subject.links.must_equal({"feed" => link(:rel => :feed, :href => "//songs/feed")})
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe "template_representer#from_json" do
|
119
|
+
it "parses object" do
|
120
|
+
song = OpenStruct.new.extend(rpr.template_representer).from_hash(
|
121
|
+
"template"=>{
|
122
|
+
"data"=>[
|
123
|
+
{"name"=>"title", "value"=>"Black Star"},
|
124
|
+
{"name"=>"length", "value"=>"4.53"}
|
125
|
+
]
|
126
|
+
})
|
127
|
+
song.title.must_equal "Black Star"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -26,6 +26,7 @@ module AttributesConstructor # TODO: remove me.
|
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
# FIXME: provide a real #== for OpenStruct.
|
29
30
|
class Song < OpenStruct
|
30
31
|
def ==(other)
|
31
32
|
name == other.name and track == other.track
|
@@ -55,7 +56,7 @@ MiniTest::Spec.class_eval do
|
|
55
56
|
Module.new do
|
56
57
|
include *modules.reverse
|
57
58
|
|
58
|
-
|
59
|
+
module_exec(&block)
|
59
60
|
end
|
60
61
|
end
|
61
62
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: roar
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.11.
|
4
|
+
version: 0.11.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-02-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: representable
|
@@ -98,6 +98,7 @@ files:
|
|
98
98
|
- lib/roar/representer/feature/http_verbs.rb
|
99
99
|
- lib/roar/representer/feature/hypermedia.rb
|
100
100
|
- lib/roar/representer/json.rb
|
101
|
+
- lib/roar/representer/json/collection_json.rb
|
101
102
|
- lib/roar/representer/json/hal.rb
|
102
103
|
- lib/roar/representer/transport/faraday.rb
|
103
104
|
- lib/roar/representer/transport/net_http.rb
|
@@ -107,6 +108,7 @@ files:
|
|
107
108
|
- test/Gemfile
|
108
109
|
- test/client_test.rb
|
109
110
|
- test/coercion_feature_test.rb
|
111
|
+
- test/collection_json_test.rb
|
110
112
|
- test/fake_server.rb
|
111
113
|
- test/faraday_http_transport_test.rb
|
112
114
|
- test/hal_json_test.rb
|