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
data/lib/roar.rb
CHANGED
data/lib/roar/client.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "roar/http_verbs"
|
2
|
+
|
3
|
+
module Roar
|
4
|
+
# Automatically add accessors for properties and collections. Also mixes in HttpVerbs.
|
5
|
+
module Client
|
6
|
+
include HttpVerbs
|
7
|
+
|
8
|
+
def self.extended(base)
|
9
|
+
base.instance_eval do
|
10
|
+
representable_attrs.each do |attr|
|
11
|
+
name = attr.name
|
12
|
+
next if name == "links" # ignore hyperlinks.
|
13
|
+
|
14
|
+
# TODO: could anyone please make this better?
|
15
|
+
instance_eval %Q{
|
16
|
+
def #{name}=(v)
|
17
|
+
@#{name} = v
|
18
|
+
end
|
19
|
+
|
20
|
+
def #{name}
|
21
|
+
@#{name}
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_hash(options={})
|
29
|
+
options[:links] ||= false
|
30
|
+
super(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_xml(options={}) # sorry, but i'm not even sure if anyone uses this module.
|
34
|
+
options[:links] ||= false
|
35
|
+
super(options)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/roar/decorator.rb
CHANGED
@@ -2,18 +2,10 @@ require 'roar/representer'
|
|
2
2
|
require 'representable/decorator'
|
3
3
|
|
4
4
|
class Roar::Decorator < Representable::Decorator
|
5
|
-
extend Roar::Representer::InheritableArray
|
6
|
-
|
7
5
|
module HypermediaConsumer
|
8
|
-
def
|
9
|
-
|
10
|
-
represented.links
|
11
|
-
end
|
12
|
-
|
13
|
-
# TODO: what is the deal with #links_array and #links?
|
14
|
-
def links=(*args)
|
15
|
-
super
|
16
|
-
represented.links = links
|
6
|
+
def links=(arr)
|
7
|
+
links = super
|
8
|
+
represented.instance_variable_set :@links, links
|
17
9
|
end
|
18
10
|
end
|
19
11
|
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'roar/transport/net_http'
|
2
|
+
|
3
|
+
module Roar
|
4
|
+
# Gives HTTP-power to representers. They can serialize, send, process and deserialize HTTP-requests.
|
5
|
+
module HttpVerbs
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :transport_engine
|
9
|
+
|
10
|
+
def included(base)
|
11
|
+
base.extend ClassMethods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
self.transport_engine = ::Roar::Transport::NetHTTP
|
15
|
+
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# GETs +url+ with +format+ and returns deserialized represented object.
|
19
|
+
def get(*args)
|
20
|
+
new.get(*args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
attr_writer :transport_engine
|
26
|
+
def transport_engine
|
27
|
+
@transport_engine || HttpVerbs.transport_engine
|
28
|
+
end
|
29
|
+
|
30
|
+
# Serializes the object, POSTs it to +url+ with +format+, deserializes the returned document
|
31
|
+
# and updates properties accordingly.
|
32
|
+
def post(options={}, &block)
|
33
|
+
response = http.post_uri(options.merge(:body => serialize), &block)
|
34
|
+
handle_response(response)
|
35
|
+
end
|
36
|
+
|
37
|
+
# GETs +url+ with +format+, deserializes the returned document and updates properties accordingly.
|
38
|
+
def get(options={}, &block)
|
39
|
+
response = http.get_uri(options, &block)
|
40
|
+
handle_response(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Serializes the object, PUTs it to +url+ with +format+, deserializes the returned document
|
44
|
+
# and updates properties accordingly.
|
45
|
+
def put(options={}, &block)
|
46
|
+
response = http.put_uri(options.merge(:body => serialize), &block)
|
47
|
+
handle_response(response)
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def patch(options={}, &block)
|
52
|
+
response = http.patch_uri(options.merge(:body => serialize), &block)
|
53
|
+
handle_response(response)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
def delete(options, &block)
|
58
|
+
http.delete_uri(options, &block)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
def handle_response(response)
|
64
|
+
document = response.body
|
65
|
+
deserialize(document)
|
66
|
+
end
|
67
|
+
|
68
|
+
def http
|
69
|
+
transport_engine.new
|
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
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
module Roar
|
2
|
+
# Define hypermedia links in your representations.
|
3
|
+
#
|
4
|
+
# Example:
|
5
|
+
#
|
6
|
+
# class Order
|
7
|
+
# include Roar::Representer::JSON
|
8
|
+
#
|
9
|
+
# property :id
|
10
|
+
#
|
11
|
+
# link :self do
|
12
|
+
# "http://orders/#{id}"
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# If you want more attributes, just pass a hash to #link.
|
16
|
+
#
|
17
|
+
# link :rel => :next, :title => "Next, please!" do
|
18
|
+
# "http://orders/#{id}"
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# If you need dynamic attributes, the block can return a hash.
|
22
|
+
#
|
23
|
+
# link :preview do
|
24
|
+
# {:href => image.url, :title => image.name}
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# Sometimes you need values from outside when the representation links are rendered. Just pass them
|
28
|
+
# to the render method, they will be available as block parameters.
|
29
|
+
#
|
30
|
+
# link :self do |opts|
|
31
|
+
# "http://orders/#{opts[:id]}"
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# model.to_json(:id => 1)
|
35
|
+
module Hypermedia
|
36
|
+
# links= [Hyperlink, Hyperlink] is where parsing happens.
|
37
|
+
def self.included(base)
|
38
|
+
base.extend ClassMethods
|
39
|
+
end
|
40
|
+
|
41
|
+
def links=(arr) # called when assigning parsed links.
|
42
|
+
@links = LinkCollection[*arr]
|
43
|
+
end
|
44
|
+
|
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
|
+
private
|
57
|
+
# Create hypermedia links for this instance by invoking their blocks.
|
58
|
+
# This is called in links: getter: {}.
|
59
|
+
def prepare_links!(options)
|
60
|
+
return [] if options[:links] == false
|
61
|
+
|
62
|
+
LinkCollection[*compile_links_for(link_configs, options)]
|
63
|
+
end
|
64
|
+
|
65
|
+
def compile_links_for(configs, *args)
|
66
|
+
configs.collect do |config|
|
67
|
+
options, block = config.first, config.last
|
68
|
+
href = run_link_block(block, *args) or next
|
69
|
+
|
70
|
+
prepare_link_for(href, options)
|
71
|
+
end.compact # FIXME: make this less ugly.
|
72
|
+
end
|
73
|
+
|
74
|
+
def prepare_link_for(href, options)
|
75
|
+
options = options.merge(href.is_a?(::Hash) ? href : {:href => href})
|
76
|
+
Hyperlink.new(options)
|
77
|
+
end
|
78
|
+
|
79
|
+
def run_link_block(block, *args)
|
80
|
+
instance_exec(*args, &block)
|
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 collect(&block)
|
102
|
+
return values.collect(&block) if block.arity == 1
|
103
|
+
super(&block)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
module ClassMethods
|
109
|
+
# Declares a hypermedia link in the document.
|
110
|
+
#
|
111
|
+
# Example:
|
112
|
+
#
|
113
|
+
# link :self do
|
114
|
+
# "http://orders/#{id}"
|
115
|
+
# end
|
116
|
+
#
|
117
|
+
# The block is executed in instance context, so you may call properties or other accessors.
|
118
|
+
# Note that you're free to put decider logic into #link blocks, too.
|
119
|
+
def link(options, &block)
|
120
|
+
create_links_definition! # this assures the links are rendered at the right position.
|
121
|
+
|
122
|
+
options = {:rel => options} unless options.is_a?(::Hash)
|
123
|
+
link_configs << [options, block]
|
124
|
+
end
|
125
|
+
|
126
|
+
include LinkConfigsMethod
|
127
|
+
|
128
|
+
private
|
129
|
+
# Add a :links Definition to the representable_attrs so they get rendered/parsed.
|
130
|
+
def create_links_definition!
|
131
|
+
return if representable_attrs.get(:links) # only create it once.
|
132
|
+
|
133
|
+
options = links_definition_options
|
134
|
+
options.merge!(:getter => lambda { |opts| prepare_links!(opts) })
|
135
|
+
|
136
|
+
representable_attrs.add(:links, options)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
# An abstract hypermedia link with arbitrary attributes.
|
142
|
+
class Hyperlink
|
143
|
+
extend Forwardable
|
144
|
+
def_delegators :@attrs, :each, :collect
|
145
|
+
|
146
|
+
def initialize(attrs={})
|
147
|
+
@attrs = attributes!(attrs)
|
148
|
+
end
|
149
|
+
|
150
|
+
def replace(attrs) # makes it work with Hash::Hash.
|
151
|
+
@attrs = attributes!(attrs)
|
152
|
+
self
|
153
|
+
end
|
154
|
+
|
155
|
+
# Only way to write to Hyperlink after creation.
|
156
|
+
def merge!(attrs)
|
157
|
+
@attrs.merge!(attributes!(attrs))
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def method_missing(name)
|
162
|
+
@attrs[name.to_s]
|
163
|
+
end
|
164
|
+
|
165
|
+
# Converts keys to strings.
|
166
|
+
def attributes!(attrs)
|
167
|
+
attrs.inject({}) { |hsh, kv| hsh[kv.first.to_s] = kv.last; hsh }.tap do |hsh|
|
168
|
+
hsh["rel"] = hsh["rel"].to_s if hsh["rel"]
|
169
|
+
end
|
170
|
+
# raise "Hyperlink without rel doesn't work!" unless @attrs["rel"]
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
data/lib/roar/json.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'roar/representer'
|
2
|
+
require 'roar/hypermedia'
|
3
|
+
require 'representable/json'
|
4
|
+
|
5
|
+
module Roar
|
6
|
+
module JSON
|
7
|
+
def self.included(base)
|
8
|
+
base.class_eval do
|
9
|
+
include Representer
|
10
|
+
include Representable::JSON
|
11
|
+
|
12
|
+
extend ClassMethods
|
13
|
+
include InstanceMethods # otherwise Representable overrides our #to_json.
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module InstanceMethods
|
18
|
+
def from_json(document, options={})
|
19
|
+
document = '{}' if document.nil? or document.empty?
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
|
24
|
+
# Generic entry-point for rendering.
|
25
|
+
def serialize(*args)
|
26
|
+
to_json(*args)
|
27
|
+
end
|
28
|
+
|
29
|
+
def deserialize(*args)
|
30
|
+
from_json(*args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
module ClassMethods
|
36
|
+
# TODO: move to instance method, or remove?
|
37
|
+
def links_definition_options
|
38
|
+
# FIXME: this doesn't belong into the generic JSON representer.
|
39
|
+
{
|
40
|
+
:collection => true,
|
41
|
+
:class => Hypermedia::Hyperlink,
|
42
|
+
:extend => HyperlinkRepresenter,
|
43
|
+
:exec_context => :decorator,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
require "representable/json/hash"
|
50
|
+
# Represents a hyperlink in standard roar+json hash representation.
|
51
|
+
module HyperlinkRepresenter
|
52
|
+
include Representable::JSON::Hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module Roar::
|
1
|
+
module Roar::JSON
|
2
2
|
# Implementation of the Collection+JSON media format, http://amundsen.com/media-types/collection/format/
|
3
3
|
#
|
4
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
|
@@ -9,8 +9,8 @@ module Roar::Representer::JSON
|
|
9
9
|
def self.included(base)
|
10
10
|
base.class_eval do
|
11
11
|
include Roar::Representer
|
12
|
-
include Roar::
|
13
|
-
include Roar::
|
12
|
+
include Roar::JSON
|
13
|
+
include Roar::Hypermedia
|
14
14
|
|
15
15
|
extend ClassMethods
|
16
16
|
|
@@ -21,7 +21,7 @@ module Roar::Representer::JSON
|
|
21
21
|
OpenStruct.new # TODO: handle preset values.
|
22
22
|
end
|
23
23
|
|
24
|
-
collection :queries, :extend => Roar::
|
24
|
+
collection :queries, :extend => Roar::JSON::HyperlinkRepresenter, :class => lambda { |fragment,*| ::Hash }
|
25
25
|
def queries
|
26
26
|
compile_links_for(representable_attrs.collection_representers[:queries].link_configs)
|
27
27
|
end
|
@@ -45,9 +45,9 @@ module Roar::Representer::JSON
|
|
45
45
|
def __href
|
46
46
|
compile_links_for(representable_attrs.collection_representers[:href].link_configs).first.href
|
47
47
|
end
|
48
|
-
|
49
48
|
|
50
|
-
|
49
|
+
|
50
|
+
|
51
51
|
include ClientMethods
|
52
52
|
end
|
53
53
|
end
|
@@ -62,9 +62,9 @@ module Roar::Representer::JSON
|
|
62
62
|
# TODO: provide automatic copying from the ItemRepresenter here.
|
63
63
|
def template(&block)
|
64
64
|
mod = representable_attrs.collection_representers[:object_template] = Module.new do
|
65
|
-
include Roar::
|
66
|
-
include Roar::
|
67
|
-
|
65
|
+
include Roar::JSON
|
66
|
+
include Roar::JSON::CollectionJSON::DataMethods
|
67
|
+
|
68
68
|
extend PropertyWithRenderNil
|
69
69
|
|
70
70
|
module_exec(&block)
|
@@ -76,7 +76,7 @@ module Roar::Representer::JSON
|
|
76
76
|
end
|
77
77
|
|
78
78
|
representable_attrs.collection_representers[:template] = Module.new do
|
79
|
-
include Roar::
|
79
|
+
include Roar::JSON
|
80
80
|
include mod
|
81
81
|
|
82
82
|
#self.representation_wrap = false
|
@@ -93,9 +93,9 @@ module Roar::Representer::JSON
|
|
93
93
|
|
94
94
|
def queries(&block)
|
95
95
|
mod = representable_attrs.collection_representers[:queries] = Module.new do
|
96
|
-
include Roar::
|
97
|
-
include Roar::
|
98
|
-
|
96
|
+
include Roar::JSON
|
97
|
+
include Roar::Hypermedia
|
98
|
+
|
99
99
|
module_exec(&block)
|
100
100
|
|
101
101
|
def to_hash(*)
|
@@ -109,9 +109,9 @@ module Roar::Representer::JSON
|
|
109
109
|
collection :items, { :extend => lambda {|*| representable_attrs.collection_representers[:items] } }.merge!(options)
|
110
110
|
|
111
111
|
mod = representable_attrs.collection_representers[:items] = Module.new do
|
112
|
-
include Roar::
|
113
|
-
include Roar::
|
114
|
-
include Roar::
|
112
|
+
include Roar::JSON
|
113
|
+
include Roar::Hypermedia
|
114
|
+
include Roar::JSON::CollectionJSON::DataMethods
|
115
115
|
extend SharedClassMethodsBullshit
|
116
116
|
|
117
117
|
module_exec(&block)
|
@@ -122,7 +122,7 @@ module Roar::Representer::JSON
|
|
122
122
|
compile_links_for(representable_attrs.collection_representers[:href].link_configs).first.href
|
123
123
|
end
|
124
124
|
def __href=(v)
|
125
|
-
@__href = Roar::
|
125
|
+
@__href = Roar::Hypermedia::Hyperlink.new(:href => v)
|
126
126
|
end
|
127
127
|
def href
|
128
128
|
@__href
|
@@ -137,8 +137,8 @@ module Roar::Representer::JSON
|
|
137
137
|
module SharedClassMethodsBullshit
|
138
138
|
def href(&block)
|
139
139
|
mod = representable_attrs.collection_representers[:href] = Module.new do
|
140
|
-
include Roar::
|
141
|
-
include Roar::
|
140
|
+
include Roar::JSON
|
141
|
+
include Roar::Hypermedia
|
142
142
|
|
143
143
|
|
144
144
|
link(:href, &block)
|
@@ -190,7 +190,7 @@ module Roar::Representer::JSON
|
|
190
190
|
end
|
191
191
|
|
192
192
|
def __href=(v)
|
193
|
-
@__href = Roar::
|
193
|
+
@__href = Roar::Hypermedia::Hyperlink.new(:href => v)
|
194
194
|
end
|
195
195
|
def href
|
196
196
|
@__href
|