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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1a85c8ec634a2f03ac5ba0ddcf4fe6e5419bcbb7
4
- data.tar.gz: e926a07c7d8c6fcb3bae90989edbab9348e1d48b
3
+ metadata.gz: 524a169b824f7c61e5aa5c05109415206095ad99
4
+ data.tar.gz: 70538dc63539ed231dfb71d5cedaccedaf081bda
5
5
  SHA512:
6
- metadata.gz: a7a6206818559b46e1ef0867672df8d9c520344820b2dd588c2d4de3b983bdaac773de0b67b49e6f1e03b602ee36a19022e3528c63ff154c1ee7fe9f0f539ed5
7
- data.tar.gz: 25e4b3d62e9e26ff1087d453e09dad49cd2f04af7df294d488ddf9e7baa3c325ac6b1b0a132ad5916ba376846cfcfd80a321be231c9a42d70b8244285214cb15
6
+ metadata.gz: 875712e54fc2b58369277dc07d4b2418b8726aeef5dac678584ba0cbc5d617ab6a626d57365381ce3dd3ec3924de0b714060a6f6472e61396258c4cb40439fd8
7
+ data.tar.gz: 97a73477ec2d1f378b0ae5904f0c09228dd581b7f8d21289e07311c012f3b8ee2ee2f3fef08a04d15e7ba9d2af4b09ab1d19b5f489e1c7e17912dc51d6abb124
@@ -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::HTTP
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
- return init_from_resource(opts) if opts.kind_of?(HyperResource)
57
+ self.root = opts[:root] if opts[:root]
58
+ self.href = opts[:href] if opts[:href]
53
59
 
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 || {}
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
- self.adapter = opts[:adapter] || self.class.adapter ||
90
- HyperResource::Adapter::HAL_JSON
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
- self.get unless self.loaded
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[method]
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
- ## +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
- 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
- r = rc.new(:root => rsrc.root,
50
- :headers => rsrc.headers,
51
- :namespace => rsrc.namespace)
52
- r.body = collection
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
- r = rc.new(:root => rsrc.root,
57
- :headers => rsrc.headers,
58
- :namespace => rsrc.namespace)
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
- keys = [rel]
76
- if m=rel.match(/.+:(.+)/)
77
- keys << m[1]
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) # @private
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
- def []=(attr, value) # @private
66
- return self[attr] if self[attr] == value
67
- _hr_mark_changed(attr)
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
- def [](key) # @private
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
- def method_missing(method, *args) # @private
40
+ # @private
41
+ def method_missing(method, *args)
78
42
  method = method.to_s
79
- if self[method]
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
- def _hr_mark_changed(attr, is_changed=true) # @private
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