roar 0.12.9 → 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|