roar 1.0.2 → 1.1.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 +5 -5
- data/.github/ISSUE_TEMPLATE.md +20 -0
- data/.travis.yml +16 -11
- data/CHANGES.markdown +86 -57
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +7 -4
- data/LICENSE +1 -1
- data/README.markdown +133 -255
- data/Rakefile +3 -1
- data/examples/example.rb +0 -0
- data/examples/example_server.rb +0 -0
- data/lib/roar/client.rb +8 -3
- data/lib/roar/decorator.rb +2 -2
- data/lib/roar/http_verbs.rb +0 -16
- data/lib/roar/hypermedia.rb +30 -56
- data/lib/roar/json/collection.rb +10 -2
- data/lib/roar/json/hal.rb +74 -83
- data/lib/roar/json.rb +5 -5
- data/lib/roar/version.rb +1 -1
- data/lib/roar/xml.rb +1 -1
- data/lib/roar.rb +3 -3
- data/roar.gemspec +7 -5
- data/test/client_test.rb +1 -1
- data/test/coercion_feature_test.rb +7 -2
- data/test/decorator_test.rb +17 -7
- data/test/hal_json_test.rb +101 -94
- data/test/hypermedia_feature_test.rb +13 -31
- data/test/hypermedia_test.rb +26 -92
- data/test/{decorator_client_test.rb → integration/decorator_client_test.rb} +5 -4
- data/test/{faraday_http_transport_test.rb → integration/faraday_http_transport_test.rb} +1 -0
- data/test/{http_verbs_test.rb → integration/http_verbs_test.rb} +3 -2
- data/test/integration/json_collection_test.rb +35 -0
- data/test/{net_http_transport_test.rb → integration/net_http_transport_test.rb} +1 -0
- data/test/integration/runner.rb +2 -3
- data/test/integration/server.rb +6 -0
- data/test/json_representer_test.rb +2 -29
- data/test/lonely_test.rb +1 -2
- data/test/ssl_client_certs_test.rb +1 -1
- data/test/test_helper.rb +21 -3
- data/test/xml_representer_test.rb +6 -5
- metadata +21 -37
- data/gemfiles/Gemfile.representable-1.7 +0 -6
- data/gemfiles/Gemfile.representable-1.8 +0 -6
- data/gemfiles/Gemfile.representable-2.0 +0 -5
- data/gemfiles/Gemfile.representable-2.1 +0 -5
- data/gemfiles/Gemfile.representable-head +0 -6
- data/lib/roar/json/collection_json.rb +0 -208
- data/lib/roar/json/json_api.rb +0 -233
- data/test/collection_json_test.rb +0 -132
- data/test/hal_links_test.rb +0 -31
- data/test/json_api_test.rb +0 -451
- data/test/lib/runner.rb +0 -134
data/lib/roar/json/json_api.rb
DELETED
@@ -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
|
data/test/hal_links_test.rb
DELETED
@@ -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
|
-
|