hyperresource 0.1.3 → 0.1.9
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 +170 -64
- data/lib/hyper_resource/adapter.rb +26 -0
- data/lib/hyper_resource/adapter/hal_json.rb +97 -0
- data/lib/hyper_resource/attributes.rb +76 -18
- data/lib/hyper_resource/exceptions.rb +5 -2
- data/lib/hyper_resource/link.rb +4 -1
- data/lib/hyper_resource/links.rb +36 -18
- data/lib/hyper_resource/modules/http.rb +6 -4
- data/lib/hyper_resource/modules/utils.rb +16 -0
- data/lib/hyper_resource/objects.rb +52 -26
- data/lib/hyper_resource/version.rb +2 -2
- metadata +12 -10
- data/lib/hyper_resource/modules/bless.rb +0 -57
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4befe203aa8c4fc93c4ba6d09c03bcc9d76a2183
|
4
|
+
data.tar.gz: 1deed89d174bd9ac761763a731d69c06b70eb318
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4122f5fb687c63252bb05724cc8477c4ae64f3f72a2340af09e21d070066165591292ad6756c16c27381eb6aff43c6ea294ae88d482f5e8520fc9c4adb4adfef
|
7
|
+
data.tar.gz: e5170d246d1642ce97b5cdb312d47d1a3af99c462ca16116a146af39d1dd54e95e91313226d79853a4d115cf102d1fa55183d8e2a567cf48ae2f66575ae3fa44
|
data/lib/hyper_resource.rb
CHANGED
@@ -8,31 +8,57 @@ require 'hyper_resource/exceptions'
|
|
8
8
|
|
9
9
|
require 'hyper_resource/modules/utils'
|
10
10
|
require 'hyper_resource/modules/http'
|
11
|
-
|
11
|
+
|
12
|
+
require 'hyper_resource/adapter'
|
13
|
+
require 'hyper_resource/adapter/hal_json'
|
12
14
|
|
13
15
|
require 'pp'
|
14
16
|
|
17
|
+
## TODO:
|
18
|
+
## incoming_filter, outgoing_filter
|
19
|
+
## as_json, to_json (in adapter?)
|
20
|
+
## save, update, create, delete
|
21
|
+
|
15
22
|
class HyperResource
|
16
23
|
include HyperResource::Modules::Utils
|
17
24
|
include HyperResource::Modules::HTTP
|
18
|
-
include HyperResource::Modules::Bless
|
19
25
|
|
20
26
|
private
|
21
27
|
|
22
28
|
def self._hr_class_attributes
|
23
|
-
|
29
|
+
[ :root, ## e.g. 'https://example.com/api/v1'
|
30
|
+
:auth, ## e.g. {:basic => ['username', 'password']}
|
31
|
+
:headers, ## e.g. {'Accept' => 'application/vnd.example+json'}
|
32
|
+
:namespace, ## e.g. 'ExampleAPI', or the class ExampleAPI itself
|
33
|
+
:adapter ## subclass of HR::Adapter
|
34
|
+
]
|
24
35
|
end
|
25
36
|
|
26
37
|
def self._hr_attributes
|
27
|
-
|
28
|
-
|
29
|
-
|
38
|
+
[ :root,
|
39
|
+
:href,
|
40
|
+
:auth,
|
41
|
+
:headers,
|
42
|
+
:namespace,
|
43
|
+
:adapter,
|
44
|
+
|
45
|
+
:request,
|
46
|
+
:response,
|
47
|
+
:response_object,
|
48
|
+
|
49
|
+
:attributes,
|
50
|
+
:links,
|
51
|
+
:objects,
|
52
|
+
|
53
|
+
:loaded
|
54
|
+
]
|
30
55
|
end
|
31
56
|
|
32
57
|
public
|
33
58
|
|
34
|
-
_hr_class_attributes.each
|
35
|
-
_hr_attributes.each
|
59
|
+
_hr_class_attributes.each {|attr| class_attribute attr}
|
60
|
+
(_hr_attributes & _hr_class_attributes).each {|attr| fallback_attribute attr}
|
61
|
+
(_hr_attributes - _hr_class_attributes).each {|attr| attr_accessor attr}
|
36
62
|
|
37
63
|
# :nodoc:
|
38
64
|
DEFAULT_HEADERS = {
|
@@ -53,10 +79,7 @@ public
|
|
53
79
|
## [headers] Headers to send along with requests for this resource (as
|
54
80
|
## well as its eventual child resources, if any).
|
55
81
|
def initialize(opts={})
|
56
|
-
if opts.
|
57
|
-
self.class._hr_attributes.each {|attr| self.send("#{attr}=".to_sym, opts.send(attr))}
|
58
|
-
return
|
59
|
-
end
|
82
|
+
return init_from_resource(opts) if opts.kind_of?(HyperResource)
|
60
83
|
|
61
84
|
self.root = opts[:root] || self.class.root
|
62
85
|
self.href = opts[:href] || ''
|
@@ -65,54 +88,141 @@ public
|
|
65
88
|
self.headers = DEFAULT_HEADERS.merge(self.class.headers || {}).
|
66
89
|
merge(opts[:headers] || {})
|
67
90
|
|
68
|
-
|
69
|
-
|
70
|
-
self.
|
91
|
+
## There's a little acrobatics in getting Attributes, Links, and Objects
|
92
|
+
## into the correct subclass.
|
93
|
+
if self.class != HyperResource
|
94
|
+
if self.class::Attributes == HyperResource::Attributes
|
95
|
+
Object.module_eval(
|
96
|
+
"class #{self.class}::Attributes < HyperResource::Attributes; end"
|
97
|
+
)
|
98
|
+
end
|
99
|
+
if self.class::Links == HyperResource::Links
|
100
|
+
Object.module_eval(
|
101
|
+
"class #{self.class}::Links < HyperResource::Links; end"
|
102
|
+
)
|
103
|
+
end
|
104
|
+
if self.class::Objects == HyperResource::Objects
|
105
|
+
Object.module_eval(
|
106
|
+
"class #{self.class}::Objects < HyperResource::Objects; end"
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
self.attributes = self.class::Attributes.new(self)
|
112
|
+
self.links = self.class::Links.new(self)
|
113
|
+
self.objects = self.class::Objects.new(self)
|
114
|
+
|
71
115
|
self.loaded = false
|
116
|
+
|
117
|
+
self.adapter = opts[:adapter] || self.class.adapter ||
|
118
|
+
HyperResource::Adapter::HAL_JSON
|
72
119
|
end
|
73
120
|
|
74
|
-
|
75
|
-
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
new_rsrc.send("#{attr}=".to_sym, rsrc.send(attr))
|
121
|
+
private
|
122
|
+
|
123
|
+
def init_from_resource(resource)
|
124
|
+
(self.class._hr_attributes - [:attributes, :links, :objects]).each do |attr|
|
125
|
+
self.send("#{attr}=".to_sym, resource.send(attr))
|
80
126
|
end
|
81
|
-
|
127
|
+
self.adapter.apply(self.response_object, self)
|
82
128
|
end
|
83
129
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
:headers => self.headers,
|
89
|
-
:namespace => self.namespace,
|
90
|
-
:href => obj['_links']['self']['href'])
|
91
|
-
rsrc.response_body = Response[obj]
|
92
|
-
rsrc.init_from_response_body!
|
93
|
-
rsrc
|
130
|
+
public
|
131
|
+
|
132
|
+
def changed?(*args)
|
133
|
+
attributes.changed?(*args)
|
94
134
|
end
|
95
135
|
|
96
136
|
## Returns a new HyperResource based on the given link href.
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
137
|
+
def _new_from_link(href)
|
138
|
+
self.class.new(:root => self.root,
|
139
|
+
:auth => self.auth,
|
140
|
+
:headers => self.headers,
|
141
|
+
:namespace => self.namespace,
|
142
|
+
:href => href)
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_response_class
|
146
|
+
response_class = self.get_response_class
|
147
|
+
return self if self.class == response_class
|
148
|
+
response_class.new(self)
|
149
|
+
end
|
150
|
+
|
151
|
+
def incoming_filter(attr_hash)
|
152
|
+
attr_hash
|
153
|
+
end
|
154
|
+
|
155
|
+
def outgoing_filter(attr_hash)
|
156
|
+
attr_hash
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_response_class
|
160
|
+
self.namespace ||= self.class.to_s unless self.class.to_s=='HyperResource'
|
161
|
+
self.class.get_response_class(self.response, self.namespace)
|
162
|
+
end
|
163
|
+
|
164
|
+
## Returns the class into which the given response should be cast.
|
165
|
+
## If the object is not loaded yet, or if +opts[:namespace]+ is
|
166
|
+
## not set, returns +self+.
|
167
|
+
##
|
168
|
+
## Otherwise, +get_response_class+ uses +get_response_data_type+ to
|
169
|
+
## determine subclass name, glues it to the given namespace, and
|
170
|
+
## creates the class if it's not there yet. E.g., given a namespace of
|
171
|
+
## +FooAPI+ and a response content-type of
|
172
|
+
## "application/vnd.foocorp.fooapi.v1+json;type=User", this should
|
173
|
+
## return +FooAPI::User+ (even if +FooAPI::User+ hadn't existed yet).
|
174
|
+
|
175
|
+
def self.get_response_class(response, namespace)
|
176
|
+
if self.to_s == 'HyperResource'
|
177
|
+
return self unless namespace
|
178
|
+
end
|
179
|
+
|
180
|
+
namespace ||= self.to_s
|
181
|
+
|
182
|
+
type_name = self.get_response_data_type(response)
|
183
|
+
return self unless type_name
|
184
|
+
|
185
|
+
class_name = "#{namespace}::#{type_name}"
|
186
|
+
class_name.gsub!(/[^_0-9A-Za-z:]/, '') ## sanitize class_name
|
187
|
+
|
188
|
+
## Return data type class if it exists
|
189
|
+
klass = eval(class_name) rescue :sorry_dude
|
190
|
+
return klass if klass.is_a?(Class)
|
191
|
+
|
192
|
+
## Data type class didn't exist -- create namespace (if necessary),
|
193
|
+
## then the data type class
|
194
|
+
if namespace != ''
|
195
|
+
nsc = eval(namespace) rescue :bzzzzzt
|
196
|
+
unless nsc.is_a?(Class)
|
197
|
+
Object.module_eval "class #{namespace} < #{self}; end"
|
198
|
+
end
|
199
|
+
end
|
200
|
+
Object.module_eval "class #{class_name} < #{namespace}; end"
|
201
|
+
eval(class_name)
|
202
|
+
end
|
203
|
+
|
204
|
+
|
205
|
+
def get_response_data_type
|
206
|
+
self.class.get_response_data_type(self.response)
|
103
207
|
end
|
104
208
|
|
105
|
-
##
|
106
|
-
##
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
209
|
+
## Inspects the given response, and returns a string describing this
|
210
|
+
## resource's data type.
|
211
|
+
##
|
212
|
+
## By default, this method looks for a +type=...+ modifier in the
|
213
|
+
## response's +Content-type+ and returns that value, capitalized.
|
214
|
+
##
|
215
|
+
## Override this method in a subclass to alter HyperResource's behavior.
|
216
|
+
|
217
|
+
def self.get_response_data_type(response)
|
218
|
+
return nil unless response
|
219
|
+
return nil unless content_type = response['content-type']
|
220
|
+
return nil unless m=content_type.match(/;\s* type=(?<type> [0-9A-Za-z:]+)/x)
|
221
|
+
m[:type][0].upcase + m[:type][1..-1]
|
114
222
|
end
|
115
223
|
|
224
|
+
|
225
|
+
|
116
226
|
## Returns the first object in the first collection of objects embedded
|
117
227
|
## in this resource. Equivalent to +self.objects.first+.
|
118
228
|
def first; self.objects.first end
|
@@ -122,27 +232,20 @@ public
|
|
122
232
|
def [](i); self.objects.ith(i) end
|
123
233
|
|
124
234
|
## method_missing will load this resource if not yet loaded, then
|
125
|
-
## attempt to delegate to +attributes+, then +objects+,
|
126
|
-
## then +links+. When it finds a match, it will define a method class-wide
|
127
|
-
## if self.class != HyperResource, instance-wide otherwise.
|
235
|
+
## attempt to delegate to +attributes+, then +objects+, then +links+.
|
128
236
|
def method_missing(method, *args)
|
129
237
|
self.get unless self.loaded
|
130
238
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
self.class.send(:define_method, method) do |*args|
|
139
|
-
self.send(field).send(method, *args)
|
140
|
-
end
|
141
|
-
end
|
142
|
-
return self.send(field).send(method, *args)
|
143
|
-
end
|
239
|
+
method = method.to_s
|
240
|
+
if method[-1] == '='
|
241
|
+
return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
|
242
|
+
else
|
243
|
+
return attributes[method] if attributes && attributes[method]
|
244
|
+
return objects[method] if objects && objects[method]
|
245
|
+
return links[method] if links && links[method]
|
144
246
|
end
|
145
|
-
|
247
|
+
|
248
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
146
249
|
end
|
147
250
|
|
148
251
|
|
@@ -152,4 +255,7 @@ public
|
|
152
255
|
"@namespace=#{self.namespace.inspect} ...>"
|
153
256
|
end
|
154
257
|
|
258
|
+
## +response_body+ is deprecated in favor of +response_object+.
|
259
|
+
def response_body; response_object end
|
260
|
+
|
155
261
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Adapter
|
3
|
+
class << self
|
4
|
+
|
5
|
+
## Serialize the object into a string.
|
6
|
+
def serialize(object)
|
7
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
8
|
+
"of HyperResource::Adapter must implement it."
|
9
|
+
end
|
10
|
+
|
11
|
+
## Deserialize a string into an object.
|
12
|
+
def deserialize(string)
|
13
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
14
|
+
"of HyperResource::Adapter must implement it."
|
15
|
+
end
|
16
|
+
|
17
|
+
## Apply a response object (generally parsed JSON or XML) to the given
|
18
|
+
## HyperResource object. Returns the updated resource.
|
19
|
+
def apply(object, resource, opts={})
|
20
|
+
raise NotImplementedError, "This is an abstract method -- subclasses "+
|
21
|
+
"of HyperResource::Adapter must implement it."
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class HyperResource
|
4
|
+
class Adapter
|
5
|
+
class HAL_JSON < Adapter
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def serialize(object)
|
9
|
+
JSON.dump(object)
|
10
|
+
end
|
11
|
+
|
12
|
+
def deserialize(string)
|
13
|
+
JSON.parse(string)
|
14
|
+
end
|
15
|
+
|
16
|
+
def apply(response, resource, opts={})
|
17
|
+
if !response.kind_of?(Hash)
|
18
|
+
raise ArgumentError, "'response' argument must be a Hash"
|
19
|
+
end
|
20
|
+
if !resource.kind_of?(HyperResource)
|
21
|
+
raise ArgumentError, "'resource' argument must be a HyperResource"
|
22
|
+
end
|
23
|
+
|
24
|
+
apply_objects(response, resource)
|
25
|
+
apply_links(response, resource)
|
26
|
+
apply_attributes(response, resource)
|
27
|
+
resource.loaded = true
|
28
|
+
resource.href = response['_links']['self']['href'] rescue nil
|
29
|
+
resource
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def apply_objects(resp, rsrc)
|
36
|
+
return unless resp['_embedded']
|
37
|
+
rc = rsrc.class
|
38
|
+
rsrc.objects = rc::Objects.new(rsrc)
|
39
|
+
objs = rsrc.objects
|
40
|
+
|
41
|
+
resp['_embedded'].each do |name, collection|
|
42
|
+
if collection.is_a? Hash
|
43
|
+
r = rc.new
|
44
|
+
r.response_object = collection
|
45
|
+
objs[name] = apply(collection, r)
|
46
|
+
else
|
47
|
+
objs[name] = collection.map do |obj|
|
48
|
+
r = rc.new
|
49
|
+
r.response_object = obj
|
50
|
+
apply(obj, r)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
objs.create_methods!
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def apply_links(resp, rsrc)
|
60
|
+
return unless resp['_links']
|
61
|
+
rsrc.links = rsrc.get_response_class::Links.new(rsrc)
|
62
|
+
links = rsrc.links
|
63
|
+
|
64
|
+
resp['_links'].each do |rel, link_spec|
|
65
|
+
if link_spec.is_a? Array
|
66
|
+
links[rel] = link_spec.map do |link|
|
67
|
+
new_link_from_spec(rsrc, link)
|
68
|
+
end
|
69
|
+
else
|
70
|
+
links[rel] = new_link_from_spec(rsrc, link_spec)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
links.create_methods!
|
75
|
+
end
|
76
|
+
|
77
|
+
def new_link_from_spec(resource, link_spec) # :nodoc:
|
78
|
+
resource.class::Link.new(resource, link_spec)
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
def apply_attributes(resp, rsrc)
|
83
|
+
rsrc.attributes = rsrc.get_response_class::Attributes.new(rsrc)
|
84
|
+
attrs = rsrc.attributes
|
85
|
+
|
86
|
+
(resp.keys - ['_links', '_embedded']).map(&:to_s).each do |attr|
|
87
|
+
attrs[attr] = resp[attr]
|
88
|
+
end
|
89
|
+
|
90
|
+
attrs.create_methods!
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -1,27 +1,85 @@
|
|
1
|
-
class HyperResource
|
2
|
-
|
1
|
+
class HyperResource
|
2
|
+
class Attributes < Hash
|
3
3
|
|
4
|
-
|
5
|
-
self.parent_resource = resource || HyperResource.new
|
6
|
-
end
|
4
|
+
attr_accessor :_resource # :nodoc:
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
self[attr] = hal_resp[attr]
|
6
|
+
def initialize(resource=nil) # :nodoc:
|
7
|
+
self._resource = resource || HyperResource.new
|
8
|
+
end
|
12
9
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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 create_methods!(opts={})
|
14
|
+
return if self.class.to_s == 'HyperResource::Attributes' ||
|
15
|
+
self._resource.class.to_s == 'HyperResource'
|
16
|
+
|
17
|
+
self.keys.each do |attr|
|
18
|
+
attr_sym = attr.to_sym
|
19
|
+
attr_eq_sym = "#{attr}=".to_sym
|
20
|
+
|
21
|
+
self.class.send(:define_method, attr_sym) do
|
22
|
+
self[attr]
|
23
|
+
end
|
24
|
+
self.class.send(:define_method, attr_eq_sym) do |val|
|
25
|
+
self[attr] = val
|
26
|
+
end
|
17
27
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
28
|
+
## Don't stomp on _resource's methods
|
29
|
+
unless _resource.respond_to?(attr_sym)
|
30
|
+
_resource.class.send(:define_method, attr_sym) do
|
31
|
+
attributes.send(attr_sym)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
unless _resource.respond_to?(attr_eq_sym)
|
35
|
+
_resource.class.send(:define_method, attr_eq_sym) do |val|
|
36
|
+
attributes.send(attr_eq_sym, val)
|
37
|
+
end
|
23
38
|
end
|
24
39
|
end
|
40
|
+
|
41
|
+
## This is a good time to mark this object as not-changed
|
42
|
+
_hr_clear_changed
|
43
|
+
end
|
44
|
+
|
45
|
+
## Returns +true+ if the given attribute has been changed since creation
|
46
|
+
## time, +false+ otherwise.
|
47
|
+
## If no attribute is given, return whether any attributes have been
|
48
|
+
## changed.
|
49
|
+
def changed?(attr=nil)
|
50
|
+
@_hr_changed ||= Hash.new(false)
|
51
|
+
return @_hr_changed[attr.to_sym] if attr
|
52
|
+
return @_hr_changed.keys.count > 0
|
53
|
+
end
|
54
|
+
|
55
|
+
def []=(attr, value) # :nodoc:
|
56
|
+
_hr_mark_changed(attr)
|
57
|
+
super(attr.to_s, value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def [](key) # :nodoc:
|
61
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
62
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
63
|
+
nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def method_missing(method, *args) # :nodoc:
|
67
|
+
return self[method] if self[method]
|
68
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def _hr_mark_changed(attr, is_changed=true) # :nodoc:
|
74
|
+
attr = attr.to_sym
|
75
|
+
@_hr_changed ||= Hash.new(false)
|
76
|
+
@_hr_changed[attr] = is_changed
|
77
|
+
end
|
78
|
+
|
79
|
+
def _hr_clear_changed
|
80
|
+
@_hr_changed = nil
|
25
81
|
end
|
82
|
+
|
26
83
|
end
|
27
84
|
end
|
85
|
+
|
@@ -1,9 +1,12 @@
|
|
1
1
|
class HyperResource::Exception < Exception
|
2
2
|
end
|
3
3
|
|
4
|
-
class HyperResource::
|
4
|
+
class HyperResource::ResponseError < HyperResource::Exception
|
5
5
|
end
|
6
6
|
|
7
|
-
class HyperResource::
|
7
|
+
class HyperResource::ClientError < HyperResource::ResponseError
|
8
|
+
end
|
9
|
+
|
10
|
+
class HyperResource::ServerError < HyperResource::ResponseError
|
8
11
|
end
|
9
12
|
|
data/lib/hyper_resource/link.rb
CHANGED
@@ -2,6 +2,7 @@ require 'uri_template'
|
|
2
2
|
|
3
3
|
class HyperResource::Link
|
4
4
|
attr_accessor :base_href,
|
5
|
+
:name,
|
5
6
|
:templated,
|
6
7
|
:params,
|
7
8
|
:parent_resource
|
@@ -11,6 +12,7 @@ class HyperResource::Link
|
|
11
12
|
def initialize(resource=nil, link_spec={})
|
12
13
|
self.parent_resource = resource || HyperResource.new
|
13
14
|
self.base_href = link_spec['href']
|
15
|
+
self.name = link_spec['name']
|
14
16
|
self.templated = !!link_spec['templated']
|
15
17
|
self.params = link_spec['params'] || {}
|
16
18
|
end
|
@@ -29,13 +31,14 @@ class HyperResource::Link
|
|
29
31
|
def where(params)
|
30
32
|
self.class.new(self.parent_resource,
|
31
33
|
'href' => self.base_href,
|
34
|
+
'name' => self.name,
|
32
35
|
'templated' => self.templated,
|
33
36
|
'params' => self.params.merge(params))
|
34
37
|
end
|
35
38
|
|
36
39
|
## Returns a HyperResource representing this link
|
37
40
|
def resource
|
38
|
-
parent_resource.
|
41
|
+
parent_resource._new_from_link(self.href)
|
39
42
|
end
|
40
43
|
|
41
44
|
## Returns a HyperResource representing this link, and fetches it.
|
data/lib/hyper_resource/links.rb
CHANGED
@@ -1,35 +1,53 @@
|
|
1
1
|
class HyperResource
|
2
2
|
class Links < Hash
|
3
|
-
attr_accessor :
|
3
|
+
attr_accessor :_resource
|
4
4
|
|
5
5
|
def initialize(resource=nil)
|
6
|
-
self.
|
6
|
+
self._resource = resource || HyperResource.new
|
7
7
|
end
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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 create_methods!(opts={})
|
13
|
+
return if self.class.to_s == 'HyperResource::Links' ||
|
14
|
+
self._resource.class.to_s == 'HyperResource'
|
15
|
+
|
16
|
+
self.keys.each do |attr|
|
17
|
+
attr_sym = attr.to_sym
|
18
|
+
|
19
|
+
self.class.send(:define_method, attr_sym) do |*args|
|
20
|
+
if args.count > 0
|
21
|
+
self[attr].where(*args)
|
22
|
+
else
|
23
|
+
self[attr]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
## Don't stomp on _resource's methods
|
28
|
+
unless _resource.respond_to?(attr_sym)
|
29
|
+
_resource.class.send(:define_method, attr_sym) do |*args|
|
30
|
+
links.send(attr_sym, *args)
|
31
|
+
end
|
32
|
+
end
|
15
33
|
end
|
16
34
|
end
|
17
35
|
|
18
|
-
|
36
|
+
def []=(attr, value) # :nodoc:
|
37
|
+
super(attr.to_s, value)
|
38
|
+
end
|
19
39
|
|
20
|
-
def
|
21
|
-
|
40
|
+
def [](key) # :nodoc:
|
41
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
42
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
43
|
+
nil
|
22
44
|
end
|
23
45
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
27
|
-
return link if args.empty?
|
28
|
-
link.where(*args)
|
29
|
-
end
|
46
|
+
def method_missing(method, *args) # :nodoc:
|
47
|
+
return self[method] if self[method]
|
48
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
30
49
|
end
|
31
50
|
|
32
51
|
end
|
33
52
|
end
|
34
53
|
|
35
|
-
|
@@ -30,11 +30,13 @@ module HyperResource::Modules::HTTP
|
|
30
30
|
private
|
31
31
|
|
32
32
|
def finish_up
|
33
|
+
self.loaded = true
|
34
|
+
self.response_object = self.adapter.deserialize(self.response.body)
|
35
|
+
self.adapter.apply(self.response_object, self)
|
36
|
+
|
33
37
|
status = self.response.status
|
34
38
|
if status / 100 == 2
|
35
|
-
|
36
|
-
self.init_from_response_body!
|
37
|
-
self.blessed
|
39
|
+
return self.to_response_class
|
38
40
|
elsif status / 100 == 3
|
39
41
|
## TODO redirect logic?
|
40
42
|
elsif status / 100 == 4
|
@@ -42,7 +44,7 @@ private
|
|
42
44
|
elsif status / 100 == 5
|
43
45
|
raise HyperResource::ServerError, status.to_s
|
44
46
|
else ## 1xx? really?
|
45
|
-
raise HyperResource::
|
47
|
+
raise HyperResource::ResponseError, "Got status #{status}, wtf?"
|
46
48
|
end
|
47
49
|
end
|
48
50
|
|
@@ -23,6 +23,22 @@ module HyperResource::Modules
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
## Instance attributes which fall back to class attributes.
|
27
|
+
def fallback_attribute(*names)
|
28
|
+
names.map(&:to_sym).each do |name|
|
29
|
+
class_eval <<-EOT
|
30
|
+
def #{name}=(val)
|
31
|
+
@#{name} = val
|
32
|
+
end
|
33
|
+
def #{name}
|
34
|
+
return @#{name} if defined?(@#{name})
|
35
|
+
return self.class.#{name} if self.class.respond_to?(:#{name})
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
EOT
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
26
42
|
end # module ClassMethods
|
27
43
|
|
28
44
|
end
|
@@ -1,33 +1,59 @@
|
|
1
|
-
class HyperResource
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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 create_methods!(opts={})
|
13
|
+
return if self.class.to_s == 'HyperResource::Objects' ||
|
14
|
+
self._resource.class.to_s == 'HyperResource'
|
15
|
+
|
16
|
+
self.keys.each do |attr|
|
17
|
+
attr_sym = attr.to_sym
|
18
|
+
|
19
|
+
self.class.send(:define_method, attr_sym) do
|
20
|
+
self[attr]
|
21
|
+
end
|
22
|
+
|
23
|
+
## Don't stomp on _resource's methods
|
24
|
+
unless _resource.respond_to?(attr_sym)
|
25
|
+
_resource.class.send(:define_method, attr_sym) do
|
26
|
+
objects.send(attr_sym)
|
27
|
+
end
|
18
28
|
end
|
19
29
|
end
|
20
30
|
end
|
21
|
-
end
|
22
31
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
32
|
+
## Returns the first item in the first collection in +self+.
|
33
|
+
alias_method :first_orig, :first
|
34
|
+
def first
|
35
|
+
self.first_orig[1][0] rescue caller
|
36
|
+
end
|
37
|
+
|
38
|
+
## Returns the ith item in the first collection in +self+.
|
39
|
+
def ith(i)
|
40
|
+
self.first_orig[1][i] rescue caller
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(attr, value) # :nodoc:
|
44
|
+
super(attr.to_s, value)
|
45
|
+
end
|
46
|
+
|
47
|
+
def [](key) # :nodoc:
|
48
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
49
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
50
|
+
nil
|
51
|
+
end
|
52
|
+
|
53
|
+
def method_missing(method, *args) # :nodoc:
|
54
|
+
return self[method] if self[method]
|
55
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
56
|
+
end
|
28
57
|
|
29
|
-
## Returns the ith item in the first collection in +self+.
|
30
|
-
def ith(i)
|
31
|
-
self.first_orig[1][i] rescue caller
|
32
58
|
end
|
33
59
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hyperresource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pete Gamache
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: uri_template
|
@@ -90,18 +90,19 @@ executables: []
|
|
90
90
|
extensions: []
|
91
91
|
extra_rdoc_files: []
|
92
92
|
files:
|
93
|
-
- lib/
|
94
|
-
- lib/hyper_resource.rb
|
93
|
+
- lib/hyper_resource/adapter/hal_json.rb
|
94
|
+
- lib/hyper_resource/adapter.rb
|
95
|
+
- lib/hyper_resource/attributes.rb
|
96
|
+
- lib/hyper_resource/exceptions.rb
|
95
97
|
- lib/hyper_resource/link.rb
|
96
98
|
- lib/hyper_resource/links.rb
|
97
|
-
- lib/hyper_resource/exceptions.rb
|
98
|
-
- lib/hyper_resource/version.rb
|
99
|
-
- lib/hyper_resource/objects.rb
|
100
|
-
- lib/hyper_resource/attributes.rb
|
101
|
-
- lib/hyper_resource/modules/utils.rb
|
102
99
|
- lib/hyper_resource/modules/http.rb
|
103
|
-
- lib/hyper_resource/modules/
|
100
|
+
- lib/hyper_resource/modules/utils.rb
|
101
|
+
- lib/hyper_resource/objects.rb
|
104
102
|
- lib/hyper_resource/response.rb
|
103
|
+
- lib/hyper_resource/version.rb
|
104
|
+
- lib/hyper_resource.rb
|
105
|
+
- lib/hyperresource.rb
|
105
106
|
homepage: https://github.com/gamache/hyperresource
|
106
107
|
licenses:
|
107
108
|
- MIT
|
@@ -127,3 +128,4 @@ signing_key:
|
|
127
128
|
specification_version: 4
|
128
129
|
summary: Extensible hypermedia client for Ruby
|
129
130
|
test_files: []
|
131
|
+
has_rdoc: true
|
@@ -1,57 +0,0 @@
|
|
1
|
-
module HyperResource::Modules::Bless
|
2
|
-
|
3
|
-
## Returns this resource as an instance of +self.resource_class+.
|
4
|
-
## The returned object will share structure with the source object;
|
5
|
-
## beware.
|
6
|
-
def blessed
|
7
|
-
return self unless self.namespace
|
8
|
-
self.resource_class.new(self)
|
9
|
-
end
|
10
|
-
|
11
|
-
## Returns the class into which this resource should be cast.
|
12
|
-
## If the object is not loaded yet, or if +self.namespace+ is
|
13
|
-
## not set, returns +self.class+.
|
14
|
-
##
|
15
|
-
## Otherwise, +resource_class+ looks at the returned content-type, and
|
16
|
-
## attempts to match a 'type=...' modifier. Given a namespace of
|
17
|
-
## +FooAPI+ and a response content-type of
|
18
|
-
## "application/vnd.foocorp.fooapi.v1+json;type=User", this should
|
19
|
-
## return +FooAPI::User+ (even if +FooAPI::User+ hadn't existed yet).
|
20
|
-
def resource_class
|
21
|
-
return self.class unless self.namespace
|
22
|
-
return self.class unless type_name = self.data_type_name
|
23
|
-
class_name = "#{self.namespace}::#{type_name}".
|
24
|
-
gsub(/[^_0-9A-Za-z:]/, '')
|
25
|
-
|
26
|
-
## Return data type class if it exists
|
27
|
-
klass = eval(class_name) rescue :sorry_dude
|
28
|
-
return klass if klass.is_a?(Class)
|
29
|
-
|
30
|
-
## Data type class didn't exist -- create namespace (if necessary),
|
31
|
-
## then the data type class
|
32
|
-
if self.namespace != ''
|
33
|
-
nsc = eval(self.namespace) rescue :bzzzzzt
|
34
|
-
unless nsc.is_a?(Class)
|
35
|
-
Object.module_eval "class #{self.namespace} < #{self.class}; end"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
Object.module_eval "class #{class_name} < #{self.namespace}; end"
|
39
|
-
eval(class_name)
|
40
|
-
end
|
41
|
-
|
42
|
-
## Inspects the response, and returns a string describing this
|
43
|
-
## resource's data type.
|
44
|
-
##
|
45
|
-
## By default, this method looks for a +type=...+ modifier in the
|
46
|
-
## response's +Content-type+. Override this method in a
|
47
|
-
## HyperResource subclass in order to implement different data type
|
48
|
-
## detection.
|
49
|
-
def data_type_name
|
50
|
-
return nil unless self.response
|
51
|
-
return nil unless content_type = self.response['content-type']
|
52
|
-
return nil unless m=content_type.match(/;\s* type=(?<type> [0-9A-Za-z:]+)/x)
|
53
|
-
m[:type][0].upcase + m[:type][1..-1]
|
54
|
-
end
|
55
|
-
|
56
|
-
|
57
|
-
end
|