restfully 0.6.3 → 0.7.0.pre
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.
- data/README.md +166 -0
- data/Rakefile +35 -35
- data/bin/restfully +68 -10
- data/lib/restfully.rb +8 -14
- data/lib/restfully/collection.rb +70 -90
- data/lib/restfully/error.rb +2 -0
- data/lib/restfully/http.rb +3 -3
- data/lib/restfully/http/error.rb +1 -20
- data/lib/restfully/http/helper.rb +49 -0
- data/lib/restfully/http/request.rb +60 -24
- data/lib/restfully/http/response.rb +55 -24
- data/lib/restfully/link.rb +32 -24
- data/lib/restfully/media_type.rb +70 -0
- data/lib/restfully/media_type/abstract_media_type.rb +162 -0
- data/lib/restfully/media_type/application_json.rb +21 -0
- data/lib/restfully/media_type/application_vnd_bonfire_xml.rb +177 -0
- data/lib/restfully/media_type/application_x_www_form_urlencoded.rb +33 -0
- data/lib/restfully/media_type/grid5000.rb +67 -0
- data/lib/restfully/media_type/wildcard.rb +27 -0
- data/lib/restfully/rack.rb +1 -0
- data/lib/restfully/rack/basic_auth.rb +26 -0
- data/lib/restfully/resource.rb +134 -197
- data/lib/restfully/session.rb +127 -70
- data/lib/restfully/version.rb +3 -0
- data/spec/fixtures/bonfire-collection-with-fragments.xml +6 -0
- data/spec/fixtures/bonfire-compute-existing.xml +43 -0
- data/spec/fixtures/bonfire-empty-collection.xml +4 -0
- data/spec/fixtures/bonfire-experiment-collection.xml +51 -0
- data/spec/fixtures/bonfire-network-collection.xml +35 -0
- data/spec/fixtures/bonfire-network-existing.xml +6 -0
- data/spec/fixtures/bonfire-root.xml +5 -0
- data/spec/fixtures/grid5000-rennes-jobs.json +988 -146
- data/spec/fixtures/grid5000-rennes.json +63 -0
- data/spec/restfully/collection_spec.rb +87 -0
- data/spec/restfully/http/helper_spec.rb +18 -0
- data/spec/restfully/http/request_spec.rb +97 -0
- data/spec/restfully/http/response_spec.rb +53 -0
- data/spec/restfully/link_spec.rb +80 -0
- data/spec/restfully/media_type/application_vnd_bonfire_xml_spec.rb +153 -0
- data/spec/restfully/media_type_spec.rb +117 -0
- data/spec/restfully/resource_spec.rb +109 -0
- data/spec/restfully/session_spec.rb +229 -0
- data/spec/spec_helper.rb +10 -9
- metadata +162 -83
- data/.document +0 -5
- data/CHANGELOG +0 -62
- data/README.rdoc +0 -146
- data/TODO.rdoc +0 -3
- data/VERSION +0 -1
- data/examples/grid5000.rb +0 -33
- data/examples/scratch.rb +0 -37
- data/lib/restfully/extensions.rb +0 -34
- data/lib/restfully/http/adapters/abstract_adapter.rb +0 -29
- data/lib/restfully/http/adapters/patron_adapter.rb +0 -16
- data/lib/restfully/http/adapters/rest_client_adapter.rb +0 -75
- data/lib/restfully/http/headers.rb +0 -20
- data/lib/restfully/parsing.rb +0 -66
- data/lib/restfully/special_array.rb +0 -5
- data/lib/restfully/special_hash.rb +0 -5
- data/restfully.gemspec +0 -114
- data/spec/collection_spec.rb +0 -120
- data/spec/fixtures/configuration_file.yml +0 -4
- data/spec/fixtures/grid5000-sites.json +0 -540
- data/spec/http/error_spec.rb +0 -18
- data/spec/http/headers_spec.rb +0 -17
- data/spec/http/request_spec.rb +0 -49
- data/spec/http/response_spec.rb +0 -19
- data/spec/http/rest_client_adapter_spec.rb +0 -35
- data/spec/link_spec.rb +0 -61
- data/spec/parsing_spec.rb +0 -40
- data/spec/resource_spec.rb +0 -320
- data/spec/restfully_spec.rb +0 -16
- data/spec/session_spec.rb +0 -171
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
|
6
|
+
class ApplicationJson < AbstractMediaType
|
7
|
+
class JSONParser
|
8
|
+
def self.load(io, *args)
|
9
|
+
JSON.load(io)
|
10
|
+
end
|
11
|
+
def self.dump(object, *args)
|
12
|
+
JSON.dump(object)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
set :signature, "application/json"
|
16
|
+
set :parser, JSONParser
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'xml'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
class ApplicationVndBonfireXml < AbstractMediaType
|
6
|
+
NS = "http://api.bonfire-project.eu/doc/schemas/occi"
|
7
|
+
HIDDEN_TYPE_KEY = "__type__"
|
8
|
+
|
9
|
+
def collection?
|
10
|
+
!!(property("items") && property("total") && property("offset"))
|
11
|
+
end
|
12
|
+
|
13
|
+
class Parser
|
14
|
+
class << self
|
15
|
+
def load(io, *args)
|
16
|
+
if io.respond_to?(:read)
|
17
|
+
io = io.read
|
18
|
+
end
|
19
|
+
xml = XML::Document.string(io.to_s)
|
20
|
+
load_xml(xml.root).merge(HIDDEN_TYPE_KEY => xml.root.name)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def dump(object, opts = {})
|
25
|
+
root_name = if object[HIDDEN_TYPE_KEY]
|
26
|
+
object[HIDDEN_TYPE_KEY]
|
27
|
+
elsif opts[:uri]
|
28
|
+
# OK, this is ugly
|
29
|
+
opts[:uri].path.to_s.split("/").last.gsub(/s$/,'')
|
30
|
+
else
|
31
|
+
fail "Can't infer a name for the root element for object: #{object.inspect}"
|
32
|
+
end
|
33
|
+
xml = XML::Document.new
|
34
|
+
xml.root = XML::Node.new(root_name)
|
35
|
+
xml.root["xmlns"] = NS
|
36
|
+
dump_object(object, xml.root)
|
37
|
+
xml.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def load_xml(element)
|
43
|
+
h = {}
|
44
|
+
element.each_element do |e|
|
45
|
+
next if e.empty?
|
46
|
+
if e.name == 'items' && element.name == 'collection'
|
47
|
+
h['total'] = e.attributes['total'].to_i
|
48
|
+
h['offset'] = e.attributes['offset'].to_i
|
49
|
+
h['items'] = []
|
50
|
+
e.each_element do |e2|
|
51
|
+
h['items'] << load_xml(e2).merge(HIDDEN_TYPE_KEY => e2.name)
|
52
|
+
end
|
53
|
+
# if no total specified, total equals the number of items
|
54
|
+
h['total'] = h['items'].length if h['total'] == 0
|
55
|
+
else
|
56
|
+
load_xml_element(e, h)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
if element.attributes["href"]
|
60
|
+
h["link"] ||= []
|
61
|
+
h["link"].push({
|
62
|
+
"href" => element.attributes["href"],
|
63
|
+
"rel" => "self"
|
64
|
+
})
|
65
|
+
end
|
66
|
+
h
|
67
|
+
end
|
68
|
+
|
69
|
+
# We use <tt>HIDDEN_TYPE_KEY</tt> property to keep track of the root name.
|
70
|
+
def load_xml_element(element, h)
|
71
|
+
if element.attributes? && href = element.attributes.find{|attr|
|
72
|
+
attr.name == "href"
|
73
|
+
}
|
74
|
+
single_or_array(h, element.name, element.attributes.inject({}) {|memo, attr| memo[attr.name] = attr.value; memo})
|
75
|
+
#build_resource(href.value))
|
76
|
+
else
|
77
|
+
if element.children.any?(&:element?)
|
78
|
+
single_or_array(h, element.name, load_xml(element))
|
79
|
+
else
|
80
|
+
value = element.content.strip
|
81
|
+
unless value.empty?
|
82
|
+
single_or_array(h, element.name, value)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def single_or_array(h, key, value)
|
89
|
+
if h.has_key?(key)
|
90
|
+
if h[key].kind_of?(Array)
|
91
|
+
h[key].push(value)
|
92
|
+
else
|
93
|
+
h[key] = [h[key], value]
|
94
|
+
end
|
95
|
+
elsif ["disk", "nic", "link"].include?(key)
|
96
|
+
h[key] = [value]
|
97
|
+
else
|
98
|
+
h[key] = value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# def build_resource(uri)
|
103
|
+
# uri
|
104
|
+
# end
|
105
|
+
|
106
|
+
def dump_object(object, parent)
|
107
|
+
case object
|
108
|
+
when Restfully::Resource
|
109
|
+
dump_object({"href" => object.uri.to_s}, parent)
|
110
|
+
when Hash
|
111
|
+
if object.has_key?("href")
|
112
|
+
# only attributes
|
113
|
+
object.each{|kattr,vattr|
|
114
|
+
parent.attributes[kattr.to_s] = vattr
|
115
|
+
}
|
116
|
+
else
|
117
|
+
object.each do |k,v|
|
118
|
+
next if k == HIDDEN_TYPE_KEY
|
119
|
+
node = XML::Node.new(k.to_s)
|
120
|
+
parent << node
|
121
|
+
dump_object(v, node)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
when Array
|
125
|
+
dump_object(object[0], parent)
|
126
|
+
object[1..-1].each{|item|
|
127
|
+
node = XML::Node.new(parent.name)
|
128
|
+
dump_object(item, node)
|
129
|
+
parent.parent << node
|
130
|
+
}
|
131
|
+
else
|
132
|
+
parent << object.to_s
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
set :signature, "application/vnd.bonfire+xml"
|
139
|
+
set :parser, Parser
|
140
|
+
|
141
|
+
def extract_links
|
142
|
+
(property.delete("link") || []).map do |link|
|
143
|
+
l = Link.new(
|
144
|
+
:rel => link['rel'],
|
145
|
+
:type => link['type'] || self.class.default_type,
|
146
|
+
:href => link['href'],
|
147
|
+
:title => link["title"],
|
148
|
+
:id => link["title"]
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def represents?(id)
|
154
|
+
property("id") == id.to_s || property("name") == id.to_s
|
155
|
+
end
|
156
|
+
|
157
|
+
def complete?
|
158
|
+
if property.reject{|k,v| k==HIDDEN_TYPE_KEY}.empty? && links.find(&:self?)
|
159
|
+
false
|
160
|
+
else
|
161
|
+
true
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# Only for collections
|
166
|
+
def each(*args, &block)
|
167
|
+
@items ||= (property("items") || []).map{|i|
|
168
|
+
self.class.new(self.class.serialize(i), @session)
|
169
|
+
}
|
170
|
+
@items.each(*args, &block)
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
register ApplicationVndBonfireXml
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
|
6
|
+
class ApplicationXWwwFormUrlencoded < AbstractMediaType
|
7
|
+
|
8
|
+
class Parser
|
9
|
+
def self.load(object, *args)
|
10
|
+
if object.respond_to? :to_str
|
11
|
+
object = object.to_str
|
12
|
+
elsif object.respond_to? :to_io
|
13
|
+
object = object.to_io.read
|
14
|
+
else
|
15
|
+
object = object.read
|
16
|
+
end
|
17
|
+
::Rack::Utils.parse_nested_query(object)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.dump(object, *args)
|
21
|
+
::Rack::Utils.build_nested_query(object)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
set :signature, "application/x-www-form-urlencoded"
|
26
|
+
set :parser, Parser
|
27
|
+
end
|
28
|
+
|
29
|
+
register ApplicationXWwwFormUrlencoded
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module MediaType
|
5
|
+
class Grid5000 < AbstractMediaType
|
6
|
+
|
7
|
+
set :signature, %w{
|
8
|
+
grid
|
9
|
+
site
|
10
|
+
cluster
|
11
|
+
node
|
12
|
+
nodeStatus
|
13
|
+
version
|
14
|
+
collection
|
15
|
+
timeseries
|
16
|
+
versions
|
17
|
+
user
|
18
|
+
metric
|
19
|
+
job
|
20
|
+
deployment
|
21
|
+
notification
|
22
|
+
}.map{|n|
|
23
|
+
"application/vnd.fr.grid5000.api.#{n}+json"
|
24
|
+
}.push(
|
25
|
+
"application/vnd.grid5000+json"
|
26
|
+
)
|
27
|
+
set :parser, ApplicationJson::JSONParser
|
28
|
+
|
29
|
+
def extract_links
|
30
|
+
(property.delete("links") || []).map do |link|
|
31
|
+
l = Link.new(
|
32
|
+
:rel => link['rel'],
|
33
|
+
:type => link['type'],
|
34
|
+
:href => link['href'],
|
35
|
+
:title => link["title"],
|
36
|
+
:id => link["title"]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def collection?
|
42
|
+
!!(property("items") && property("total") && property("offset"))
|
43
|
+
end
|
44
|
+
|
45
|
+
def meta
|
46
|
+
if collection?
|
47
|
+
property("items")
|
48
|
+
else
|
49
|
+
property
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def represents?(id)
|
54
|
+
property("uid") == id.to_s || property("uid") == id.to_i
|
55
|
+
end
|
56
|
+
|
57
|
+
# Only for collections
|
58
|
+
def each(*args, &block)
|
59
|
+
(property("items") || []).map{|i|
|
60
|
+
self.class.new(self.class.serialize(i), @session)
|
61
|
+
}.each(*args, &block)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Restfully
|
2
|
+
module MediaType
|
3
|
+
|
4
|
+
class Wildcard < AbstractMediaType
|
5
|
+
class IdentityParser
|
6
|
+
def self.load(object, *args)
|
7
|
+
if object.respond_to? :to_str
|
8
|
+
object = object.to_str
|
9
|
+
elsif object.respond_to? :to_io
|
10
|
+
object = object.to_io.read
|
11
|
+
else
|
12
|
+
object = object.read
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.dump(object, *args)
|
17
|
+
object
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
set :signature, "*/*"
|
22
|
+
set :parser, IdentityParser
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'restfully/rack/basic_auth'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'base64'
|
2
|
+
|
3
|
+
module Restfully
|
4
|
+
module Rack
|
5
|
+
class BasicAuth
|
6
|
+
def initialize(app, username, password)
|
7
|
+
@app = app
|
8
|
+
@username = username
|
9
|
+
@password = password
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
env['HTTP_AUTHORIZATION'] = [
|
14
|
+
"Basic",
|
15
|
+
Base64.encode64([
|
16
|
+
@username,
|
17
|
+
@password
|
18
|
+
].join(":"))
|
19
|
+
].join(" ")
|
20
|
+
|
21
|
+
@app.call(env)
|
22
|
+
end
|
23
|
+
|
24
|
+
end # class BasicAuth
|
25
|
+
end # module Rack
|
26
|
+
end
|
data/lib/restfully/resource.rb
CHANGED
@@ -1,256 +1,193 @@
|
|
1
1
|
module Restfully
|
2
|
-
|
3
2
|
# This class represents a Resource, which can be accessed and manipulated
|
4
3
|
# via HTTP methods.
|
5
|
-
#
|
4
|
+
#
|
6
5
|
# The <tt>#load</tt> method must have been called on the resource before
|
7
6
|
# trying to access its attributes or links.
|
8
|
-
#
|
7
|
+
#
|
9
8
|
class Resource
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
:links,
|
14
|
-
:title,
|
15
|
-
:properties,
|
16
|
-
:executed_requests
|
17
|
-
|
18
|
-
# == Description
|
19
|
-
# Creates a new Resource.
|
20
|
-
# <tt>uri</tt>:: a URI object representing the URI of the resource
|
21
|
-
# (complete, absolute or relative URI)
|
22
|
-
# <tt>session</tt>:: an instantiated Restfully::Session object
|
23
|
-
# <tt>options</tt>:: a hash of options (see below)
|
24
|
-
# == Options
|
25
|
-
# <tt>:title</tt>:: an optional title for the resource
|
26
|
-
def initialize(uri, session, options = {})
|
27
|
-
options = options.symbolize_keys
|
28
|
-
@uri = uri.kind_of?(URI) ? uri : URI.parse(uri.to_s)
|
9
|
+
attr_reader :response, :request, :session
|
10
|
+
|
11
|
+
def initialize(session, response, request)
|
29
12
|
@session = session
|
30
|
-
@
|
31
|
-
|
13
|
+
@response = response
|
14
|
+
@request = request
|
15
|
+
@associations = {}
|
32
16
|
end
|
33
|
-
|
34
|
-
# Resets all the inner objects of the resource
|
35
|
-
# (you must call <tt>#load</tt> if you want to repopulate the resource).
|
36
|
-
def reset
|
37
|
-
@executed_requests = Hash.new
|
38
|
-
@links = Hash.new
|
39
|
-
@properties = Hash.new
|
40
|
-
@status = :stale
|
41
|
-
self
|
42
|
-
end
|
43
|
-
|
17
|
+
|
44
18
|
# == Description
|
45
|
-
# Returns the value corresponding to the specified key,
|
19
|
+
# Returns the value corresponding to the specified key,
|
46
20
|
# among the list of resource properties
|
47
|
-
#
|
21
|
+
#
|
48
22
|
# == Usage
|
49
23
|
# resource["uid"]
|
50
24
|
# => "rennes"
|
51
25
|
def [](key)
|
52
|
-
|
26
|
+
media_type.property(key)
|
53
27
|
end
|
54
|
-
|
55
|
-
def
|
56
|
-
|
28
|
+
|
29
|
+
def uri
|
30
|
+
request.uri
|
31
|
+
end
|
32
|
+
|
33
|
+
def media_type
|
34
|
+
response.media_type
|
35
|
+
end
|
36
|
+
|
37
|
+
def collection?
|
38
|
+
media_type.collection?
|
57
39
|
end
|
58
40
|
|
59
|
-
def
|
60
|
-
|
61
|
-
session.logger.debug "Loading link #{method}, args=#{args.inspect}"
|
62
|
-
link.load(*args)
|
63
|
-
else
|
64
|
-
super(method, *args)
|
65
|
-
end
|
41
|
+
def kind
|
42
|
+
collection? ? "Collection" : "Resource"
|
66
43
|
end
|
67
44
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
# <tt>:query</tt>:: a hash of query parameters to pass along the request.
|
76
|
-
# E.g. : resource.load(:query => {:from => (Time.now-3600).to_i, :to => Time.now.to_i})
|
77
|
-
# <tt>:headers</tt>:: a hash of HTTP headers to pass along the request.
|
78
|
-
# E.g. : resource.load(:headers => {'Accept' => 'application/json'})
|
79
|
-
# <tt>:body</tt>:: if you already have the unserialized response body of this resource,
|
80
|
-
# you may pass it so that the GET request is not triggered.
|
45
|
+
def signature(closed=true)
|
46
|
+
s = "#<#{kind}:0x#{object_id.to_s(16)}"
|
47
|
+
s += " uri=#{uri.to_s}"
|
48
|
+
s += ">" if closed
|
49
|
+
s
|
50
|
+
end
|
51
|
+
|
81
52
|
def load(options = {})
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
if !force_reload && options[:body]
|
88
|
-
body = options[:body]
|
89
|
-
headers = {}
|
53
|
+
# Send a GET request only if given a different set of options
|
54
|
+
if @request.update!(options) || @request.no_cache?
|
55
|
+
@response = session.execute(@request)
|
56
|
+
if session.process(@response, @request)
|
57
|
+
@associations.clear
|
90
58
|
else
|
91
|
-
|
92
|
-
body = response.body
|
93
|
-
headers = response.headers
|
94
|
-
end
|
95
|
-
executed_requests['GET'] = {
|
96
|
-
'options' => options,
|
97
|
-
'body' => body,
|
98
|
-
'headers' => headers
|
99
|
-
}
|
100
|
-
executed_requests['GET']['body'].each do |key, value|
|
101
|
-
populate_object(key, value)
|
59
|
+
raise Error, "Cannot reload the resource"
|
102
60
|
end
|
103
|
-
@status = :loaded
|
104
61
|
end
|
105
|
-
|
62
|
+
|
63
|
+
build
|
64
|
+
end
|
65
|
+
|
66
|
+
def relationships
|
67
|
+
@associations.keys
|
68
|
+
end
|
69
|
+
|
70
|
+
def properties
|
71
|
+
media_type.property.reject{|k,v|
|
72
|
+
# do not return keys used for internal use
|
73
|
+
k.to_s =~ /^\_\_(.+)\_\_$/
|
74
|
+
}
|
106
75
|
end
|
107
76
|
|
108
|
-
# Convenience function to make a resource.load(:reload => true)
|
109
77
|
def reload
|
110
|
-
|
111
|
-
stale!
|
112
|
-
self.load(current_options.merge(:reload => true))
|
78
|
+
load(:head => {'Cache-Control' => 'no-cache'})
|
113
79
|
end
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
# It may be a serialized string, or a ruby object
|
120
|
-
# (that will be serialized according to the given or default content-type).
|
121
|
-
# <tt>options</tt>:: list of options to pass to the request (see below)
|
122
|
-
# == Options
|
123
|
-
# <tt>:query</tt>:: a hash of query parameters to pass along the request.
|
124
|
-
# E.g. : resource.submit("body", :query => {:param1 => "value1"})
|
125
|
-
# <tt>:headers</tt>:: a hash of HTTP headers to pass along the request.
|
126
|
-
# E.g. : resource.submit("body", :headers => {:accept => 'application/json', :content_type => 'application/json'})
|
127
|
-
def submit(payload, options = {})
|
128
|
-
options = options.symbolize_keys
|
129
|
-
raise NotImplementedError, "The POST method is not allowed for this resource." unless http_methods.include?('POST')
|
130
|
-
raise ArgumentError, "You must pass a payload" if payload.nil?
|
131
|
-
headers = {
|
132
|
-
:content_type => (executed_requests['GET']['headers']['Content-Type'] || "application/x-www-form-urlencoded").split(/,/).sort{|a,b| a.length <=> b.length}[0],
|
133
|
-
:accept => (executed_requests['GET']['headers']['Content-Type'] || "text/plain")
|
134
|
-
}.merge(options[:headers] || {})
|
135
|
-
options = {:headers => headers}
|
136
|
-
options.merge!(:query => options[:query]) unless options[:query].nil?
|
137
|
-
response = session.post(self.uri, payload, options) # raises an exception if there is an error
|
138
|
-
stale!
|
139
|
-
if [201, 202].include?(response.status)
|
140
|
-
Resource.new(uri_for(response.headers['Location']), session).load
|
80
|
+
|
81
|
+
def submit(*args)
|
82
|
+
if allow?(:post)
|
83
|
+
payload, options = extract_payload_from_args(args)
|
84
|
+
session.post(request.uri, payload, options)
|
141
85
|
else
|
142
|
-
|
86
|
+
raise MethodNotAllowed
|
143
87
|
end
|
144
88
|
end
|
145
|
-
|
146
|
-
# == Description
|
147
|
-
# Executes a DELETE request on the resource, and returns true if successful.
|
148
|
-
# If the response status is different from 2xx or 3xx, raises an HTTP::ClientError or HTTP::ServerError.
|
149
|
-
# <tt>options</tt>:: list of options to pass to the request (see below)
|
150
|
-
# == Options
|
151
|
-
# <tt>:query</tt>:: a hash of query parameters to pass along the request.
|
152
|
-
# E.g. : resource.delete(:query => {:param1 => "value1"})
|
153
|
-
# <tt>:headers</tt>:: a hash of HTTP headers to pass along the request.
|
154
|
-
# E.g. : resource.delete(:headers => {:accept => 'application/json'})
|
89
|
+
|
155
90
|
def delete(options = {})
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
91
|
+
if allow?(:delete)
|
92
|
+
session.delete(request.uri)
|
93
|
+
else
|
94
|
+
raise MethodNotAllowed
|
95
|
+
end
|
161
96
|
end
|
162
|
-
|
163
|
-
|
164
|
-
def stale!; @status = :stale; end
|
165
|
-
def stale?; @status == :stale; end
|
166
97
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
def http_methods
|
175
|
-
reload if executed_requests['GET'].nil? || executed_requests['GET']['headers'].nil? || executed_requests['GET']['headers'].empty?
|
176
|
-
(executed_requests['GET']['headers']['Allow'] || "GET").split(/,\s*/)
|
98
|
+
def update(*args)
|
99
|
+
if allow?(:put)
|
100
|
+
payload, options = extract_payload_from_args(args)
|
101
|
+
session.put(request.uri, payload, options)
|
102
|
+
else
|
103
|
+
raise MethodNotAllowed
|
104
|
+
end
|
177
105
|
end
|
178
|
-
|
179
|
-
def
|
180
|
-
|
106
|
+
|
107
|
+
def allow?(method)
|
108
|
+
response.allow?(method)
|
181
109
|
end
|
182
|
-
|
183
|
-
def inspect
|
184
|
-
|
110
|
+
|
111
|
+
def inspect
|
112
|
+
if media_type.complete?
|
113
|
+
properties.inspect
|
114
|
+
else
|
115
|
+
"{...}"
|
116
|
+
end
|
185
117
|
end
|
186
118
|
|
187
119
|
def pretty_print(pp)
|
188
|
-
pp.text
|
189
|
-
pp.text " uid=#{self['uid'].inspect}" if self.class == Resource
|
120
|
+
pp.text signature(false)
|
190
121
|
pp.nest 2 do
|
191
|
-
|
192
|
-
pp.text "@uri="
|
193
|
-
uri.pretty_print(pp)
|
194
|
-
if @links.length > 0
|
122
|
+
if relationships.length > 0
|
195
123
|
pp.breakable
|
196
|
-
pp.text "
|
124
|
+
pp.text "RELATIONSHIPS"
|
197
125
|
pp.nest 2 do
|
198
|
-
|
126
|
+
pp.breakable
|
127
|
+
pp.text "#{relationships.join(", ")}"
|
128
|
+
end
|
129
|
+
end
|
130
|
+
pp.breakable
|
131
|
+
if collection?
|
132
|
+
# display items
|
133
|
+
pp.text "ITEMS (#{offset}..#{offset+length})/#{total}"
|
134
|
+
pp.nest 2 do
|
135
|
+
self.each do |item|
|
199
136
|
pp.breakable
|
200
|
-
pp.text
|
201
|
-
pp.text "," if i < @links.length-1
|
137
|
+
pp.text item.signature(true)
|
202
138
|
end
|
203
|
-
end
|
204
|
-
|
205
|
-
if @properties.length > 0
|
206
|
-
pp.breakable
|
139
|
+
end
|
140
|
+
else
|
207
141
|
pp.text "PROPERTIES"
|
208
142
|
pp.nest 2 do
|
209
|
-
|
143
|
+
properties.each do |key, value|
|
210
144
|
pp.breakable
|
211
145
|
pp.text "#{key.inspect}=>"
|
212
146
|
value.pretty_print(pp)
|
213
|
-
pp.text "," if i < @properties.length-1
|
214
147
|
end
|
215
148
|
end
|
216
149
|
end
|
217
150
|
yield pp if block_given?
|
218
151
|
end
|
219
152
|
pp.text ">"
|
220
|
-
end
|
221
|
-
|
222
|
-
protected
|
223
|
-
def populate_object(key, value)
|
224
|
-
case key
|
225
|
-
when "links"
|
226
|
-
value.each{|link| define_link(Link.new(link))}
|
227
|
-
else
|
228
|
-
case value
|
229
|
-
when Hash
|
230
|
-
@properties.store(key, SpecialHash.new.replace(value)) unless @links.has_key?(key)
|
231
|
-
when Array
|
232
|
-
@properties.store(key, SpecialArray.new(value))
|
233
|
-
else
|
234
|
-
@properties.store(key, value)
|
235
|
-
end
|
236
|
-
end
|
237
153
|
end
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
154
|
+
|
155
|
+
def build
|
156
|
+
# only build once
|
157
|
+
if @associations.empty?
|
158
|
+
extend Collection if collection?
|
159
|
+
|
160
|
+
response.links.each do |link|
|
161
|
+
@associations[link.id] = nil
|
162
|
+
|
163
|
+
self.class.class_eval do
|
164
|
+
define_method link.id do |*args|
|
165
|
+
@associations[link.id] ||= session.get(link.href, :head => {
|
166
|
+
'Accept' => link.type
|
167
|
+
}).load(*args)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
249
171
|
end
|
250
|
-
else
|
251
|
-
session.logger.warn link.errors.join("\n")
|
252
172
|
end
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
protected
|
177
|
+
def extract_payload_from_args(args)
|
178
|
+
options = args.extract_options!
|
179
|
+
head = options.delete(:headers) || options.delete(:head)
|
180
|
+
query = options.delete(:query)
|
181
|
+
|
182
|
+
payload = args.shift || options
|
183
|
+
|
184
|
+
options = {
|
185
|
+
:head => head, :query => query
|
186
|
+
}
|
187
|
+
|
188
|
+
[payload, options]
|
253
189
|
end
|
254
190
|
|
255
|
-
|
256
|
-
end
|
191
|
+
|
192
|
+
end
|
193
|
+
end
|