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.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/.github/ISSUE_TEMPLATE.md +20 -0
  3. data/.travis.yml +16 -11
  4. data/CHANGES.markdown +86 -57
  5. data/CONTRIBUTING.md +31 -0
  6. data/Gemfile +7 -4
  7. data/LICENSE +1 -1
  8. data/README.markdown +133 -255
  9. data/Rakefile +3 -1
  10. data/examples/example.rb +0 -0
  11. data/examples/example_server.rb +0 -0
  12. data/lib/roar/client.rb +8 -3
  13. data/lib/roar/decorator.rb +2 -2
  14. data/lib/roar/http_verbs.rb +0 -16
  15. data/lib/roar/hypermedia.rb +30 -56
  16. data/lib/roar/json/collection.rb +10 -2
  17. data/lib/roar/json/hal.rb +74 -83
  18. data/lib/roar/json.rb +5 -5
  19. data/lib/roar/version.rb +1 -1
  20. data/lib/roar/xml.rb +1 -1
  21. data/lib/roar.rb +3 -3
  22. data/roar.gemspec +7 -5
  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 +101 -94
  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 +21 -37
  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
-