hyperresource_zuhrig 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/hyper_resource.rb +241 -0
- data/lib/hyper_resource/adapter.rb +31 -0
- data/lib/hyper_resource/adapter/hal_json.rb +92 -0
- data/lib/hyper_resource/attributes.rb +74 -0
- data/lib/hyper_resource/configuration.rb +159 -0
- data/lib/hyper_resource/exceptions.rb +42 -0
- data/lib/hyper_resource/link.rb +100 -0
- data/lib/hyper_resource/links.rb +69 -0
- 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 +240 -0
- data/lib/hyper_resource/modules/internal_attributes.rb +79 -0
- data/lib/hyper_resource/objects.rb +41 -0
- data/lib/hyper_resource/version.rb +5 -0
- data/lib/hyperresource.rb +2 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 04783c914f04b3bd5312d7a58a7f59d9b171fb1c
|
4
|
+
data.tar.gz: 0efdff138134a4e51ab8b75b01a7cdadf730bddb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c069c3a59156831c56987c5075d3bac00ecb0bb7e135d47dc2749f27fec1469226eb7b65346ce1561ed20c4f598535aeda7a8c15bd16137d41eee20098f804fc
|
7
|
+
data.tar.gz: b06dd416f2e835e7f53d921b2e644a90ea589ecec87aab44d4ef5dcd35f17b8ba9a1e0097f13f0b9a7cd58754067b62d69560d1a6d65141f27a8cca61bb16b36
|
@@ -0,0 +1,241 @@
|
|
1
|
+
require 'hyper_resource/attributes'
|
2
|
+
require 'hyper_resource/configuration'
|
3
|
+
require 'hyper_resource/exceptions'
|
4
|
+
require 'hyper_resource/link'
|
5
|
+
require 'hyper_resource/links'
|
6
|
+
require 'hyper_resource/objects'
|
7
|
+
require 'hyper_resource/version'
|
8
|
+
|
9
|
+
require 'hyper_resource/adapter'
|
10
|
+
require 'hyper_resource/adapter/hal_json'
|
11
|
+
|
12
|
+
require 'hyper_resource/modules/data_type'
|
13
|
+
require 'hyper_resource/modules/deprecations'
|
14
|
+
require 'hyper_resource/modules/http'
|
15
|
+
require 'hyper_resource/modules/config_attributes'
|
16
|
+
require 'hyper_resource/modules/internal_attributes'
|
17
|
+
|
18
|
+
require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
|
19
|
+
|
20
|
+
require 'pp'
|
21
|
+
|
22
|
+
## HyperResource is the main resource base class. Normally it will be used
|
23
|
+
## through subclassing, though it may also be used directly.
|
24
|
+
|
25
|
+
class HyperResource
|
26
|
+
|
27
|
+
include HyperResource::Modules::ConfigAttributes
|
28
|
+
include HyperResource::Modules::DataType
|
29
|
+
include HyperResource::Modules::Deprecations
|
30
|
+
include HyperResource::Modules::InternalAttributes
|
31
|
+
include Enumerable
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
DEFAULT_HEADERS = { 'Accept' => 'application/json' }
|
36
|
+
|
37
|
+
public
|
38
|
+
|
39
|
+
## Create a new HyperResource, given a hash of options. These options
|
40
|
+
## include:
|
41
|
+
##
|
42
|
+
## [root] The root URL of the resource.
|
43
|
+
##
|
44
|
+
## [auth] Authentication information. Currently only +{basic:
|
45
|
+
## ['key', 'secret']}+ is supported.
|
46
|
+
##
|
47
|
+
## [namespace] Class or class name, into which resources should be
|
48
|
+
## instantiated.
|
49
|
+
##
|
50
|
+
## [headers] Headers to send along with requests for this resource (as
|
51
|
+
## well as its eventual child resources, if any).
|
52
|
+
##
|
53
|
+
## [faraday_options] Configuration passed to +Faraday::Connection.initialize+,
|
54
|
+
## such as +{request: {timeout: 30}}+.
|
55
|
+
##
|
56
|
+
def initialize(opts={})
|
57
|
+
self.root = opts[:root] if opts[:root]
|
58
|
+
self.href = opts[:href] if opts[:href]
|
59
|
+
|
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
|
74
|
+
|
75
|
+
## There's a little acrobatics in getting Attributes, Links, and Objects
|
76
|
+
## into the correct subclass.
|
77
|
+
if self.class != HyperResource
|
78
|
+
if self.class::Attributes == HyperResource::Attributes
|
79
|
+
Object.module_eval(
|
80
|
+
"class #{self.class}::Attributes < HyperResource::Attributes; end"
|
81
|
+
)
|
82
|
+
end
|
83
|
+
if self.class::Links == HyperResource::Links
|
84
|
+
Object.module_eval(
|
85
|
+
"class #{self.class}::Links < HyperResource::Links; end"
|
86
|
+
)
|
87
|
+
end
|
88
|
+
if self.class::Objects == HyperResource::Objects
|
89
|
+
Object.module_eval(
|
90
|
+
"class #{self.class}::Objects < HyperResource::Objects; end"
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
self.attributes = self.class::Attributes.new(self)
|
96
|
+
self.links = self.class::Links.new(self)
|
97
|
+
self.objects = self.class::Objects.new(self)
|
98
|
+
self.loaded = false
|
99
|
+
end
|
100
|
+
|
101
|
+
|
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)
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
## Returns true if one or more of this object's attributes has been
|
137
|
+
## reassigned.
|
138
|
+
def changed?(*args)
|
139
|
+
attributes.changed?(*args)
|
140
|
+
end
|
141
|
+
|
142
|
+
|
143
|
+
#### Filters
|
144
|
+
|
145
|
+
## +incoming_body_filter+ filters a hash of attribute keys and values
|
146
|
+
## on their way from a response body to a HyperResource. Override this
|
147
|
+
## in a subclass of HyperResource to implement filters on incoming data.
|
148
|
+
def incoming_body_filter(attr_hash)
|
149
|
+
attr_hash
|
150
|
+
end
|
151
|
+
|
152
|
+
## +outgoing_body_filter+ filters a hash of attribute keys and values
|
153
|
+
## on their way from a HyperResource to a request body. Override this
|
154
|
+
## in a subclass of HyperResource to implement filters on outgoing data.
|
155
|
+
def outgoing_body_filter(attr_hash)
|
156
|
+
attr_hash
|
157
|
+
end
|
158
|
+
|
159
|
+
## +outgoing_uri_filter+ filters a hash of attribute keys and values
|
160
|
+
## on their way from a HyperResource to a URL. Override this
|
161
|
+
## in a subclass of HyperResource to implement filters on outgoing URI
|
162
|
+
## parameters.
|
163
|
+
def outgoing_uri_filter(attr_hash)
|
164
|
+
attr_hash
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
#### Enumerable support
|
169
|
+
|
170
|
+
## Returns the *i*th object in the first collection of objects embedded
|
171
|
+
## in this resource. Returns nil on failure.
|
172
|
+
def [](i)
|
173
|
+
get unless loaded
|
174
|
+
self.objects.first[1][i] rescue nil
|
175
|
+
end
|
176
|
+
|
177
|
+
## Iterates over the objects in the first collection of embedded objects
|
178
|
+
## in this resource.
|
179
|
+
def each(&block)
|
180
|
+
get unless loaded
|
181
|
+
self.objects.first[1].each(&block) rescue nil
|
182
|
+
end
|
183
|
+
|
184
|
+
#### Magic
|
185
|
+
|
186
|
+
## method_missing will load this resource if not yet loaded, then
|
187
|
+
## attempt to delegate to +attributes+, then +objects+, then +links+.
|
188
|
+
## Override with extreme care.
|
189
|
+
def method_missing(method, *args)
|
190
|
+
## If not loaded, load and retry.
|
191
|
+
unless loaded
|
192
|
+
return self.get.send(method, *args)
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
## Otherwise, try to match against attributes, then objects, then links.
|
197
|
+
method = method.to_s
|
198
|
+
if method[-1,1] == '='
|
199
|
+
return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
|
200
|
+
else
|
201
|
+
return attributes[method] if attributes && attributes.has_key?(method.to_s)
|
202
|
+
return objects[method] if objects && objects[method]
|
203
|
+
if links && links[method]
|
204
|
+
if args.count > 0
|
205
|
+
return links[method].where(*args)
|
206
|
+
else
|
207
|
+
return links[method]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
213
|
+
end
|
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
|
+
|
225
|
+
|
226
|
+
def inspect # @private
|
227
|
+
"#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
|
228
|
+
"@href=#{self.href.inspect} @loaded=#{self.loaded} "+
|
229
|
+
"@namespace=#{self.namespace.inspect} ...>"
|
230
|
+
end
|
231
|
+
|
232
|
+
def self.user_agent # @private
|
233
|
+
"HyperResource #{HyperResource::VERSION}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def user_agent # @private
|
237
|
+
self.class.user_agent
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
@@ -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,92 @@
|
|
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 (got #{response.inspect})"
|
25
|
+
end
|
26
|
+
if !resource.kind_of?(HyperResource)
|
27
|
+
raise ArgumentError, "'resource' argument must be a HyperResource (got #{resource.inspect})"
|
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
|
+
objs = rsrc.objects
|
44
|
+
|
45
|
+
resp['_embedded'].each do |name, collection|
|
46
|
+
if collection.is_a? Hash
|
47
|
+
objs[name] =
|
48
|
+
rsrc.new_from(:resource => rsrc,
|
49
|
+
:body => collection,
|
50
|
+
:href => collection['_links']['self']['href'] )
|
51
|
+
else
|
52
|
+
objs[name] = collection.map do |obj|
|
53
|
+
rsrc.new_from(:resource => rsrc,
|
54
|
+
:body => obj,
|
55
|
+
:href => obj['_links']['self']['href'] )
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def apply_links(resp, rsrc)
|
63
|
+
return unless resp['links']
|
64
|
+
links = rsrc.links
|
65
|
+
|
66
|
+
resp['links'].each do | link |
|
67
|
+
rel = link['rel']
|
68
|
+
links[rel] = new_link_from_spec(rsrc, link)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def new_link_from_spec(resource, link_spec)
|
73
|
+
resource.class::Link.new(resource, link_spec)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def apply_attributes(resp, rsrc)
|
78
|
+
given_attrs = resp.reject{|k,v| %w(_links _embedded).include?(k)}
|
79
|
+
filtered_attrs = rsrc.incoming_body_filter(given_attrs)
|
80
|
+
|
81
|
+
filtered_attrs.keys.each do |attr|
|
82
|
+
rsrc.attributes[attr] = filtered_attrs[attr]
|
83
|
+
end
|
84
|
+
|
85
|
+
rsrc.attributes._hr_clear_changed
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class HyperResource
|
2
|
+
class Attributes < Hash
|
3
|
+
|
4
|
+
attr_accessor :_resource # @private
|
5
|
+
|
6
|
+
def initialize(resource=nil)
|
7
|
+
self._resource = resource || HyperResource.new
|
8
|
+
end
|
9
|
+
|
10
|
+
## Returns +true+ if the given attribute has been changed since creation
|
11
|
+
## time, +false+ otherwise.
|
12
|
+
## If no attribute is given, return whether any attributes have been
|
13
|
+
## changed.
|
14
|
+
def changed?(attr=nil)
|
15
|
+
@_hr_changed ||= Hash.new(false)
|
16
|
+
return @_hr_changed[attr.to_sym] if attr
|
17
|
+
return @_hr_changed.keys.count > 0
|
18
|
+
end
|
19
|
+
|
20
|
+
## Returns a hash of the attributes and values which have been changed
|
21
|
+
## since creation time.
|
22
|
+
def changed_attributes
|
23
|
+
@_hr_changed.select{|k,v| v}.keys.inject({}) {|h,k| h[k]=self[k]; h}
|
24
|
+
end
|
25
|
+
|
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)
|
30
|
+
super(attr.to_s, value)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def [](key)
|
35
|
+
return super(key.to_s) if self.has_key?(key.to_s)
|
36
|
+
return super(key.to_sym) if self.has_key?(key.to_sym)
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# @private
|
41
|
+
def method_missing(method, *args)
|
42
|
+
method = method.to_s
|
43
|
+
if self.has_key?(method)
|
44
|
+
self[method]
|
45
|
+
elsif method[-1,1] == '='
|
46
|
+
self[method[0..-2]] = args.first
|
47
|
+
else
|
48
|
+
raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
|
49
|
+
end
|
50
|
+
end
|
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
|
61
|
+
def _hr_clear_changed # @private
|
62
|
+
@_hr_changed = nil
|
63
|
+
end
|
64
|
+
|
65
|
+
# @private
|
66
|
+
def _hr_mark_changed(attr, is_changed=true)
|
67
|
+
attr = attr.to_sym
|
68
|
+
@_hr_changed ||= Hash.new(false)
|
69
|
+
@_hr_changed[attr] = is_changed
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|