hyperresource 0.2.4 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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