roar 0.12.9 → 1.0.0.beta1
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/.gitignore +1 -0
- data/.travis.yml +8 -10
- data/CHANGES.markdown +24 -2
- data/Gemfile +5 -2
- data/README.markdown +132 -28
- data/TODO.markdown +9 -7
- data/examples/example.rb +2 -0
- data/examples/example_server.rb +19 -3
- data/gemfiles/Gemfile.representable-2.0 +5 -0
- data/gemfiles/Gemfile.representable-2.1 +5 -0
- data/lib/roar.rb +1 -1
- data/lib/roar/client.rb +38 -0
- data/lib/roar/{representer/feature/coercion.rb → coercion.rb} +2 -1
- data/lib/roar/decorator.rb +3 -11
- data/lib/roar/http_verbs.rb +88 -0
- data/lib/roar/hypermedia.rb +174 -0
- data/lib/roar/json.rb +55 -0
- data/lib/roar/json/collection.rb +3 -0
- data/lib/roar/{representer/json → json}/collection_json.rb +20 -20
- data/lib/roar/{representer/json → json}/hal.rb +33 -31
- data/lib/roar/json/hash.rb +3 -0
- data/lib/roar/json/json_api.rb +208 -0
- data/lib/roar/representer.rb +3 -36
- data/lib/roar/transport/faraday.rb +49 -0
- data/lib/roar/transport/net_http.rb +57 -0
- data/lib/roar/transport/net_http/request.rb +72 -0
- data/lib/roar/version.rb +1 -1
- data/lib/roar/xml.rb +54 -0
- data/roar.gemspec +5 -4
- data/test/client_test.rb +3 -3
- data/test/coercion_feature_test.rb +6 -3
- data/test/collection_json_test.rb +8 -10
- data/test/decorator_test.rb +27 -15
- data/test/faraday_http_transport_test.rb +13 -15
- data/test/hal_json_test.rb +16 -16
- data/test/hal_links_test.rb +3 -3
- data/test/http_verbs_test.rb +17 -22
- data/test/hypermedia_feature_test.rb +23 -45
- data/test/hypermedia_test.rb +11 -23
- data/test/integration/band_representer.rb +2 -2
- data/test/integration/runner.rb +4 -3
- data/test/integration/server.rb +13 -2
- data/test/integration/ssl_server.rb +1 -1
- data/test/json_api_test.rb +336 -0
- data/test/json_representer_test.rb +16 -12
- data/test/lib/runner.rb +134 -0
- data/test/lonely_test.rb +9 -0
- data/test/net_http_transport_test.rb +4 -4
- data/test/representer_test.rb +2 -2
- data/test/{lib/roar/representer/transport/net_http/request_test.rb → ssl_client_certs_test.rb} +43 -5
- data/test/test_helper.rb +12 -5
- data/test/xml_representer_test.rb +26 -166
- metadata +49 -29
- data/gemfiles/Gemfile.representable-1.7 +0 -6
- data/gemfiles/Gemfile.representable-1.8 +0 -6
- data/lib/roar/representer/feature/client.rb +0 -39
- data/lib/roar/representer/feature/http_verbs.rb +0 -95
- data/lib/roar/representer/feature/hypermedia.rb +0 -175
- data/lib/roar/representer/json.rb +0 -67
- data/lib/roar/representer/transport/faraday.rb +0 -50
- data/lib/roar/representer/transport/net_http.rb +0 -59
- data/lib/roar/representer/transport/net_http/request.rb +0 -75
- data/lib/roar/representer/xml.rb +0 -61
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'roar/
|
1
|
+
require 'roar/json'
|
2
2
|
|
3
|
-
module Roar
|
3
|
+
module Roar
|
4
4
|
module JSON
|
5
5
|
# Including the JSON::HAL module in your representer will render and parse documents
|
6
6
|
# following the HAL specification: http://stateless.co/hal_specification.html
|
@@ -44,7 +44,7 @@ module Roar::Representer
|
|
44
44
|
module HAL
|
45
45
|
def self.included(base)
|
46
46
|
base.class_eval do
|
47
|
-
include Roar::
|
47
|
+
include Roar::JSON
|
48
48
|
include Links # overwrites #links_definition_options.
|
49
49
|
extend ClassMethods # overwrites #links_definition_options, again.
|
50
50
|
include Resources
|
@@ -60,13 +60,13 @@ module Roar::Representer
|
|
60
60
|
module Resources
|
61
61
|
# Write the property to the +_embedded+ hash when it's a resource.
|
62
62
|
def compile_fragment(bin, doc)
|
63
|
-
embedded =
|
63
|
+
embedded = bin[:embedded]
|
64
64
|
return super unless embedded
|
65
65
|
super(bin, doc[:_embedded] ||= {})
|
66
66
|
end
|
67
67
|
|
68
68
|
def uncompile_fragment(bin, doc)
|
69
|
-
embedded =
|
69
|
+
embedded = bin[:embedded]
|
70
70
|
return super unless embedded
|
71
71
|
super(bin, doc["_embedded"] || {})
|
72
72
|
end
|
@@ -74,11 +74,11 @@ module Roar::Representer
|
|
74
74
|
|
75
75
|
module ClassMethods
|
76
76
|
def links_definition_options
|
77
|
-
super.
|
77
|
+
super.merge(:as => :_links)
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
class LinkCollection <
|
81
|
+
class LinkCollection < Hypermedia::LinkCollection
|
82
82
|
def initialize(array_rels, *args)
|
83
83
|
super(*args)
|
84
84
|
@array_rels = array_rels.map(&:to_s)
|
@@ -106,11 +106,9 @@ module Roar::Representer
|
|
106
106
|
# Note that the HAL::Links module alone doesn't prepend an underscore to +links+. Use the JSON::HAL module for that.
|
107
107
|
module Links
|
108
108
|
def self.included(base)
|
109
|
-
base.
|
110
|
-
|
111
|
-
|
112
|
-
include InstanceMethods
|
113
|
-
end
|
109
|
+
base.extend ClassMethods # ::links_definition_options
|
110
|
+
base.send :include, Hypermedia
|
111
|
+
base.send :include, InstanceMethods
|
114
112
|
end
|
115
113
|
|
116
114
|
module InstanceMethods
|
@@ -118,7 +116,7 @@ module Roar::Representer
|
|
118
116
|
def prepare_link_for(href, options)
|
119
117
|
return super(href, options) unless options[:array] # TODO: remove :array and use special instan
|
120
118
|
|
121
|
-
list = href.collect { |opts|
|
119
|
+
list = href.collect { |opts| Hypermedia::Hyperlink.new(opts.merge!(:rel => options[:rel])) }
|
122
120
|
LinkArray.new(list, options[:rel])
|
123
121
|
end
|
124
122
|
|
@@ -133,25 +131,29 @@ module Roar::Representer
|
|
133
131
|
module LinkCollectionRepresenter
|
134
132
|
include Representable::JSON::Hash
|
135
133
|
|
136
|
-
values :extend => lambda { |item, *|
|
137
|
-
|
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
|
+
}
|
138
138
|
|
139
139
|
def to_hash(options)
|
140
140
|
super.tap do |hsh| # TODO: cool: super(:exclude => [:rel]).
|
141
|
-
hsh.each { |k,v| v.delete(
|
141
|
+
hsh.each { |k,v| v.delete("rel") }
|
142
142
|
end
|
143
143
|
end
|
144
144
|
|
145
145
|
|
146
|
-
def from_hash(hash,
|
146
|
+
def from_hash(hash, *args)
|
147
147
|
hash.each { |k,v| hash[k] = LinkArray.new(v, k) if is_array?(k) }
|
148
148
|
|
149
149
|
hsh = super(hash) # this is where :class and :extend do the work.
|
150
150
|
|
151
|
-
hsh.each { |k, v| v.rel
|
151
|
+
hsh.each { |k, v| v.merge!(:rel => k) }
|
152
|
+
hsh.values # links= expects [Hyperlink, Hyperlink]
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
156
|
+
# DISCUSS: we can probably get rid of this asset.
|
155
157
|
class LinkArray < Array
|
156
158
|
def initialize(elems, rel)
|
157
159
|
super(elems)
|
@@ -160,8 +162,8 @@ module Roar::Representer
|
|
160
162
|
|
161
163
|
attr_reader :rel
|
162
164
|
|
163
|
-
def
|
164
|
-
each { |lnk| lnk.
|
165
|
+
def merge!(attrs)
|
166
|
+
each { |lnk| lnk.merge!(attrs) }
|
165
167
|
end
|
166
168
|
end
|
167
169
|
|
@@ -169,12 +171,12 @@ module Roar::Representer
|
|
169
171
|
module LinkArrayRepresenter
|
170
172
|
include Representable::JSON::Collection
|
171
173
|
|
172
|
-
items :extend => Roar::
|
173
|
-
:class => Roar::
|
174
|
+
items :extend => Roar::JSON::HyperlinkRepresenter,
|
175
|
+
:class => Roar::Hypermedia::Hyperlink
|
174
176
|
|
175
177
|
def to_hash(*)
|
176
178
|
super.tap do |ary|
|
177
|
-
ary.each { |lnk| rel = lnk.delete(
|
179
|
+
ary.each { |lnk| rel = lnk.delete("rel") }
|
178
180
|
end
|
179
181
|
end
|
180
182
|
end
|
@@ -182,13 +184,13 @@ module Roar::Representer
|
|
182
184
|
|
183
185
|
module ClassMethods
|
184
186
|
def links_definition_options
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
187
|
+
# property :links_array,
|
188
|
+
{
|
189
|
+
:as => :links,
|
190
|
+
:extend => HAL::Links::LinkCollectionRepresenter,
|
191
|
+
:instance => lambda { |*| LinkCollection.new(link_array_rels) }, # defined in InstanceMethods as this is executed in represented context.
|
192
|
+
:exec_context => :decorator,
|
193
|
+
}
|
192
194
|
end
|
193
195
|
|
194
196
|
# Use this to define link arrays. It accepts the shared rel attribute and an array of options per link object.
|
@@ -199,7 +201,7 @@ module Roar::Representer
|
|
199
201
|
# end
|
200
202
|
def links(options, &block)
|
201
203
|
options = {:rel => options} if options.is_a?(Symbol)
|
202
|
-
options[:array] = true
|
204
|
+
options[:array] = true
|
203
205
|
link(options, &block)
|
204
206
|
end
|
205
207
|
|
@@ -0,0 +1,208 @@
|
|
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
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
module ForCollection
|
21
|
+
def for_collection # same API as representable. TODO: we could use ::collection_representer! here.
|
22
|
+
singular = self # e.g. Song::Representer
|
23
|
+
|
24
|
+
# this basically does Module.new { include Hash::Collection .. }
|
25
|
+
build_inline(nil, [Document::Collection, Representable::Hash::Collection, Roar::JSON], "", {}) do
|
26
|
+
items extend: singular, :parse_strategy => :sync
|
27
|
+
|
28
|
+
representable_attrs[:resource_representer] = singular.representable_attrs[:resource_representer]
|
29
|
+
representable_attrs[:_wrap] = singular.representable_attrs[:_wrap]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
module Singular
|
36
|
+
def to_hash(options={})
|
37
|
+
# per resource:
|
38
|
+
super(:exclude => [:links]).tap do |hash|
|
39
|
+
hash["links"] = hash.delete("_links") if hash["_links"]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_hash(hash, options={})
|
44
|
+
hash["_links"] = hash["links"]
|
45
|
+
super
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
module Resource
|
51
|
+
# ::link is delegated to Representer which handles the hypermedia (rendering
|
52
|
+
# and parsing links).
|
53
|
+
class Representer < Roar::Decorator
|
54
|
+
include Roar::JSON
|
55
|
+
include Roar::Hypermedia
|
56
|
+
|
57
|
+
def self.links_definition_options
|
58
|
+
{
|
59
|
+
:extend => LinkCollectionRepresenter,
|
60
|
+
:exec_context => :decorator
|
61
|
+
}
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.included(base)
|
66
|
+
base.extend Declarative # inject our ::link.
|
67
|
+
end
|
68
|
+
|
69
|
+
# New API for JSON-API representers.
|
70
|
+
module Declarative
|
71
|
+
def type(name=nil)
|
72
|
+
return super unless name # original name.
|
73
|
+
representable_attrs[:_wrap] = name.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
# Define global document links for the links: directive.
|
77
|
+
def link(*args, &block)
|
78
|
+
representable_attrs[:resource_representer].link(*args, &block)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Per-model links.
|
82
|
+
def links(&block)
|
83
|
+
nested(:_links, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# TODO: always create _links.
|
87
|
+
def has_one(name)
|
88
|
+
property :_links, :inherit => true, :use_decorator => true do # simply extend the Decorator _links.
|
89
|
+
property "#{name}_id", :as => name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def has_many(name)
|
94
|
+
property :_links, :inherit => true, :use_decorator => true do # simply extend the Decorator _links.
|
95
|
+
collection "#{name.to_s.sub(/s$/, "")}_ids", :as => name
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def compound(&block)
|
100
|
+
nested(:linked, &block)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
# TODO: don't use Document for singular+wrap AND singular in collection (this way, we can get rid of the only_body)
|
107
|
+
module Document
|
108
|
+
def to_hash(options={})
|
109
|
+
# per resource:
|
110
|
+
res = super # render single resource or collection.
|
111
|
+
return res if options[:only_body]
|
112
|
+
# 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.
|
113
|
+
|
114
|
+
to_document(res)
|
115
|
+
end
|
116
|
+
|
117
|
+
def from_hash(hash, options={})
|
118
|
+
|
119
|
+
return super(hash, options) if options[:only_body] # singular
|
120
|
+
|
121
|
+
super(from_document(hash)) # singular
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def to_document(res)
|
126
|
+
links = representable_attrs[:resource_representer].new(represented).to_hash # creates links: section.
|
127
|
+
# FIXME: provide two different #to_document
|
128
|
+
|
129
|
+
if res.is_a?(Array)
|
130
|
+
compound = collection_compound!(res, {})
|
131
|
+
else
|
132
|
+
compound = compile_compound!(res.delete("linked"), {})
|
133
|
+
end
|
134
|
+
|
135
|
+
{representable_attrs[:_wrap] => res}.tap do |doc|
|
136
|
+
doc.merge!(links)
|
137
|
+
doc.merge!("linked" => compound) if compound && compound.size > 0
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def from_document(hash)
|
142
|
+
hash[representable_attrs[:_wrap]]
|
143
|
+
end
|
144
|
+
|
145
|
+
# Compiles the linked: section for compound objects in the document.
|
146
|
+
def collection_compound!(collection, compound)
|
147
|
+
collection.each { |res|
|
148
|
+
kv = res.delete("linked") or next
|
149
|
+
|
150
|
+
compile_compound!(kv, compound)
|
151
|
+
}
|
152
|
+
|
153
|
+
compound
|
154
|
+
end
|
155
|
+
|
156
|
+
# Go through {"album"=>{"title"=>"Hackers"}, "musicians"=>[{"name"=>"Eddie Van Halen"}, ..]} from linked:
|
157
|
+
# and wrap every item in an array.
|
158
|
+
def compile_compound!(linked, compound)
|
159
|
+
return unless linked
|
160
|
+
|
161
|
+
linked.each { |k,v| # {"album"=>{"title"=>"Hackers"}, "musicians"=>[{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]}
|
162
|
+
compound[k] ||= []
|
163
|
+
|
164
|
+
if v.is_a?(::Hash) # {"title"=>"Hackers"}
|
165
|
+
compound[k] << v
|
166
|
+
else
|
167
|
+
compound[k].push(*v) # [{"name"=>"Eddie Van Halen"}, {"name"=>"Greg Howe"}]
|
168
|
+
end
|
169
|
+
|
170
|
+
compound[k] = compound[k].uniq
|
171
|
+
}
|
172
|
+
|
173
|
+
compound
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
module Collection
|
178
|
+
include Document
|
179
|
+
|
180
|
+
def to_hash(options={})
|
181
|
+
res = super(options.merge(:only_body => true))
|
182
|
+
to_document(res)
|
183
|
+
end
|
184
|
+
|
185
|
+
def from_hash(hash, options={})
|
186
|
+
hash = from_document(hash)
|
187
|
+
super(hash, options.merge(:only_body => true))
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
module LinkRepresenter
|
194
|
+
include Roar::JSON
|
195
|
+
|
196
|
+
property :href
|
197
|
+
property :type
|
198
|
+
end
|
199
|
+
|
200
|
+
require 'representable/json/hash'
|
201
|
+
module LinkCollectionRepresenter
|
202
|
+
include Representable::JSON::Hash
|
203
|
+
|
204
|
+
values :extend => LinkRepresenter # TODO: parsing.
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
data/lib/roar/representer.rb
CHANGED
@@ -1,44 +1,11 @@
|
|
1
1
|
require 'representable'
|
2
2
|
|
3
3
|
module Roar
|
4
|
+
# generic features can be included here and will be available in all format-specific representers.
|
4
5
|
module Representer
|
5
|
-
# TODO: move to separate module
|
6
|
-
# DISCUSS: experimental. this will soon be moved to a separate gem
|
7
|
-
module InheritableArray
|
8
|
-
def representable_attrs
|
9
|
-
super.extend(ConfigExtensions)
|
10
|
-
end
|
11
|
-
|
12
|
-
module ConfigExtensions
|
13
|
-
def inheritable_array(name)
|
14
|
-
inheritable_arrays[name] ||= []
|
15
|
-
end
|
16
|
-
def inheritable_arrays
|
17
|
-
@inheritable_arrays ||= {}
|
18
|
-
end
|
19
|
-
|
20
|
-
def inherit(parent)
|
21
|
-
super
|
22
|
-
|
23
|
-
parent.inheritable_arrays.keys.each do |k|
|
24
|
-
inheritable_array(k).push *parent.inheritable_array(k).clone
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
6
|
def self.included(base)
|
31
|
-
|
32
|
-
|
33
|
-
extend InheritableArray # this adds InheritableArray::representable_attrs to the module, e.g. when a representer includes a representer, we don't work with the instance method, yet.
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
include InheritableArray
|
38
|
-
|
39
|
-
|
40
|
-
private
|
41
|
-
def before_serialize(*)
|
7
|
+
super
|
8
|
+
base.send(:include, Representable)
|
42
9
|
end
|
43
10
|
end
|
44
11
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
gem 'faraday'
|
2
|
+
require 'faraday'
|
3
|
+
|
4
|
+
module Roar
|
5
|
+
module Transport
|
6
|
+
# Advanced implementation of the HTTP verbs with the Faraday HTTP library
|
7
|
+
# (which can, in turn, use adapters based on Net::HTTP or libcurl)
|
8
|
+
#
|
9
|
+
# Depending on how the Faraday middleware stack is configured, this
|
10
|
+
# Transport can support features such as HTTP error code handling,
|
11
|
+
# redirects, etc.
|
12
|
+
#
|
13
|
+
# @see http://rubydoc.info/gems/faraday/file/README.md Faraday README
|
14
|
+
class Faraday
|
15
|
+
|
16
|
+
def get_uri(options)
|
17
|
+
build_connection(options[:uri], options[:as]).get
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_uri(options)
|
21
|
+
build_connection(options[:uri], options[:as]).post(nil, options[:body])
|
22
|
+
end
|
23
|
+
|
24
|
+
def put_uri(options)
|
25
|
+
build_connection(options[:uri], options[:as]).put(nil, options[:body])
|
26
|
+
end
|
27
|
+
|
28
|
+
def patch_uri(options)
|
29
|
+
build_connection(options[:uri], options[:as]).patch(nil, options[:body])
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete_uri(options)
|
33
|
+
build_connection(options[:uri], options[:as]).delete
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_connection(uri, as)
|
39
|
+
::Faraday::Connection.new(
|
40
|
+
:url => uri,
|
41
|
+
:headers => { :accept => as, :content_type => as }
|
42
|
+
) do |builder|
|
43
|
+
builder.use ::Faraday::Response::RaiseError
|
44
|
+
builder.adapter ::Faraday.default_adapter
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|