hyperresource 0.2.4 → 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 +4 -4
- data/lib/hyper_resource.rb +79 -136
- data/lib/hyper_resource/adapter/hal_json.rb +14 -33
- data/lib/hyper_resource/attributes.rb +21 -47
- data/lib/hyper_resource/configuration.rb +159 -0
- data/lib/hyper_resource/exceptions.rb +4 -2
- data/lib/hyper_resource/link.rb +91 -50
- data/lib/hyper_resource/links.rb +42 -36
- data/lib/hyper_resource/modules/config_attributes.rb +293 -0
- data/lib/hyper_resource/modules/data_type.rb +98 -0
- data/lib/hyper_resource/modules/deprecations.rb +56 -0
- data/lib/hyper_resource/modules/http.rb +155 -46
- data/lib/hyper_resource/modules/internal_attributes.rb +7 -18
- data/lib/hyper_resource/objects.rb +10 -29
- data/lib/hyper_resource/version.rb +3 -2
- metadata +24 -6
- data/lib/hyper_resource/response.rb +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 524a169b824f7c61e5aa5c05109415206095ad99
|
4
|
+
data.tar.gz: 70538dc63539ed231dfb71d5cedaccedaf081bda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 875712e54fc2b58369277dc07d4b2418b8726aeef5dac678584ba0cbc5d617ab6a626d57365381ce3dd3ec3924de0b714060a6f6472e61396258c4cb40439fd8
|
7
|
+
data.tar.gz: 97a73477ec2d1f378b0ae5904f0c09228dd581b7f8d21289e07311c012f3b8ee2ee2f3fef08a04d15e7ba9d2af4b09ab1d19b5f489e1c7e17912dc51d6abb124
|
data/lib/hyper_resource.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
require 'hyper_resource/attributes'
|
2
|
+
require 'hyper_resource/configuration'
|
2
3
|
require 'hyper_resource/exceptions'
|
3
4
|
require 'hyper_resource/link'
|
4
5
|
require 'hyper_resource/links'
|
5
6
|
require 'hyper_resource/objects'
|
6
|
-
require 'hyper_resource/response'
|
7
7
|
require 'hyper_resource/version'
|
8
8
|
|
9
9
|
require 'hyper_resource/adapter'
|
10
10
|
require 'hyper_resource/adapter/hal_json'
|
11
11
|
|
12
|
+
require 'hyper_resource/modules/data_type'
|
13
|
+
require 'hyper_resource/modules/deprecations'
|
12
14
|
require 'hyper_resource/modules/http'
|
15
|
+
require 'hyper_resource/modules/config_attributes'
|
13
16
|
require 'hyper_resource/modules/internal_attributes'
|
14
17
|
|
15
18
|
require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
|
@@ -21,7 +24,9 @@ require 'pp'
|
|
21
24
|
|
22
25
|
class HyperResource
|
23
26
|
|
24
|
-
include HyperResource::Modules::
|
27
|
+
include HyperResource::Modules::ConfigAttributes
|
28
|
+
include HyperResource::Modules::DataType
|
29
|
+
include HyperResource::Modules::Deprecations
|
25
30
|
include HyperResource::Modules::InternalAttributes
|
26
31
|
include Enumerable
|
27
32
|
|
@@ -49,16 +54,23 @@ public
|
|
49
54
|
## such as +{request: {timeout: 30}}+.
|
50
55
|
##
|
51
56
|
def initialize(opts={})
|
52
|
-
|
57
|
+
self.root = opts[:root] if opts[:root]
|
58
|
+
self.href = opts[:href] if opts[:href]
|
53
59
|
|
54
|
-
self.
|
55
|
-
|
56
|
-
self.
|
57
|
-
self.
|
58
|
-
self.
|
59
|
-
|
60
|
-
self.
|
61
|
-
|
60
|
+
self.hr_config = self.class.hr_config.clone
|
61
|
+
|
62
|
+
self.adapter = opts[:adapter] if opts[:adapter]
|
63
|
+
self.faraday_options = opts[:faraday_options] if opts[:faraday_options]
|
64
|
+
self.auth = opts[:auth] if opts[:auth]
|
65
|
+
|
66
|
+
self.headers = DEFAULT_HEADERS.
|
67
|
+
merge(self.class.headers || {}).
|
68
|
+
merge(opts[:headers] || {})
|
69
|
+
|
70
|
+
self.namespace = opts[:namespace] if opts[:namespace]
|
71
|
+
if !self.namespace && self.class != HyperResource
|
72
|
+
self.namespace = self.class.namespace || self.class.to_s
|
73
|
+
end
|
62
74
|
|
63
75
|
## There's a little acrobatics in getting Attributes, Links, and Objects
|
64
76
|
## into the correct subclass.
|
@@ -83,11 +95,41 @@ public
|
|
83
95
|
self.attributes = self.class::Attributes.new(self)
|
84
96
|
self.links = self.class::Links.new(self)
|
85
97
|
self.objects = self.class::Objects.new(self)
|
86
|
-
|
87
98
|
self.loaded = false
|
99
|
+
end
|
100
|
+
|
88
101
|
|
89
|
-
|
90
|
-
|
102
|
+
|
103
|
+
## Creates a new resource given args :link, :resource, :href, :response, :url,
|
104
|
+
## and :body. Either :link or (:resource and :href and :url) are required.
|
105
|
+
# @private
|
106
|
+
def self.new_from(args)
|
107
|
+
link = args[:link]
|
108
|
+
resource = args[:resource] || link.resource
|
109
|
+
href = args[:href] || link.href
|
110
|
+
url = args[:url] || URI.join(resource.root, href || '')
|
111
|
+
response = args[:response]
|
112
|
+
body = args[:body] || {}
|
113
|
+
|
114
|
+
old_rsrc = resource
|
115
|
+
new_class = old_rsrc.get_data_type_class(:resource => old_rsrc,
|
116
|
+
:link => link,
|
117
|
+
:url => url,
|
118
|
+
:response => response,
|
119
|
+
:body => body)
|
120
|
+
new_rsrc = new_class.new(:root => old_rsrc.root,
|
121
|
+
:href => href)
|
122
|
+
new_rsrc.hr_config = old_rsrc.hr_config.clone
|
123
|
+
new_rsrc.response = response
|
124
|
+
new_rsrc.body = body
|
125
|
+
new_rsrc.adapter.apply(body, new_rsrc)
|
126
|
+
new_rsrc.loaded = true
|
127
|
+
new_rsrc
|
128
|
+
end
|
129
|
+
|
130
|
+
# @private
|
131
|
+
def new_from(args)
|
132
|
+
self.class.new_from(args)
|
91
133
|
end
|
92
134
|
|
93
135
|
|
@@ -143,15 +185,20 @@ public
|
|
143
185
|
|
144
186
|
## method_missing will load this resource if not yet loaded, then
|
145
187
|
## attempt to delegate to +attributes+, then +objects+, then +links+.
|
146
|
-
## Override with care.
|
188
|
+
## Override with extreme care.
|
147
189
|
def method_missing(method, *args)
|
148
|
-
|
190
|
+
## If not loaded, load and retry.
|
191
|
+
unless loaded
|
192
|
+
return self.get.send(method, *args)
|
193
|
+
end
|
194
|
+
|
149
195
|
|
196
|
+
## Otherwise, try to match against attributes, then objects, then links.
|
150
197
|
method = method.to_s
|
151
198
|
if method[-1,1] == '='
|
152
199
|
return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
|
153
200
|
else
|
154
|
-
return attributes[method] if attributes && attributes
|
201
|
+
return attributes[method] if attributes && attributes.has_key?(method.to_s)
|
155
202
|
return objects[method] if objects && objects[method]
|
156
203
|
if links && links[method]
|
157
204
|
if args.count > 0
|
@@ -165,6 +212,16 @@ public
|
|
165
212
|
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
166
213
|
end
|
167
214
|
|
215
|
+
## respond_to? is patched to return +true+ if +method_missing+ would
|
216
|
+
## successfully delegate a method call to +attributes+, +links+, or
|
217
|
+
## +objects+.
|
218
|
+
def respond_to?(method, *args)
|
219
|
+
return true if self.links && self.links.respond_to?(method)
|
220
|
+
return true if self.attributes && self.attributes.respond_to?(method)
|
221
|
+
return true if self.objects && self.objects.respond_to?(method)
|
222
|
+
super
|
223
|
+
end
|
224
|
+
|
168
225
|
|
169
226
|
def inspect # @private
|
170
227
|
"#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
|
@@ -172,127 +229,13 @@ public
|
|
172
229
|
"@namespace=#{self.namespace.inspect} ...>"
|
173
230
|
end
|
174
231
|
|
175
|
-
|
176
|
-
|
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
|
-
class_name = "#{namespace}::#{type_name}"
|
227
|
-
class_name.gsub!(/[^_0-9A-Za-z:]/, '') ## sanitize class_name
|
228
|
-
|
229
|
-
## Return data type class if it exists
|
230
|
-
klass = eval(class_name) rescue :sorry_dude
|
231
|
-
return klass if klass.is_a?(Class)
|
232
|
-
|
233
|
-
## Data type class didn't exist -- create namespace (if necessary),
|
234
|
-
## then the data type class
|
235
|
-
if namespace != ''
|
236
|
-
nsc = eval(namespace) rescue :bzzzzzt
|
237
|
-
unless nsc.is_a?(Class)
|
238
|
-
Object.module_eval "class #{namespace} < #{self}; end"
|
239
|
-
end
|
240
|
-
end
|
241
|
-
Object.module_eval "class #{class_name} < #{namespace}; end"
|
242
|
-
eval(class_name)
|
243
|
-
end
|
244
|
-
|
245
|
-
def _hr_response_class # @private
|
246
|
-
self.namespace ||= self.class.to_s unless self.class.to_s=='HyperResource'
|
247
|
-
self.class.response_class(self.response, self.namespace)
|
248
|
-
end
|
249
|
-
|
250
|
-
|
251
|
-
## Inspects the given Faraday::Response, and returns a string describing
|
252
|
-
## this resource's data type.
|
253
|
-
##
|
254
|
-
## By default, this method looks for a +type=...+ modifier in the
|
255
|
-
## response's +Content-type+ and returns that value, capitalized.
|
256
|
-
##
|
257
|
-
## Override this method in a subclass to alter HyperResource's behavior.
|
258
|
-
def self.get_data_type_from_response(response)
|
259
|
-
return nil unless response
|
260
|
-
return nil unless content_type = response['content-type']
|
261
|
-
return nil unless m=content_type.match(/;\s* type=([0-9A-Za-z:]+)/x)
|
262
|
-
m[1][0,1].upcase + m[1][1..-1]
|
263
|
-
end
|
264
|
-
|
265
|
-
## Uses +HyperResource.get_response_data_type+ to determine the proper
|
266
|
-
## data type for this object. Override to change behavior (though you
|
267
|
-
## probably just want to override the class method).
|
268
|
-
def get_data_type_from_response
|
269
|
-
self.class.get_data_type_from_response(self.response)
|
232
|
+
def self.user_agent # @private
|
233
|
+
"HyperResource #{HyperResource::VERSION}"
|
270
234
|
end
|
271
235
|
|
272
|
-
private
|
273
|
-
|
274
|
-
## Return this object, "cast" into its proper response class.
|
275
|
-
def to_response_class
|
276
|
-
response_class = self._hr_response_class
|
277
|
-
return self if self.class == response_class
|
278
|
-
response_class.new(self)
|
236
|
+
def user_agent # @private
|
237
|
+
self.class.user_agent
|
279
238
|
end
|
280
239
|
|
281
|
-
## Use the given resource's data to initialize this one.
|
282
|
-
def init_from_resource(resource)
|
283
|
-
(self.class._hr_attributes - [:attributes, :links, :objects]).each do |attr|
|
284
|
-
self.send("#{attr}=".to_sym, resource.send(attr))
|
285
|
-
end
|
286
|
-
self.adapter.apply(self.body, self)
|
287
|
-
end
|
288
|
-
|
289
|
-
|
290
|
-
## Show a deprecation message.
|
291
|
-
def self._hr_deprecate(message) # @private
|
292
|
-
STDERR.puts "#{message} (called from #{caller[2]})"
|
293
|
-
end
|
294
|
-
|
295
|
-
def _hr_deprecate(*args) # @private
|
296
|
-
self.class._hr_deprecate(*args)
|
297
|
-
end
|
298
240
|
end
|
241
|
+
|
@@ -21,10 +21,10 @@ class HyperResource
|
|
21
21
|
|
22
22
|
def apply(response, resource, opts={})
|
23
23
|
if !response.kind_of?(Hash)
|
24
|
-
raise ArgumentError, "'response' argument must be a Hash"
|
24
|
+
raise ArgumentError, "'response' argument must be a Hash (got #{response.inspect})"
|
25
25
|
end
|
26
26
|
if !resource.kind_of?(HyperResource)
|
27
|
-
raise ArgumentError, "'resource' argument must be a HyperResource"
|
27
|
+
raise ArgumentError, "'resource' argument must be a HyperResource (got #{resource.inspect})"
|
28
28
|
end
|
29
29
|
|
30
30
|
apply_objects(response, resource)
|
@@ -40,54 +40,38 @@ class HyperResource
|
|
40
40
|
|
41
41
|
def apply_objects(resp, rsrc)
|
42
42
|
return unless resp['_embedded']
|
43
|
-
rc = rsrc.class
|
44
|
-
rsrc.objects = rc::Objects.new(rsrc)
|
45
43
|
objs = rsrc.objects
|
46
44
|
|
47
45
|
resp['_embedded'].each do |name, collection|
|
48
46
|
if collection.is_a? Hash
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
objs[name] = apply(collection, r)
|
47
|
+
objs[name] =
|
48
|
+
rsrc.new_from(:resource => rsrc,
|
49
|
+
:body => collection,
|
50
|
+
:href => collection['_links']['self']['href'] )
|
54
51
|
else
|
55
52
|
objs[name] = collection.map do |obj|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
r.body = obj
|
60
|
-
apply(obj, r)
|
53
|
+
rsrc.new_from(:resource => rsrc,
|
54
|
+
:body => obj,
|
55
|
+
:href => obj['_links']['self']['href'] )
|
61
56
|
end
|
62
57
|
end
|
63
58
|
end
|
64
|
-
|
65
|
-
objs._hr_create_methods!
|
66
59
|
end
|
67
60
|
|
68
61
|
|
69
62
|
def apply_links(resp, rsrc)
|
70
63
|
return unless resp['_links']
|
71
|
-
rsrc.links = rsrc._hr_response_class::Links.new(rsrc)
|
72
64
|
links = rsrc.links
|
73
65
|
|
74
66
|
resp['_links'].each do |rel, link_spec|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
end
|
79
|
-
keys.each do |key|
|
80
|
-
if link_spec.is_a? Array
|
81
|
-
links[key] = link_spec.map do |link|
|
82
|
-
new_link_from_spec(rsrc, link)
|
83
|
-
end
|
84
|
-
else
|
85
|
-
links[key] = new_link_from_spec(rsrc, link_spec)
|
67
|
+
if link_spec.is_a? Array
|
68
|
+
links[rel] = link_spec.map do |link|
|
69
|
+
new_link_from_spec(rsrc, link)
|
86
70
|
end
|
71
|
+
else
|
72
|
+
links[rel] = new_link_from_spec(rsrc, link_spec)
|
87
73
|
end
|
88
74
|
end
|
89
|
-
|
90
|
-
links._hr_create_methods!
|
91
75
|
end
|
92
76
|
|
93
77
|
def new_link_from_spec(resource, link_spec)
|
@@ -96,8 +80,6 @@ class HyperResource
|
|
96
80
|
|
97
81
|
|
98
82
|
def apply_attributes(resp, rsrc)
|
99
|
-
rsrc.attributes = rsrc._hr_response_class::Attributes.new(rsrc)
|
100
|
-
|
101
83
|
given_attrs = resp.reject{|k,v| %w(_links _embedded).include?(k)}
|
102
84
|
filtered_attrs = rsrc.incoming_body_filter(given_attrs)
|
103
85
|
|
@@ -106,7 +88,6 @@ class HyperResource
|
|
106
88
|
end
|
107
89
|
|
108
90
|
rsrc.attributes._hr_clear_changed
|
109
|
-
rsrc.attributes._hr_create_methods!
|
110
91
|
end
|
111
92
|
|
112
93
|
end
|
@@ -3,49 +3,10 @@ class HyperResource
|
|
3
3
|
|
4
4
|
attr_accessor :_resource # @private
|
5
5
|
|
6
|
-
def initialize(resource=nil)
|
6
|
+
def initialize(resource=nil)
|
7
7
|
self._resource = resource || HyperResource.new
|
8
8
|
end
|
9
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
10
|
## Returns +true+ if the given attribute has been changed since creation
|
50
11
|
## time, +false+ otherwise.
|
51
12
|
## If no attribute is given, return whether any attributes have been
|
@@ -62,21 +23,24 @@ class HyperResource
|
|
62
23
|
@_hr_changed.select{|k,v| v}.keys.inject({}) {|h,k| h[k]=self[k]; h}
|
63
24
|
end
|
64
25
|
|
65
|
-
|
66
|
-
|
67
|
-
|
26
|
+
# @private
|
27
|
+
def []=(attr, value)
|
28
|
+
return self[attr] if self.has_key?(attr.to_s) && self[attr] == value
|
29
|
+
_hr_mark_changed(attr)
|
68
30
|
super(attr.to_s, value)
|
69
31
|
end
|
70
32
|
|
71
|
-
|
33
|
+
# @private
|
34
|
+
def [](key)
|
72
35
|
return super(key.to_s) if self.has_key?(key.to_s)
|
73
36
|
return super(key.to_sym) if self.has_key?(key.to_sym)
|
74
37
|
nil
|
75
38
|
end
|
76
39
|
|
77
|
-
|
40
|
+
# @private
|
41
|
+
def method_missing(method, *args)
|
78
42
|
method = method.to_s
|
79
|
-
if self
|
43
|
+
if self.has_key?(method)
|
80
44
|
self[method]
|
81
45
|
elsif method[-1,1] == '='
|
82
46
|
self[method[0..-2]] = args.first
|
@@ -85,11 +49,21 @@ class HyperResource
|
|
85
49
|
end
|
86
50
|
end
|
87
51
|
|
52
|
+
# @private
|
53
|
+
def respond_to?(method, *args)
|
54
|
+
method = method.to_s
|
55
|
+
return true if self.has_key?(method)
|
56
|
+
return true if method[-1,1] == '=' && self.has_key?(method[0..-2])
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
88
61
|
def _hr_clear_changed # @private
|
89
62
|
@_hr_changed = nil
|
90
63
|
end
|
91
64
|
|
92
|
-
|
65
|
+
# @private
|
66
|
+
def _hr_mark_changed(attr, is_changed=true)
|
93
67
|
attr = attr.to_sym
|
94
68
|
@_hr_changed ||= Hash.new(false)
|
95
69
|
@_hr_changed[attr] = is_changed
|