hyperresource-aptible 0.9.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 +7 -0
- data/lib/hyper_resource.rb +302 -0
- data/lib/hyper_resource/adapter.rb +31 -0
- data/lib/hyper_resource/adapter/hal_json.rb +135 -0
- data/lib/hyper_resource/attributes.rb +100 -0
- data/lib/hyper_resource/exceptions.rb +40 -0
- data/lib/hyper_resource/link.rb +57 -0
- data/lib/hyper_resource/links.rb +63 -0
- data/lib/hyper_resource/modules/http.rb +129 -0
- data/lib/hyper_resource/modules/internal_attributes.rb +90 -0
- data/lib/hyper_resource/objects.rb +60 -0
- data/lib/hyper_resource/response.rb +2 -0
- data/lib/hyper_resource/version.rb +4 -0
- data/lib/hyperresource.rb +2 -0
- metadata +172 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8016963a4231079f134bfe019e5d8092f355321f
|
4
|
+
data.tar.gz: c51ccfdd3523321c47d9d9efb7a80c9039026407
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 91c88986ff52e6fe1f693559c887988de9dcfed25d8517b9f37d45f7e5b30609313647e7cdf9eaa97a833e6b9ccdfc215a8b13936eab0d86344461c535df6540
|
7
|
+
data.tar.gz: f0bcd175f570e738f7bb79452fb847d49adf67a02b6659bec16ed2e6fa9c6d2fa0fcaf7b31e42a8245be30a1930c3349919ede76e15d681b46017232bdf1bbb7
|
@@ -0,0 +1,302 @@
|
|
1
|
+
require 'hyper_resource/attributes'
|
2
|
+
require 'hyper_resource/exceptions'
|
3
|
+
require 'hyper_resource/link'
|
4
|
+
require 'hyper_resource/links'
|
5
|
+
require 'hyper_resource/objects'
|
6
|
+
require 'hyper_resource/response'
|
7
|
+
require 'hyper_resource/version'
|
8
|
+
|
9
|
+
require 'hyper_resource/adapter'
|
10
|
+
require 'hyper_resource/adapter/hal_json'
|
11
|
+
|
12
|
+
require 'hyper_resource/modules/http'
|
13
|
+
require 'hyper_resource/modules/internal_attributes'
|
14
|
+
|
15
|
+
require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
|
16
|
+
|
17
|
+
require 'pp'
|
18
|
+
|
19
|
+
## HyperResource is the main resource base class. Normally it will be used
|
20
|
+
## through subclassing, though it may also be used directly.
|
21
|
+
|
22
|
+
class HyperResource
|
23
|
+
|
24
|
+
include HyperResource::Modules::HTTP
|
25
|
+
include HyperResource::Modules::InternalAttributes
|
26
|
+
include Enumerable
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
DEFAULT_HEADERS = { 'Accept' => 'application/json' }
|
31
|
+
|
32
|
+
public
|
33
|
+
|
34
|
+
## Create a new HyperResource, given a hash of options. These options
|
35
|
+
## include:
|
36
|
+
##
|
37
|
+
## [root] The root URL of the resource.
|
38
|
+
##
|
39
|
+
## [auth] Authentication information. Currently only +{basic:
|
40
|
+
## ['key', 'secret']}+ is supported.
|
41
|
+
##
|
42
|
+
## [namespace] Class or class name, into which resources should be
|
43
|
+
## instantiated.
|
44
|
+
##
|
45
|
+
## [headers] Headers to send along with requests for this resource (as
|
46
|
+
## well as its eventual child resources, if any).
|
47
|
+
##
|
48
|
+
## [faraday_options] Configuration passed to +Faraday::Connection.initialize+,
|
49
|
+
## such as +{request: {timeout: 30}}+.
|
50
|
+
##
|
51
|
+
def initialize(opts={})
|
52
|
+
return init_from_resource(opts) if opts.kind_of?(HyperResource)
|
53
|
+
|
54
|
+
self.root = opts[:root] || self.class.root
|
55
|
+
self.href = opts[:href] || ''
|
56
|
+
self.auth = (self.class.auth || {}).merge(opts[:auth] || {})
|
57
|
+
self.namespace = opts[:namespace] || self.class.namespace
|
58
|
+
self.headers = DEFAULT_HEADERS.merge(self.class.headers || {}).
|
59
|
+
merge(opts[:headers] || {})
|
60
|
+
self.faraday_options = opts[:faraday_options] ||
|
61
|
+
self.class.faraday_options || {}
|
62
|
+
|
63
|
+
## There's a little acrobatics in getting Attributes, Links, and Objects
|
64
|
+
## into the correct subclass.
|
65
|
+
if self.class != HyperResource
|
66
|
+
if self.class::Attributes == HyperResource::Attributes
|
67
|
+
Object.module_eval(
|
68
|
+
"class #{self.class}::Attributes < HyperResource::Attributes; end"
|
69
|
+
)
|
70
|
+
end
|
71
|
+
if self.class::Links == HyperResource::Links
|
72
|
+
Object.module_eval(
|
73
|
+
"class #{self.class}::Links < HyperResource::Links; end"
|
74
|
+
)
|
75
|
+
end
|
76
|
+
if self.class::Objects == HyperResource::Objects
|
77
|
+
Object.module_eval(
|
78
|
+
"class #{self.class}::Objects < HyperResource::Objects; end"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
self.attributes = self.class::Attributes.new(self)
|
84
|
+
self.links = self.class::Links.new(self)
|
85
|
+
self.objects = self.class::Objects.new(self)
|
86
|
+
|
87
|
+
self.loaded = false
|
88
|
+
|
89
|
+
self.adapter = opts[:adapter] || self.class.adapter ||
|
90
|
+
HyperResource::Adapter::HAL_JSON
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
## Returns true if one or more of this object's attributes has been
|
95
|
+
## reassigned.
|
96
|
+
def changed?(*args)
|
97
|
+
attributes.changed?(*args)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
#### Filters
|
102
|
+
|
103
|
+
## +incoming_body_filter+ filters a hash of attribute keys and values
|
104
|
+
## on their way from a response body to a HyperResource. Override this
|
105
|
+
## in a subclass of HyperResource to implement filters on incoming data.
|
106
|
+
def incoming_body_filter(attr_hash)
|
107
|
+
attr_hash
|
108
|
+
end
|
109
|
+
|
110
|
+
## +outgoing_body_filter+ filters a hash of attribute keys and values
|
111
|
+
## on their way from a HyperResource to a request body. Override this
|
112
|
+
## in a subclass of HyperResource to implement filters on outgoing data.
|
113
|
+
def outgoing_body_filter(attr_hash)
|
114
|
+
attr_hash
|
115
|
+
end
|
116
|
+
|
117
|
+
## +outgoing_uri_filter+ filters a hash of attribute keys and values
|
118
|
+
## on their way from a HyperResource to a URL. Override this
|
119
|
+
## in a subclass of HyperResource to implement filters on outgoing URI
|
120
|
+
## parameters.
|
121
|
+
def outgoing_uri_filter(attr_hash)
|
122
|
+
attr_hash
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
#### Enumerable support
|
127
|
+
|
128
|
+
## Returns the *i*th object in the first collection of objects embedded
|
129
|
+
## in this resource. Returns nil on failure.
|
130
|
+
def [](i)
|
131
|
+
get unless loaded
|
132
|
+
self.objects.first[1][i] rescue nil
|
133
|
+
end
|
134
|
+
|
135
|
+
## Iterates over the objects in the first collection of embedded objects
|
136
|
+
## in this resource.
|
137
|
+
def each(&block)
|
138
|
+
get unless loaded
|
139
|
+
self.objects.first[1].each(&block) rescue nil
|
140
|
+
end
|
141
|
+
|
142
|
+
#### Magic
|
143
|
+
|
144
|
+
## method_missing will load this resource if not yet loaded, then
|
145
|
+
## attempt to delegate to +attributes+, then +objects+, then +links+.
|
146
|
+
## Override with care.
|
147
|
+
def method_missing(method, *args)
|
148
|
+
self.get unless self.loaded
|
149
|
+
|
150
|
+
method = method.to_s
|
151
|
+
if method[-1,1] == '='
|
152
|
+
return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
|
153
|
+
else
|
154
|
+
return attributes[method] if attributes && attributes.has_key?(method)
|
155
|
+
return objects[method] if objects && objects[method]
|
156
|
+
if links && links[method]
|
157
|
+
if args.count > 0
|
158
|
+
return links[method].where(*args)
|
159
|
+
else
|
160
|
+
return links[method]
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def inspect # @private
|
170
|
+
"#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
|
171
|
+
"@href=#{self.href.inspect} @loaded=#{self.loaded} "+
|
172
|
+
"@namespace=#{self.namespace.inspect} ...>"
|
173
|
+
end
|
174
|
+
|
175
|
+
## +response_body+, +response_object+, and +deserialized_response+
|
176
|
+
## are deprecated in favor of +body+. (Sorry. Naming things is hard.)
|
177
|
+
def response_body # @private
|
178
|
+
_hr_deprecate('HyperResource#response_body is deprecated. '+
|
179
|
+
'Please use HyperResource#body instead.')
|
180
|
+
body
|
181
|
+
end
|
182
|
+
def response_object # @private
|
183
|
+
_hr_deprecate('HyperResource#response_object is deprecated. '+
|
184
|
+
'Please use HyperResource#body instead.')
|
185
|
+
body
|
186
|
+
end
|
187
|
+
def deserialized_response # @private
|
188
|
+
_hr_deprecate('HyperResource#deserialized_response is deprecated. '+
|
189
|
+
'Please use HyperResource#body instead.')
|
190
|
+
body
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
|
195
|
+
## Return a new HyperResource based on this object and a given href.
|
196
|
+
def _hr_new_from_link(href) # @private
|
197
|
+
self.class.new(:root => self.root,
|
198
|
+
:auth => self.auth,
|
199
|
+
:headers => self.headers,
|
200
|
+
:namespace => self.namespace,
|
201
|
+
:faraday_options => self.faraday_options,
|
202
|
+
:href => href)
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
## Returns the class into which the given response should be cast.
|
207
|
+
## If the object is not loaded yet, or if +namespace+ is
|
208
|
+
## not set, returns +self+.
|
209
|
+
##
|
210
|
+
## Otherwise, +response_class+ uses +get_data_type_from_response+ to
|
211
|
+
## determine subclass name, glues it to the given namespace, and
|
212
|
+
## creates the class if it's not there yet. E.g., given a namespace of
|
213
|
+
## +FooAPI+ and a response content-type of
|
214
|
+
## "application/vnd.foocorp.fooapi.v1+json;type=User", this should
|
215
|
+
## return +FooAPI::User+ (even if +FooAPI::User+ hadn't existed yet).
|
216
|
+
def self.response_class(response, namespace)
|
217
|
+
if self.to_s == 'HyperResource'
|
218
|
+
return self unless namespace
|
219
|
+
end
|
220
|
+
|
221
|
+
namespace ||= self.to_s
|
222
|
+
|
223
|
+
type_name = self.get_data_type_from_response(response)
|
224
|
+
return self unless type_name
|
225
|
+
|
226
|
+
namespaced_class(type_name, namespace)
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.namespaced_class(type_name, namespace)
|
230
|
+
class_name = "#{namespace}::#{type_name}"
|
231
|
+
class_name.gsub!(/[^_0-9A-Za-z:]/, '') ## sanitize class_name
|
232
|
+
|
233
|
+
## Return data type class if it exists
|
234
|
+
klass = eval(class_name) rescue :sorry_dude
|
235
|
+
return klass if klass.is_a?(Class)
|
236
|
+
|
237
|
+
## Data type class didn't exist -- create namespace (if necessary),
|
238
|
+
## then the data type class
|
239
|
+
if namespace != ''
|
240
|
+
nsc = eval(namespace) rescue :bzzzzzt
|
241
|
+
unless nsc.is_a?(Class)
|
242
|
+
Object.module_eval "class #{namespace} < #{self}; end"
|
243
|
+
end
|
244
|
+
end
|
245
|
+
Object.module_eval "class #{class_name} < #{namespace}; end"
|
246
|
+
eval(class_name)
|
247
|
+
end
|
248
|
+
|
249
|
+
def _hr_response_class # @private
|
250
|
+
self.namespace ||= self.class.to_s unless self.class.to_s=='HyperResource'
|
251
|
+
self.class.response_class(self.response, self.namespace)
|
252
|
+
end
|
253
|
+
|
254
|
+
|
255
|
+
## Inspects the given Faraday::Response, and returns a string describing
|
256
|
+
## this resource's data type.
|
257
|
+
##
|
258
|
+
## By default, this method looks for a +type=...+ modifier in the
|
259
|
+
## response's +Content-type+ and returns that value, capitalized.
|
260
|
+
##
|
261
|
+
## Override this method in a subclass to alter HyperResource's behavior.
|
262
|
+
def self.get_data_type_from_response(response)
|
263
|
+
return nil unless response
|
264
|
+
return nil unless content_type = response['content-type']
|
265
|
+
return nil unless m=content_type.match(/;\s* type=([0-9A-Za-z:]+)/x)
|
266
|
+
m[1][0,1].upcase + m[1][1..-1]
|
267
|
+
end
|
268
|
+
|
269
|
+
## Uses +HyperResource.get_response_data_type+ to determine the proper
|
270
|
+
## data type for this object. Override to change behavior (though you
|
271
|
+
## probably just want to override the class method).
|
272
|
+
def get_data_type_from_response
|
273
|
+
self.class.get_data_type_from_response(self.response)
|
274
|
+
end
|
275
|
+
|
276
|
+
private
|
277
|
+
|
278
|
+
## Return this object, "cast" into its proper response class.
|
279
|
+
def to_response_class
|
280
|
+
response_class = self._hr_response_class
|
281
|
+
return self if self.class == response_class
|
282
|
+
response_class.new(self)
|
283
|
+
end
|
284
|
+
|
285
|
+
## Use the given resource's data to initialize this one.
|
286
|
+
def init_from_resource(resource)
|
287
|
+
(self.class._hr_attributes - [:attributes, :links, :objects]).each do |attr|
|
288
|
+
self.send("#{attr}=".to_sym, resource.send(attr))
|
289
|
+
end
|
290
|
+
self.adapter.apply(self.body, self)
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
## Show a deprecation message.
|
295
|
+
def self._hr_deprecate(message) # @private
|
296
|
+
STDERR.puts "#{message} (called from #{caller[2]})"
|
297
|
+
end
|
298
|
+
|
299
|
+
def _hr_deprecate(*args) # @private
|
300
|
+
self.class._hr_deprecate(*args)
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class HyperResource
|
2
|
+
|
3
|
+
## HyperResource::Adapter is the interface/abstract base class for
|
4
|
+
## adapters to different hypermedia formats (e.g., HAL+JSON). New
|
5
|
+
## adapters must implement the public methods of this class.
|
6
|
+
|
7
|
+
class Adapter
|
8
|
+
class << self
|
9
|
+
|
10
|
+
## Serialize the given object into a string.
|
11
|
+
def serialize(object)
|
12
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
13
|
+
"of HyperResource::Adapter must implement it."
|
14
|
+
end
|
15
|
+
|
16
|
+
## Deserialize a given string into an object (Hash).
|
17
|
+
def deserialize(string)
|
18
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
19
|
+
"of HyperResource::Adapter must implement it."
|
20
|
+
end
|
21
|
+
|
22
|
+
## Use a given deserialized response object (Hash) to update a given
|
23
|
+
## resource (HyperResource), returning the updated resource.
|
24
|
+
def apply(response, resource, opts={})
|
25
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
26
|
+
"of HyperResource::Adapter must implement it."
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
class HyperResource
|
5
|
+
class Adapter
|
6
|
+
|
7
|
+
## HyperResource::Adapter::HAL_JSON provides support for the HAL+JSON
|
8
|
+
## hypermedia format by implementing the interface defined in
|
9
|
+
## HyperResource::Adapter.
|
10
|
+
|
11
|
+
class HAL_JSON < Adapter
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def serialize(object)
|
15
|
+
JSON.dump(object)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deserialize(string)
|
19
|
+
JSON.parse(string)
|
20
|
+
end
|
21
|
+
|
22
|
+
def apply(response, resource, opts={})
|
23
|
+
if !response.kind_of?(Hash)
|
24
|
+
raise ArgumentError, "'response' argument must be a Hash"
|
25
|
+
end
|
26
|
+
if !resource.kind_of?(HyperResource)
|
27
|
+
raise ArgumentError, "'resource' argument must be a HyperResource"
|
28
|
+
end
|
29
|
+
|
30
|
+
apply_objects(response, resource)
|
31
|
+
apply_links(response, resource)
|
32
|
+
apply_attributes(response, resource)
|
33
|
+
resource.loaded = true
|
34
|
+
resource.href = response['_links']['self']['href'] rescue nil
|
35
|
+
resource
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def apply_objects(resp, rsrc)
|
42
|
+
return unless resp['_embedded']
|
43
|
+
rc = rsrc.class
|
44
|
+
rsrc.objects = rc::Objects.new(rsrc)
|
45
|
+
objs = rsrc.objects
|
46
|
+
|
47
|
+
resp['_embedded'].each do |name, collection|
|
48
|
+
if collection.is_a? Hash
|
49
|
+
r = rc.new(:root => rsrc.root,
|
50
|
+
:headers => rsrc.headers,
|
51
|
+
:namespace => rsrc.namespace)
|
52
|
+
r.body = collection
|
53
|
+
objs[name] = apply(collection, r)
|
54
|
+
else
|
55
|
+
objs[name] = collection.map do |obj|
|
56
|
+
r = rc.new(:root => rsrc.root,
|
57
|
+
:headers => rsrc.headers,
|
58
|
+
:namespace => rsrc.namespace)
|
59
|
+
r.body = obj
|
60
|
+
r = classify(obj, r)
|
61
|
+
apply(obj, r)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# objs._hr_create_methods!
|
67
|
+
end
|
68
|
+
|
69
|
+
def classify(resp, rsrc)
|
70
|
+
return rsrc unless (type_name = get_data_type_from_object(resp)) &&
|
71
|
+
(namespace = rsrc.namespace)
|
72
|
+
klass = rsrc.class.namespaced_class(type_name, namespace)
|
73
|
+
|
74
|
+
if klass
|
75
|
+
rsrc = klass.new(:root => rsrc.root,
|
76
|
+
:headers => rsrc.headers,
|
77
|
+
:namespace => rsrc.namespace)
|
78
|
+
rsrc.body = resp
|
79
|
+
end
|
80
|
+
rsrc
|
81
|
+
end
|
82
|
+
|
83
|
+
def get_data_type_from_object(object)
|
84
|
+
return nil unless object && object['type']
|
85
|
+
object['type'][0].upcase + object['type'][1..-1]
|
86
|
+
end
|
87
|
+
|
88
|
+
def apply_links(resp, rsrc)
|
89
|
+
return unless resp['_links']
|
90
|
+
rsrc.links = rsrc._hr_response_class::Links.new(rsrc)
|
91
|
+
links = rsrc.links
|
92
|
+
|
93
|
+
resp['_links'].each do |rel, link_spec|
|
94
|
+
keys = [rel]
|
95
|
+
if m=rel.match(/.+:(.+)/)
|
96
|
+
keys << m[1]
|
97
|
+
end
|
98
|
+
keys.each do |key|
|
99
|
+
if link_spec.is_a? Array
|
100
|
+
links[key] = link_spec.map do |link|
|
101
|
+
new_link_from_spec(rsrc, link)
|
102
|
+
end
|
103
|
+
else
|
104
|
+
links[key] = new_link_from_spec(rsrc, link_spec)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# links._hr_create_methods!
|
110
|
+
end
|
111
|
+
|
112
|
+
def new_link_from_spec(resource, link_spec)
|
113
|
+
resource.class::Link.new(resource, link_spec)
|
114
|
+
end
|
115
|
+
|
116
|
+
|
117
|
+
def apply_attributes(resp, rsrc)
|
118
|
+
rsrc.attributes = rsrc._hr_response_class::Attributes.new(rsrc)
|
119
|
+
|
120
|
+
given_attrs = resp.reject{|k,v| %w(_links _embedded).include?(k)}
|
121
|
+
filtered_attrs = rsrc.incoming_body_filter(given_attrs)
|
122
|
+
|
123
|
+
filtered_attrs.keys.each do |attr|
|
124
|
+
rsrc.attributes[attr] = filtered_attrs[attr]
|
125
|
+
end
|
126
|
+
|
127
|
+
rsrc.attributes._hr_clear_changed
|
128
|
+
# rsrc.attributes._hr_create_methods!
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
@@ -0,0 +1,100 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Attributes < Hash
|
3
|
+
|
4
|
+
attr_accessor :_resource # @private
|
5
|
+
|
6
|
+
def initialize(resource=nil) # @private
|
7
|
+
self._resource = resource || HyperResource.new
|
8
|
+
end
|
9
|
+
|
10
|
+
## Creates accessor methods in self.class and self._resource.class.
|
11
|
+
## Protects against method creation into HyperResource::Attributes and
|
12
|
+
## HyperResource classes. Just subclasses, please!
|
13
|
+
def _hr_create_methods!(opts={}) # @private
|
14
|
+
return if self.class.to_s == 'HyperResource::Attributes'
|
15
|
+
return if self._resource.class.to_s == 'HyperResource'
|
16
|
+
return if self.class.send(
|
17
|
+
:class_variable_defined?, :@@_hr_created_attributes_methods)
|
18
|
+
|
19
|
+
self.keys.each do |attr|
|
20
|
+
attr_sym = attr.to_sym
|
21
|
+
attr_eq_sym = "#{attr}=".to_sym
|
22
|
+
|
23
|
+
self.class.send(:define_method, attr_sym) do
|
24
|
+
self[attr]
|
25
|
+
end
|
26
|
+
self.class.send(:define_method, attr_eq_sym) do |val|
|
27
|
+
self[attr] = val
|
28
|
+
end
|
29
|
+
|
30
|
+
## Don't stomp on _resource's methods
|
31
|
+
unless _resource.respond_to?(attr_sym)
|
32
|
+
_resource.class.send(:define_method, attr_sym) do
|
33
|
+
attributes.send(attr_sym)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
unless _resource.respond_to?(attr_eq_sym)
|
37
|
+
_resource.class.send(:define_method, attr_eq_sym) do |val|
|
38
|
+
attributes.send(attr_eq_sym, val)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
## This is a good time to mark this object as not-changed
|
44
|
+
_hr_clear_changed
|
45
|
+
|
46
|
+
self.class.send(:class_variable_set, :@@_hr_created_attributes_methods, true)
|
47
|
+
end
|
48
|
+
|
49
|
+
## Returns +true+ if the given attribute has been changed since creation
|
50
|
+
## time, +false+ otherwise.
|
51
|
+
## If no attribute is given, return whether any attributes have been
|
52
|
+
## changed.
|
53
|
+
def changed?(attr=nil)
|
54
|
+
@_hr_changed ||= Hash.new(false)
|
55
|
+
return @_hr_changed[attr.to_sym] if attr
|
56
|
+
return @_hr_changed.keys.count > 0
|
57
|
+
end
|
58
|
+
|
59
|
+
## Returns a hash of the attributes and values which have been changed
|
60
|
+
## since creation time.
|
61
|
+
def changed_attributes
|
62
|
+
@_hr_changed.select{|k,v| v}.keys.inject({}) {|h,k| h[k]=self[k]; h}
|
63
|
+
end
|
64
|
+
|
65
|
+
def []=(attr, value) # @private
|
66
|
+
return self[attr] if self.has_key?(attr.to_s) && self[attr] == value
|
67
|
+
_hr_mark_changed(attr)
|
68
|
+
super(attr.to_s, value)
|
69
|
+
end
|
70
|
+
|
71
|
+
def [](key) # @private
|
72
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
73
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def method_missing(method, *args) # @private
|
78
|
+
method = method.to_s
|
79
|
+
if has_key?(method)
|
80
|
+
self[method]
|
81
|
+
elsif method[-1,1] == '='
|
82
|
+
self[method[0..-2]] = args.first
|
83
|
+
else
|
84
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def _hr_clear_changed # @private
|
89
|
+
@_hr_changed = nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def _hr_mark_changed(attr, is_changed=true) # @private
|
93
|
+
attr = attr.to_sym
|
94
|
+
@_hr_changed ||= Hash.new(false)
|
95
|
+
@_hr_changed[attr] = is_changed
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Exception < ::StandardError
|
3
|
+
## The internal exception which led to this one, if any.
|
4
|
+
attr_accessor :cause
|
5
|
+
|
6
|
+
def initialize(message, attrs={}) # @private
|
7
|
+
self.cause = attrs[:cause]
|
8
|
+
super(message)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ResponseError < Exception
|
13
|
+
## The +Faraday::Response+ object which led to this exception.
|
14
|
+
attr_accessor :response
|
15
|
+
|
16
|
+
## The deserialized response body which led to this exception.
|
17
|
+
## May be blank, e.g. in case of deserialization errors.
|
18
|
+
attr_accessor :body
|
19
|
+
|
20
|
+
def initialize(message, attrs={}) # @private
|
21
|
+
self.response = attrs[:response]
|
22
|
+
self.body = attrs[:body]
|
23
|
+
|
24
|
+
## Try to help out with the message
|
25
|
+
if self.body
|
26
|
+
if error = self.body['error']
|
27
|
+
message = "#{message} (#{error})"
|
28
|
+
end
|
29
|
+
elsif self.response
|
30
|
+
message = "#{message} (\"#{self.response.inspect}\")"
|
31
|
+
end
|
32
|
+
|
33
|
+
super(message, attrs)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ClientError < ResponseError; end
|
38
|
+
class ServerError < ResponseError; end
|
39
|
+
end
|
40
|
+
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'uri_template'
|
2
|
+
|
3
|
+
class HyperResource::Link
|
4
|
+
attr_accessor :base_href,
|
5
|
+
:name,
|
6
|
+
:templated,
|
7
|
+
:params,
|
8
|
+
:parent_resource
|
9
|
+
|
10
|
+
## Returns true if this link is templated.
|
11
|
+
def templated?; templated end
|
12
|
+
|
13
|
+
def initialize(resource=nil, link_spec={})
|
14
|
+
self.parent_resource = resource || HyperResource.new
|
15
|
+
self.base_href = link_spec['href']
|
16
|
+
self.name = link_spec['name']
|
17
|
+
self.templated = !!link_spec['templated']
|
18
|
+
self.params = link_spec['params'] || {}
|
19
|
+
end
|
20
|
+
|
21
|
+
## Returns this link's href, applying any URI template params.
|
22
|
+
def href
|
23
|
+
if self.templated?
|
24
|
+
filtered_params = self.parent_resource.outgoing_uri_filter(params)
|
25
|
+
URITemplate.new(self.base_href).expand(filtered_params)
|
26
|
+
else
|
27
|
+
self.base_href
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
## Returns a new scope with the given params; that is, returns a copy of
|
32
|
+
## itself with the given params applied.
|
33
|
+
def where(params)
|
34
|
+
params = Hash[ params.map{|(k,v)| [k.to_s, v]} ]
|
35
|
+
self.class.new(self.parent_resource,
|
36
|
+
'href' => self.base_href,
|
37
|
+
'name' => self.name,
|
38
|
+
'templated' => self.templated,
|
39
|
+
'params' => self.params.merge(params))
|
40
|
+
end
|
41
|
+
|
42
|
+
## Returns a HyperResource representing this link
|
43
|
+
def resource
|
44
|
+
parent_resource._hr_new_from_link(self.href)
|
45
|
+
end
|
46
|
+
|
47
|
+
## Returns a HyperResource representing this link, and fetches it.
|
48
|
+
def get
|
49
|
+
self.resource.get
|
50
|
+
end
|
51
|
+
|
52
|
+
## If we were called with a method we don't know, load this resource
|
53
|
+
## and pass the message along. This achieves implicit loading.
|
54
|
+
def method_missing(method, *args)
|
55
|
+
self.get.send(method, *args)
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Links < Hash
|
3
|
+
attr_accessor :_resource
|
4
|
+
|
5
|
+
def initialize(resource=nil)
|
6
|
+
self._resource = resource || HyperResource.new
|
7
|
+
end
|
8
|
+
|
9
|
+
## Creates accessor methods in self.class and self._resource.class.
|
10
|
+
## Protects against method creation into HyperResource::Links and
|
11
|
+
## HyperResource classes. Just subclasses, please!
|
12
|
+
def _hr_create_methods!(opts={}) # @private
|
13
|
+
return if self.class.to_s == 'HyperResource::Links'
|
14
|
+
return if self._resource.class.to_s == 'HyperResource'
|
15
|
+
return if self.class.send(
|
16
|
+
:class_variable_defined?, :@@_hr_created_links_methods)
|
17
|
+
|
18
|
+
self.keys.each do |attr|
|
19
|
+
attr_sym = attr.to_sym
|
20
|
+
self.class.send(:define_method, attr_sym) do |*args|
|
21
|
+
if args.count > 0
|
22
|
+
self[attr].where(*args)
|
23
|
+
else
|
24
|
+
self[attr]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
## Don't stomp on _resource's methods
|
29
|
+
unless _resource.respond_to?(attr_sym)
|
30
|
+
_resource.class.send(:define_method, attr_sym) do |*args|
|
31
|
+
links.send(attr_sym, *args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
self.class.send(:class_variable_set, :@@_hr_created_links_methods, true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def []=(attr, value) # @private
|
40
|
+
super(attr.to_s, value)
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](key) # @private
|
44
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
45
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def method_missing(method, *args) # @private
|
50
|
+
unless self[method]
|
51
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
52
|
+
end
|
53
|
+
|
54
|
+
if args.count > 0
|
55
|
+
self[method].where(*args)
|
56
|
+
else
|
57
|
+
self[method]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,129 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'uri'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class HyperResource
|
6
|
+
module Modules
|
7
|
+
module HTTP
|
8
|
+
|
9
|
+
## Loads and returns the resource pointed to by +href+. The returned
|
10
|
+
## resource will be blessed into its "proper" class, if
|
11
|
+
## +self.class.namespace != nil+.
|
12
|
+
def get
|
13
|
+
self.response = faraday_connection.get(self.href || '')
|
14
|
+
finish_up
|
15
|
+
end
|
16
|
+
|
17
|
+
## By default, calls +post+ with the given arguments. Override to
|
18
|
+
## change this behavior.
|
19
|
+
def create(*args)
|
20
|
+
post(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
## POSTs the given attributes to this resource's href, and returns
|
24
|
+
## the response resource.
|
25
|
+
def post(attrs)
|
26
|
+
self.response = faraday_connection.post do |req|
|
27
|
+
req.body = adapter.serialize(attrs)
|
28
|
+
end
|
29
|
+
finish_up
|
30
|
+
end
|
31
|
+
|
32
|
+
## By default, calls +put+ with the given arguments. Override to
|
33
|
+
## change this behavior.
|
34
|
+
def update(*args)
|
35
|
+
put(*args)
|
36
|
+
end
|
37
|
+
|
38
|
+
## PUTs this resource's attributes to this resource's href, and returns
|
39
|
+
## the response resource. If attributes are given, +put+ uses those
|
40
|
+
## instead.
|
41
|
+
def put(attrs=nil)
|
42
|
+
attrs ||= self.attributes
|
43
|
+
self.response = faraday_connection.put do |req|
|
44
|
+
req.body = adapter.serialize(attrs)
|
45
|
+
end
|
46
|
+
finish_up
|
47
|
+
end
|
48
|
+
|
49
|
+
## PATCHes this resource's changed attributes to this resource's href,
|
50
|
+
## and returns the response resource. If attributes are given, +patch+
|
51
|
+
## uses those instead.
|
52
|
+
def patch(attrs=nil)
|
53
|
+
attrs ||= self.attributes.changed_attributes
|
54
|
+
self.response = faraday_connection.patch do |req|
|
55
|
+
req.body = adapter.serialize(attrs)
|
56
|
+
end
|
57
|
+
finish_up
|
58
|
+
end
|
59
|
+
|
60
|
+
## DELETEs this resource's href, and returns the response resource.
|
61
|
+
def delete
|
62
|
+
self.response = faraday_connection.delete
|
63
|
+
finish_up
|
64
|
+
end
|
65
|
+
|
66
|
+
## Returns a raw Faraday connection to this resource's URL, with proper
|
67
|
+
## headers (including auth). Threadsafe.
|
68
|
+
def faraday_connection(url=nil)
|
69
|
+
url ||= URI.join(self.root, self.href)
|
70
|
+
key = Digest::MD5.hexdigest({
|
71
|
+
'faraday_connection' => {
|
72
|
+
'url' => url,
|
73
|
+
'headers' => self.headers,
|
74
|
+
'ba' => self.auth[:basic]
|
75
|
+
}
|
76
|
+
}.to_json)
|
77
|
+
return Thread.current[key] if Thread.current[key]
|
78
|
+
|
79
|
+
fc = Faraday.new(self.faraday_options.merge(:url => url))
|
80
|
+
fc.headers.merge!('User-Agent' => "HyperResource #{HyperResource::VERSION}")
|
81
|
+
fc.headers.merge!(self.headers || {})
|
82
|
+
if ba=self.auth[:basic]
|
83
|
+
fc.basic_auth(*ba)
|
84
|
+
end
|
85
|
+
Thread.current[key] = fc
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def finish_up
|
91
|
+
begin
|
92
|
+
self.body = self.adapter.deserialize(self.response.body) unless self.response.body.nil?
|
93
|
+
rescue StandardError => e
|
94
|
+
raise HyperResource::ResponseError.new(
|
95
|
+
"Error when deserializing response body",
|
96
|
+
:response => self.response,
|
97
|
+
:cause => e
|
98
|
+
)
|
99
|
+
end
|
100
|
+
|
101
|
+
self.adapter.apply(self.body, self)
|
102
|
+
self.loaded = true
|
103
|
+
|
104
|
+
status = self.response.status
|
105
|
+
if status / 100 == 2
|
106
|
+
return to_response_class
|
107
|
+
elsif status / 100 == 3
|
108
|
+
## TODO redirect logic?
|
109
|
+
elsif status / 100 == 4
|
110
|
+
raise HyperResource::ClientError.new(status.to_s,
|
111
|
+
:response => self.response,
|
112
|
+
:body => self.body)
|
113
|
+
elsif status / 100 == 5
|
114
|
+
raise HyperResource::ServerError.new(status.to_s,
|
115
|
+
:response => self.response,
|
116
|
+
:body => self.body)
|
117
|
+
|
118
|
+
else ## 1xx? really?
|
119
|
+
raise HyperResource::ResponseError.new("Got status #{status}, wtf?",
|
120
|
+
:response => self.response,
|
121
|
+
:body => self.body)
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module HyperResource::Modules
|
2
|
+
module InternalAttributes
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend(ClassMethods)
|
6
|
+
|
7
|
+
base._hr_class_attributes.each do |attr|
|
8
|
+
base._hr_class_attribute attr
|
9
|
+
end
|
10
|
+
|
11
|
+
(base._hr_attributes - base._hr_class_attributes).each do |attr|
|
12
|
+
base.send(:attr_accessor, attr)
|
13
|
+
end
|
14
|
+
|
15
|
+
## Fallback attributes fall back from instance to class.
|
16
|
+
(base._hr_attributes & base._hr_class_attributes).each do |attr|
|
17
|
+
base._hr_fallback_attribute attr
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
def _hr_class_attributes # @private
|
24
|
+
[ :root, ## e.g. 'https://example.com/api/v1'
|
25
|
+
:auth, ## e.g. {:basic => ['username', 'password']}
|
26
|
+
:headers, ## e.g. {'Accept' => 'application/vnd.example+json'}
|
27
|
+
:namespace, ## e.g. 'ExampleAPI', or the class ExampleAPI itself
|
28
|
+
:adapter, ## subclass of HR::Adapter
|
29
|
+
:faraday_options ## e.g. {:request => {:timeout => 30}}
|
30
|
+
]
|
31
|
+
end
|
32
|
+
|
33
|
+
def _hr_attributes # @private
|
34
|
+
[ :root,
|
35
|
+
:href,
|
36
|
+
:auth,
|
37
|
+
:headers,
|
38
|
+
:namespace,
|
39
|
+
:adapter,
|
40
|
+
:faraday_options,
|
41
|
+
|
42
|
+
:request,
|
43
|
+
:response,
|
44
|
+
:body,
|
45
|
+
|
46
|
+
:attributes,
|
47
|
+
:links,
|
48
|
+
:objects,
|
49
|
+
|
50
|
+
:loaded
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
## Inheritable class attribute, kinda like in Rails.
|
55
|
+
def _hr_class_attribute(*names)
|
56
|
+
names.map(&:to_sym).each do |name|
|
57
|
+
instance_eval <<-EOT
|
58
|
+
def #{name}=(val)
|
59
|
+
@#{name} = val
|
60
|
+
end
|
61
|
+
def #{name}
|
62
|
+
return @#{name} if defined?(@#{name})
|
63
|
+
return superclass.#{name} if superclass.respond_to?(:#{name})
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
EOT
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
## Instance attributes which fall back to class attributes.
|
71
|
+
def _hr_fallback_attribute(*names)
|
72
|
+
names.map(&:to_sym).each do |name|
|
73
|
+
class_eval <<-EOT
|
74
|
+
def #{name}=(val)
|
75
|
+
@#{name} = val
|
76
|
+
end
|
77
|
+
def #{name}
|
78
|
+
return @#{name} if defined?(@#{name})
|
79
|
+
return self.class.#{name} if self.class.respond_to?(:#{name})
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
EOT
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end # ClassMethods
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,60 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Objects < Hash
|
3
|
+
attr_accessor :_resource
|
4
|
+
|
5
|
+
def initialize(resource=nil)
|
6
|
+
self._resource = resource || HyperResource.new
|
7
|
+
end
|
8
|
+
|
9
|
+
## Creates accessor methods in self.class and self._resource.class.
|
10
|
+
## Protects against method creation into HyperResource::Objects and
|
11
|
+
## HyperResource classes. Just subclasses, please!
|
12
|
+
def _hr_create_methods!(opts={}) # @private
|
13
|
+
return if self.class.to_s == 'HyperResource::Objects'
|
14
|
+
return if self._resource.class.to_s == 'HyperResource'
|
15
|
+
return if self.class.send(
|
16
|
+
:class_variable_defined?, :@@_hr_created_objects_methods)
|
17
|
+
|
18
|
+
self.keys.each do |attr|
|
19
|
+
attr_sym = attr.to_sym
|
20
|
+
|
21
|
+
self.class.send(:define_method, attr_sym) do
|
22
|
+
self[attr]
|
23
|
+
end
|
24
|
+
|
25
|
+
## Don't stomp on _resource's methods
|
26
|
+
unless _resource.respond_to?(attr_sym)
|
27
|
+
_resource.class.send(:define_method, attr_sym) do
|
28
|
+
objects.send(attr_sym)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
self.class.send(:class_variable_set, :@@_hr_created_objects_methods, true)
|
34
|
+
end
|
35
|
+
|
36
|
+
def []=(attr, value) # @private
|
37
|
+
super(attr.to_s, value)
|
38
|
+
end
|
39
|
+
|
40
|
+
## When +key+ is a string, returns the array of objects under that name.
|
41
|
+
## When +key+ is a number, returns +ith(key)+. Returns nil on lookup
|
42
|
+
## failure.
|
43
|
+
def [](key)
|
44
|
+
case key
|
45
|
+
when String, Symbol
|
46
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
47
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
48
|
+
when Fixnum
|
49
|
+
return ith(key)
|
50
|
+
end
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(method, *args) # @private
|
55
|
+
return self[method] if self[method]
|
56
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: hyperresource-aptible
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Frank Macreery
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: uri_template
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.5.2
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.5.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faraday
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.8.6
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.8.6
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: json
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.3'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.3'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 10.0.4
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 10.0.4
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: minitest
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.7.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.7.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: mocha
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - '>='
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.13.3
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.13.3
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: sinatra
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 1.4.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - '>='
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: 1.4.0
|
125
|
+
description: |2
|
126
|
+
HyperResource is a hypermedia client library for Ruby. Its goals are to
|
127
|
+
interface directly with well-behaved hypermedia APIs, to allow the data
|
128
|
+
returned from these APIs to optionally be extended by client-side code,
|
129
|
+
and to present a modern replacement for ActiveResource.
|
130
|
+
email: frank@macreery.com
|
131
|
+
executables: []
|
132
|
+
extensions: []
|
133
|
+
extra_rdoc_files: []
|
134
|
+
files:
|
135
|
+
- lib/hyper_resource.rb
|
136
|
+
- lib/hyper_resource/adapter.rb
|
137
|
+
- lib/hyper_resource/adapter/hal_json.rb
|
138
|
+
- lib/hyper_resource/attributes.rb
|
139
|
+
- lib/hyper_resource/exceptions.rb
|
140
|
+
- lib/hyper_resource/link.rb
|
141
|
+
- lib/hyper_resource/links.rb
|
142
|
+
- lib/hyper_resource/modules/http.rb
|
143
|
+
- lib/hyper_resource/modules/internal_attributes.rb
|
144
|
+
- lib/hyper_resource/objects.rb
|
145
|
+
- lib/hyper_resource/response.rb
|
146
|
+
- lib/hyper_resource/version.rb
|
147
|
+
- lib/hyperresource.rb
|
148
|
+
homepage: https://github.com/fancyremarker/hyperresource
|
149
|
+
licenses:
|
150
|
+
- MIT
|
151
|
+
metadata: {}
|
152
|
+
post_install_message:
|
153
|
+
rdoc_options: []
|
154
|
+
require_paths:
|
155
|
+
- lib
|
156
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - '>='
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: 1.8.7
|
161
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
requirements: []
|
167
|
+
rubyforge_project:
|
168
|
+
rubygems_version: 2.2.1
|
169
|
+
signing_key:
|
170
|
+
specification_version: 4
|
171
|
+
summary: Extensible hypermedia client for Ruby
|
172
|
+
test_files: []
|