roar 1.0.4 → 1.1.0
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 +4 -4
- data/.travis.yml +14 -6
- data/CHANGES.markdown +75 -58
- data/CONTRIBUTING.md +31 -0
- data/Gemfile +12 -2
- data/ISSUE_TEMPLATE.md +20 -0
- data/LICENSE +1 -1
- data/README.markdown +126 -250
- data/Rakefile +3 -1
- data/examples/example.rb +0 -0
- data/examples/example_server.rb +0 -0
- data/lib/roar.rb +3 -3
- 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.rb +5 -5
- data/lib/roar/json/collection.rb +10 -2
- data/lib/roar/json/hal.rb +72 -82
- data/lib/roar/version.rb +1 -1
- data/lib/roar/xml.rb +1 -1
- data/roar.gemspec +6 -6
- 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 +98 -106
- 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 +22 -36
- 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/Rakefile
CHANGED
@@ -7,6 +7,8 @@ task :default => [:test]
|
|
7
7
|
|
8
8
|
Rake::TestTask.new(:test) do |test|
|
9
9
|
test.libs << 'test'
|
10
|
-
test.test_files = FileList
|
10
|
+
test.test_files = FileList.new('test/**/*_test.rb') do |fl|
|
11
|
+
fl.exclude('test/integration/**') if RUBY_VERSION < '2.2.2'
|
12
|
+
end
|
11
13
|
test.verbose = true
|
12
14
|
end
|
data/examples/example.rb
CHANGED
File without changes
|
data/examples/example_server.rb
CHANGED
File without changes
|
data/lib/roar.rb
CHANGED
data/lib/roar/client.rb
CHANGED
@@ -2,7 +2,7 @@ require "roar/http_verbs"
|
|
2
2
|
|
3
3
|
module Roar
|
4
4
|
|
5
|
-
# Mix in HttpVerbs.
|
5
|
+
# Mix in HttpVerbs.
|
6
6
|
module Client
|
7
7
|
include HttpVerbs
|
8
8
|
|
@@ -28,12 +28,17 @@ module Roar
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def to_hash(options={})
|
31
|
-
options[:links] ||= false
|
31
|
+
# options[:links] ||= false
|
32
|
+
options[:user_options] ||= {}
|
33
|
+
options[:user_options][:links] ||= false
|
34
|
+
|
32
35
|
super(options)
|
33
36
|
end
|
34
37
|
|
35
38
|
def to_xml(options={}) # sorry, but i'm not even sure if anyone uses this module.
|
36
|
-
options[:
|
39
|
+
options[:user_options] ||= {}
|
40
|
+
options[:user_options][:links] ||= false
|
41
|
+
|
37
42
|
super(options)
|
38
43
|
end
|
39
44
|
end
|
data/lib/roar/decorator.rb
CHANGED
@@ -4,8 +4,8 @@ require 'representable/decorator'
|
|
4
4
|
class Roar::Decorator < Representable::Decorator
|
5
5
|
module HypermediaConsumer
|
6
6
|
def links=(arr)
|
7
|
-
|
8
|
-
represented.instance_variable_set :@links, links
|
7
|
+
super
|
8
|
+
represented.instance_variable_set :@links, self.links
|
9
9
|
end
|
10
10
|
end
|
11
11
|
end
|
data/lib/roar/http_verbs.rb
CHANGED
@@ -68,21 +68,5 @@ module Roar
|
|
68
68
|
def http
|
69
69
|
transport_engine.new
|
70
70
|
end
|
71
|
-
|
72
|
-
def handle_deprecated_args(body, *args) # TODO: remove in 1.0.
|
73
|
-
options = args.first
|
74
|
-
|
75
|
-
if args.size > 1
|
76
|
-
warn %{DEPRECATION WARNING: #get, #post, #put, #delete and #patch no longer accept positional arguments. Please call them as follows:
|
77
|
-
get(uri: "http://localhost/songs", as: "application/json")
|
78
|
-
post(uri: "http://localhost/songs", as: "application/json")
|
79
|
-
Thank you and have a beautiful day.}
|
80
|
-
options = {:uri => args[0], :as => args[1]} if args.size == 2
|
81
|
-
options = {:uri => args[0], :as => args[2]}
|
82
|
-
end
|
83
|
-
|
84
|
-
options[:body] = body
|
85
|
-
options
|
86
|
-
end
|
87
71
|
end
|
88
72
|
end
|
data/lib/roar/hypermedia.rb
CHANGED
@@ -33,33 +33,26 @@ module Roar
|
|
33
33
|
#
|
34
34
|
# model.to_json(:id => 1)
|
35
35
|
module Hypermedia
|
36
|
-
# links= [Hyperlink, Hyperlink] is where parsing happens.
|
37
36
|
def self.included(base)
|
38
37
|
base.extend ClassMethods
|
39
38
|
end
|
40
39
|
|
41
|
-
|
42
|
-
|
40
|
+
# public API: #links (only helpful in clients, though).
|
41
|
+
attr_writer :links # this is called in parsing when Hyperlinks are deserialized.
|
42
|
+
def links # this is _not_ called by rendering as we go via ::links_config.
|
43
|
+
tuples = (@links||[]).collect { |link| [link.rel, link] }
|
44
|
+
# tuples.to_h
|
45
|
+
::Hash[tuples] # TODO: tuples.to_h when dropping < 2.1.
|
43
46
|
end
|
44
47
|
|
45
|
-
attr_reader :links # this is only useful after parsing.
|
46
|
-
|
47
|
-
|
48
|
-
module LinkConfigsMethod
|
49
|
-
def link_configs # we could store the ::link configs in links Definition.
|
50
|
-
representable_attrs[:links] ||= Representable::Inheritable::Array.new
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
include LinkConfigsMethod
|
55
|
-
|
56
48
|
private
|
57
49
|
# Create hypermedia links for this instance by invoking their blocks.
|
58
50
|
# This is called in links: getter: {}.
|
59
51
|
def prepare_links!(options)
|
60
|
-
return [] if options[:links] == false
|
52
|
+
return [] if (options[:user_options] || {})[:links] == false
|
61
53
|
|
62
|
-
|
54
|
+
link_configs = representable_attrs["links"].link_configs
|
55
|
+
compile_links_for(link_configs, options)
|
63
56
|
end
|
64
57
|
|
65
58
|
def compile_links_for(configs, *args)
|
@@ -72,41 +65,12 @@ module Roar
|
|
72
65
|
end
|
73
66
|
|
74
67
|
def prepare_link_for(href, options)
|
75
|
-
options = options.merge(href.is_a?(::Hash) ? href : {:
|
68
|
+
options = options.merge(href.is_a?(::Hash) ? href : {href: href})
|
76
69
|
Hyperlink.new(options)
|
77
70
|
end
|
78
71
|
|
79
|
-
def run_link_block(block,
|
80
|
-
instance_exec(
|
81
|
-
end
|
82
|
-
|
83
|
-
|
84
|
-
# LinkCollection keeps an array of Hyperlinks to be rendered (setup in #prepare_links!)
|
85
|
-
# or parsed (array is passed to #links= which transforms it into a LinkCollection).
|
86
|
-
# It is implemented as a hash and keys links by their rel value.
|
87
|
-
#
|
88
|
-
# {"self" => <Hyperlink ..>, ..}
|
89
|
-
class LinkCollection < ::Hash
|
90
|
-
# The only way to create is LinkCollection[<Hyperlink>, <Hyperlink>]
|
91
|
-
def self.[](*arr)
|
92
|
-
super(arr.collect { |link| [link.rel, link] })
|
93
|
-
end
|
94
|
-
|
95
|
-
def [](rel)
|
96
|
-
super(rel.to_s)
|
97
|
-
end
|
98
|
-
|
99
|
-
# Iterating links. Block parameters: |link| or |rel, link|.
|
100
|
-
# This is used Hash::HashBinding#serialize.
|
101
|
-
def each(&block)
|
102
|
-
return values.each(&block) if block.arity == 1
|
103
|
-
super(&block)
|
104
|
-
end
|
105
|
-
|
106
|
-
def collect(&block) # TODO: remove me when we drop representable 2.0.x support!
|
107
|
-
return values.collect(&block) if block.arity == 1
|
108
|
-
super(&block)
|
109
|
-
end
|
72
|
+
def run_link_block(block, options)
|
73
|
+
instance_exec(options[:user_options], &block)
|
110
74
|
end
|
111
75
|
|
112
76
|
|
@@ -122,23 +86,34 @@ module Roar
|
|
122
86
|
# The block is executed in instance context, so you may call properties or other accessors.
|
123
87
|
# Note that you're free to put decider logic into #link blocks, too.
|
124
88
|
def link(options, &block)
|
125
|
-
|
89
|
+
heritage.record(:link, options, &block)
|
90
|
+
|
91
|
+
links_dfn = create_links_definition! # this assures the links are rendered at the right position.
|
126
92
|
|
127
93
|
options = {:rel => options} unless options.is_a?(::Hash)
|
128
|
-
link_configs << [options, block]
|
129
|
-
end
|
130
94
|
|
131
|
-
|
95
|
+
links_dfn.link_configs << [options, block]
|
96
|
+
end
|
132
97
|
|
133
98
|
private
|
134
99
|
# Add a :links Definition to the representable_attrs so they get rendered/parsed.
|
135
100
|
def create_links_definition!
|
136
|
-
|
101
|
+
dfn = definitions["links"] and return dfn # only create it once.
|
137
102
|
|
138
103
|
options = links_definition_options
|
139
|
-
options.merge!(:
|
104
|
+
options.merge!(getter: ->(opts) { prepare_links!(opts) })
|
105
|
+
|
106
|
+
dfn = build_definition(:links, options)
|
107
|
+
|
108
|
+
|
109
|
+
dfn.extend(DefinitionOptions)
|
110
|
+
dfn
|
111
|
+
end
|
112
|
+
end
|
140
113
|
|
141
|
-
|
114
|
+
module DefinitionOptions
|
115
|
+
def link_configs
|
116
|
+
@link_configs ||= []
|
142
117
|
end
|
143
118
|
end
|
144
119
|
|
@@ -172,7 +147,6 @@ module Roar
|
|
172
147
|
attrs.inject({}) { |hsh, kv| hsh[kv.first.to_s] = kv.last; hsh }.tap do |hsh|
|
173
148
|
hsh["rel"] = hsh["rel"].to_s if hsh["rel"]
|
174
149
|
end
|
175
|
-
# raise "Hyperlink without rel doesn't work!" unless @attrs["rel"]
|
176
150
|
end
|
177
151
|
end
|
178
152
|
end
|
data/lib/roar/json.rb
CHANGED
@@ -37,10 +37,10 @@ module Roar
|
|
37
37
|
def links_definition_options
|
38
38
|
# FIXME: this doesn't belong into the generic JSON representer.
|
39
39
|
{
|
40
|
-
:
|
41
|
-
:
|
42
|
-
:
|
43
|
-
:
|
40
|
+
class: Hypermedia::Hyperlink,
|
41
|
+
decorator: HyperlinkDecorator,
|
42
|
+
collection: true,
|
43
|
+
exec_context: :decorator
|
44
44
|
}
|
45
45
|
end
|
46
46
|
end
|
@@ -48,7 +48,7 @@ module Roar
|
|
48
48
|
|
49
49
|
require "representable/json/hash"
|
50
50
|
# Represents a hyperlink in standard roar+json hash representation.
|
51
|
-
|
51
|
+
class HyperlinkDecorator < Representable::Decorator
|
52
52
|
include Representable::JSON::Hash
|
53
53
|
end
|
54
54
|
end
|
data/lib/roar/json/collection.rb
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
-
require
|
1
|
+
require 'roar/json'
|
2
2
|
|
3
|
-
Roar::JSON
|
3
|
+
module Roar::JSON
|
4
|
+
module Collection
|
5
|
+
include Roar::JSON
|
6
|
+
|
7
|
+
def self.included(base)
|
8
|
+
base.send :include, Representable::Hash::Collection
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/roar/json/hal.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
require
|
1
|
+
require "roar/json"
|
2
|
+
require "representable/json/collection"
|
3
|
+
require "representable/json/hash"
|
2
4
|
|
3
5
|
module Roar
|
4
6
|
module JSON
|
@@ -46,8 +48,8 @@ module Roar
|
|
46
48
|
base.class_eval do
|
47
49
|
include Roar::JSON
|
48
50
|
include Links # overwrites #links_definition_options.
|
49
|
-
extend ClassMethods # overwrites #links_definition_options, again.
|
50
51
|
include Resources
|
52
|
+
include LinksReader # gives us Decorator#links => {self=>< >}
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
@@ -56,7 +58,7 @@ module Roar
|
|
56
58
|
super.tap do |hash|
|
57
59
|
embedded = {}
|
58
60
|
representable_attrs.find_all do |dfn|
|
59
|
-
name = dfn[:as].(nil) # DISCUSS: should we simplify that in Representable?
|
61
|
+
name = dfn[:as] ? dfn[:as].(nil) : dfn.name # DISCUSS: should we simplify that in Representable?
|
60
62
|
next unless dfn[:embedded] and fragment = hash.delete(name)
|
61
63
|
embedded[name] = fragment
|
62
64
|
end
|
@@ -72,38 +74,7 @@ module Roar
|
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
75
|
-
module ClassMethods
|
76
|
-
def links_definition_options
|
77
|
-
super.merge(:as => :_links)
|
78
|
-
end
|
79
|
-
end
|
80
77
|
|
81
|
-
class LinkCollection < Hypermedia::LinkCollection
|
82
|
-
def initialize(array_rels, *args)
|
83
|
-
super(*args)
|
84
|
-
@array_rels = array_rels.map(&:to_s)
|
85
|
-
end
|
86
|
-
|
87
|
-
def is_array?(rel)
|
88
|
-
@array_rels.include?(rel.to_s)
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
# Including this module in your representer will render and parse your embedded hyperlinks
|
93
|
-
# following the HAL specification: http://stateless.co/hal_specification.html
|
94
|
-
#
|
95
|
-
# module SongRepresenter
|
96
|
-
# include Roar::JSON
|
97
|
-
# include Roar::JSON::HAL::Links
|
98
|
-
#
|
99
|
-
# link :self { "http://self" }
|
100
|
-
# end
|
101
|
-
#
|
102
|
-
# Renders to
|
103
|
-
#
|
104
|
-
# {"links":{"self":{"href":"http://self"}}}
|
105
|
-
#
|
106
|
-
# Note that the HAL::Links module alone doesn't prepend an underscore to +links+. Use the JSON::HAL module for that.
|
107
78
|
module Links
|
108
79
|
def self.included(base)
|
109
80
|
base.extend ClassMethods # ::links_definition_options
|
@@ -112,83 +83,84 @@ module Roar
|
|
112
83
|
end
|
113
84
|
|
114
85
|
module InstanceMethods
|
86
|
+
def _links
|
87
|
+
links
|
88
|
+
end
|
89
|
+
|
115
90
|
private
|
116
91
|
def prepare_link_for(href, options)
|
117
|
-
return super(href, options) unless options[:array] #
|
92
|
+
return super(href, options) unless options[:array] # returns Hyperlink.
|
118
93
|
|
119
|
-
|
120
|
-
LinkArray.new(list, options[:rel])
|
121
|
-
end
|
122
|
-
|
123
|
-
# TODO: move to LinksDefinition.
|
124
|
-
def link_array_rels
|
125
|
-
link_configs.collect { |cfg| cfg.first[:array] ? cfg.first[:rel] : nil }.compact
|
94
|
+
ArrayLink.new(options[:rel], href.collect { |opts| Hypermedia::Hyperlink.new(opts) })
|
126
95
|
end
|
127
96
|
end
|
128
97
|
|
129
98
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
values :extend => lambda { |item, *|
|
135
|
-
item.is_a?(Array) ? LinkArrayRepresenter : Roar::JSON::HyperlinkRepresenter },
|
136
|
-
:instance => lambda { |fragment, *| fragment.is_a?(LinkArray) ? fragment : Roar::Hypermedia::Hyperlink.new
|
137
|
-
}
|
99
|
+
class SingleLink
|
100
|
+
class Representer < Representable::Decorator
|
101
|
+
include Representable::JSON::Hash
|
138
102
|
|
139
|
-
|
140
|
-
|
141
|
-
|
103
|
+
def to_hash(*)
|
104
|
+
hash = super
|
105
|
+
{hash.delete("rel").to_s => hash}
|
142
106
|
end
|
143
107
|
end
|
144
|
-
|
145
|
-
|
146
|
-
def from_hash(hash, *args)
|
147
|
-
hash.each { |k,v| hash[k] = LinkArray.new(v, k) if is_array?(k) }
|
148
|
-
|
149
|
-
hsh = super(hash) # this is where :class and :extend do the work.
|
150
|
-
|
151
|
-
hsh.each { |k, v| v.merge!(:rel => k) }
|
152
|
-
hsh.values # links= expects [Hyperlink, Hyperlink]
|
153
|
-
end
|
154
108
|
end
|
155
109
|
|
156
|
-
|
157
|
-
|
158
|
-
def initialize(elems, rel)
|
159
|
-
super(elems)
|
110
|
+
class ArrayLink < Array
|
111
|
+
def initialize(rel, links)
|
160
112
|
@rel = rel
|
113
|
+
super(links)
|
161
114
|
end
|
162
|
-
|
163
115
|
attr_reader :rel
|
164
116
|
|
165
|
-
|
166
|
-
|
117
|
+
|
118
|
+
# [Hyperlink, Hyperlink]
|
119
|
+
class Representer < Representable::Decorator
|
120
|
+
include Representable::JSON::Collection
|
121
|
+
|
122
|
+
items extend: SingleLink::Representer,
|
123
|
+
class: Roar::Hypermedia::Hyperlink
|
124
|
+
|
125
|
+
def to_hash(*)
|
126
|
+
links = super.flat_map(&:values) # [{"self"=>{"href": ..}}, ..]
|
127
|
+
|
128
|
+
{ represented.rel.to_s => links } # {"self"=>[{"lang"=>"en", "href"=>"http://en.hit"}, {"lang"=>"de", "href"=>"http://de.hit"}]}
|
129
|
+
end
|
167
130
|
end
|
168
131
|
end
|
169
132
|
|
170
|
-
|
171
|
-
|
133
|
+
|
134
|
+
# Represents all links for "_links": [Hyperlink, [Hyperlink, Hyperlink]]
|
135
|
+
class Representer < Representable::Decorator # links could be a simple collection property.
|
172
136
|
include Representable::JSON::Collection
|
173
137
|
|
174
|
-
|
175
|
-
|
138
|
+
# render: decorates represented.links with ArrayLink::R or SingleLink::R and calls #to_hash.
|
139
|
+
# parse: instantiate either Array or Hypermedia instance, decorate respectively, call #from_hash.
|
140
|
+
items decorator: ->(options) { options[:input].is_a?(Array) ? ArrayLink::Representer : SingleLink::Representer },
|
141
|
+
class: ->(options) { options[:input].is_a?(Array) ? Array : Hypermedia::Hyperlink }
|
142
|
+
|
143
|
+
def to_hash(options)
|
144
|
+
super.inject({}) { |links, hash| links.merge!(hash) } # [{ rel=>{}, rel=>[{}, {}] }]
|
145
|
+
end
|
176
146
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
147
|
+
def from_hash(hash, *args)
|
148
|
+
collection = hash.collect do |rel, value| # "self" => [{"href": "//"}, ] or "self" => {"href": "//"}
|
149
|
+
value.is_a?(Array) ? value.collect { |link| link.merge("rel"=>rel) } : value.merge("rel"=>rel)
|
180
150
|
end
|
151
|
+
|
152
|
+
super(collection) # [{rel=>self, href=>//}, ..] or {rel=>self, href=>//}
|
181
153
|
end
|
182
154
|
end
|
183
155
|
|
184
156
|
|
185
157
|
module ClassMethods
|
186
158
|
def links_definition_options
|
187
|
-
# property :links_array,
|
188
159
|
{
|
189
|
-
:
|
190
|
-
:
|
191
|
-
:
|
160
|
+
# collection: false,
|
161
|
+
:as => :_links,
|
162
|
+
decorator: Links::Representer,
|
163
|
+
instance: ->(*) { Array.new }, # defined in InstanceMethods as this is executed in represented context.
|
192
164
|
:exec_context => :decorator,
|
193
165
|
}
|
194
166
|
end
|
@@ -200,7 +172,7 @@ module Roar
|
|
200
172
|
# {:lang => "de", :href => "http://de.hit"}]
|
201
173
|
# end
|
202
174
|
def links(options, &block)
|
203
|
-
options = {:rel => options} if options.is_a?(Symbol)
|
175
|
+
options = {:rel => options} if options.is_a?(Symbol) || options.is_a?(String)
|
204
176
|
options[:array] = true
|
205
177
|
link(options, &block)
|
206
178
|
end
|
@@ -218,6 +190,24 @@ module Roar
|
|
218
190
|
end
|
219
191
|
end
|
220
192
|
end
|
193
|
+
|
194
|
+
# This is only helpful in client mode. It shouldn't be used per default.
|
195
|
+
module LinksReader
|
196
|
+
def links
|
197
|
+
return unless @links
|
198
|
+
tuples = @links.collect do |link|
|
199
|
+
if link.is_a?(Array)
|
200
|
+
next unless link.any?
|
201
|
+
[link.first.rel, link]
|
202
|
+
else
|
203
|
+
[link.rel, link]
|
204
|
+
end
|
205
|
+
end.compact
|
206
|
+
|
207
|
+
# tuples.to_h
|
208
|
+
::Hash[tuples] # TODO: tuples.to_h when dropping < 2.1.
|
209
|
+
end
|
210
|
+
end
|
221
211
|
end
|
222
212
|
end
|
223
213
|
end
|