aptible-resource 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 953b5ea9c2b350a3b37e7bc3b1125fd1962e7628
4
+ data.tar.gz: 01317c3f36bd2061be1a95e95b1995f1c0d096d9
5
+ SHA512:
6
+ metadata.gz: 7015d0822a8d50f48248610da1f89a894e5228bbd8a5618b84a202ccb96170b9af8f866b343bd94c27ea22b9841b63f8ff07078d40343746794869ba9ba7ab68
7
+ data.tar.gz: cf190060c67bb26f41340340de34ec0620c6887e793e14633e49386ae1a7cb7978249dbf2656f79b03076de1519510a6db58359ade13fa1ce929f7c7be0f2aef
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/.rubocop.yml ADDED
@@ -0,0 +1,22 @@
1
+ Documentation:
2
+ Enabled: false
3
+
4
+ Encoding:
5
+ Enabled: false
6
+
7
+ DoubleNegation:
8
+ Enabled: false
9
+
10
+ AllCops:
11
+ Include:
12
+ - !ruby/regexp /\.rb$/
13
+ - !ruby/regexp /\.rake$/
14
+ - !ruby/regexp /\.gemspec$/
15
+ - !ruby/regexp /Rakefile$/
16
+ Exclude:
17
+ # Exclude line length check from autogenerated files
18
+ - !ruby/regexp /\/db\/schema\.rb$/
19
+ - !ruby/regexp /node_modules\//
20
+ - !ruby/regexp /tmp\//
21
+ - !ruby/regexp /vendor\//
22
+ - !ruby/regexp /hyper_resource/
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ rvm:
2
+ - 2.0.0
3
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in aptible-resource.gemspec
4
+ gemspec
data/LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Aptible, Inc.
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # ![](https://raw.github.com/aptible/straptible/master/lib/straptible/rails/templates/public.api/icon-60px.png) Aptible::Resource
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/aptible-resource.png)](https://rubygems.org/gems/aptible-resource)
4
+ [![Build Status](https://travis-ci.org/aptible/aptible-resource.png?branch=master)](https://travis-ci.org/aptible/aptible-resource)
5
+ [![Dependency Status](https://gemnasium.com/aptible/aptible-resource.png)](https://gemnasium.com/aptible/aptible-resource)
6
+
7
+ Foundation classes for Aptible resource server gems.
8
+
9
+ ## Usage
10
+
11
+ To build a new resource server gem on top of `aptible-resource`, create a top-level class for your resource server. For example:
12
+
13
+ ```ruby
14
+ module Example
15
+ module Api
16
+ class Resource < Aptible::Resource::Base
17
+ def namespace
18
+ 'Example::Api'
19
+ end
20
+
21
+ def root_url
22
+ 'https://api.example.com'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ ```
28
+
29
+ Then add the gem to your gemspec:
30
+
31
+ ```ruby
32
+ spec.add_dependency 'aptible-resource'
33
+ ```
34
+
35
+ ## Development
36
+
37
+ This gem depends on a vendored version of [HyperResource](https://github.com/gamache/hyperresource), which can be updated from a local checkout of HyperResource as follows:
38
+
39
+ cp -rp /path/to/hyperresource/lib/hyper_resource* lib/
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork the project.
44
+ 1. Commit your changes, with specs.
45
+ 1. Ensure that your code passes specs (`rake spec`) and meets Aptible's Ruby style guide (`rake rubocop`).
46
+ 1. Create a new pull request on GitHub.
47
+
48
+ ## Copyright and License
49
+
50
+ MIT License, see [LICENSE](LICENSE.md) for details.
51
+
52
+ Copyright (c) 2014 [Aptible](https://www.aptible.com), Frank Macreery, and contributors.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'aptible/tasks'
4
+ Aptible::Tasks.load_tasks
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'English'
6
+ require 'aptible/resource/version'
7
+
8
+ Gem::Specification.new do |spec|
9
+ spec.name = 'aptible-resource'
10
+ spec.version = Aptible::Resource::VERSION
11
+ spec.authors = ['Frank Macreery']
12
+ spec.email = ['frank@macreery.com']
13
+ spec.description = %q(Foundation classes for Aptible resource server gems)
14
+ spec.summary = %q(Foundation classes for Aptible resource server gems)
15
+ spec.homepage = 'https://github.com/aptible/aptible-resource'
16
+ spec.license = 'MIT'
17
+
18
+ spec.files = `git ls-files`.split($RS)
19
+ spec.test_files = spec.files.grep(/^spec\//)
20
+ spec.require_paths = ['lib']
21
+
22
+ # HyperResource dependencies
23
+ spec.add_dependency 'uri_template', '>= 0.5.2'
24
+ spec.add_dependency 'faraday', '>= 0.8.6'
25
+ spec.add_dependency 'json'
26
+
27
+ spec.add_dependency 'fridge'
28
+ spec.add_dependency 'activesupport'
29
+
30
+ spec.add_development_dependency 'bundler', '~> 1.6'
31
+ spec.add_development_dependency 'aptible-tasks'
32
+ spec.add_development_dependency 'rake'
33
+ spec.add_development_dependency 'rspec', '~> 2.0'
34
+ spec.add_development_dependency 'pry'
35
+ end
@@ -0,0 +1,7 @@
1
+ require 'aptible/resource/version'
2
+ require 'aptible/resource/base'
3
+
4
+ module Aptible
5
+ module Resource
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ module Aptible
2
+ module Resource
3
+ class Adapter < HyperResource::Adapter::HAL_JSON
4
+ class << self
5
+ def get_data_type_from_object(object)
6
+ return nil unless object
7
+
8
+ if (type = object['_type'])
9
+ if type.respond_to?(:camelize)
10
+ type.camelize
11
+ else
12
+ type[0].upcase + type[1..-1]
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,70 @@
1
+ require 'fridge'
2
+
3
+ # Require vendored HyperResource
4
+ $LOAD_PATH.unshift File.expand_path('../..', __FILE__)
5
+ require 'hyper_resource'
6
+
7
+ require 'aptible/resource/adapter'
8
+ require 'aptible/resource/model'
9
+
10
+ module Aptible
11
+ module Resource
12
+ class Base < HyperResource
13
+ include Model
14
+
15
+ attr_accessor :token
16
+
17
+ def self.get_data_type_from_response(response)
18
+ return nil unless response && response.body
19
+ adapter.get_data_type_from_object(adapter.deserialize(response.body))
20
+ end
21
+
22
+ def self.adapter
23
+ Aptible::Resource::Adapter
24
+ end
25
+
26
+ def initialize(options = {})
27
+ if options.is_a?(Hash)
28
+ self.token = options[:token]
29
+
30
+ options[:root] ||= root_url
31
+ options[:namespace] ||= namespace
32
+ options[:headers] ||= { 'Content-Type' => 'application/json' }
33
+ options[:headers].merge!(
34
+ 'Authorization' => "Bearer #{bearer_token}"
35
+ ) if options[:token]
36
+ end
37
+
38
+ super(options)
39
+ end
40
+
41
+ def adapter
42
+ self.class.adapter
43
+ end
44
+
45
+ def namespace
46
+ fail 'Resource server namespace must be defined by subclass'
47
+ end
48
+
49
+ def root_url
50
+ fail 'Resource server root URL must be defined by subclass'
51
+ end
52
+
53
+ def find_by_url(url_or_href)
54
+ resource = dup
55
+ resource.href = url_or_href.gsub(/^#{root}/, '')
56
+ resource.get
57
+ end
58
+
59
+ def bearer_token
60
+ case token
61
+ when Aptible::Resource::Base then token.access_token
62
+ when Fridge::AccessToken then token.to_s
63
+ when String then token
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ require 'aptible/resource/token'
@@ -0,0 +1,115 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Aptible
4
+ module Resource
5
+ module Model
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ def collection_href
12
+ "/#{basename}"
13
+ end
14
+
15
+ def basename
16
+ name.split('::').last.downcase.pluralize
17
+ end
18
+
19
+ def all(options = {})
20
+ resource = find_by_url(collection_href, options)
21
+ return [] unless resource
22
+ resource.send(basename).entries
23
+ end
24
+
25
+ def find(id, options = {})
26
+ find_by_url("#{collection_href}/#{id}", options)
27
+ end
28
+
29
+ def find_by_url(url, options = {})
30
+ # REVIEW: Should exception be raised if return type mismatch?
31
+ new(options).find_by_url(url)
32
+ rescue HyperResource::ClientError => e
33
+ if e.response.status == 404
34
+ return nil
35
+ else
36
+ raise e
37
+ end
38
+ end
39
+
40
+ def create(params)
41
+ token = params.delete(:token)
42
+ resource = new(token: token)
43
+ resource.send(basename).create(normalize_params(params))
44
+ end
45
+
46
+ # rubocop:disable PredicateName
47
+ def has_many(relation)
48
+ define_has_many_getter(relation)
49
+ define_has_many_setter(relation)
50
+ end
51
+ # rubocop:enable PredicateName
52
+
53
+ def belongs_to(relation)
54
+ define_method relation do
55
+ get unless loaded
56
+ if (memoized = instance_variable_get("@#{relation}"))
57
+ memoized
58
+ elsif links[relation]
59
+ instance_variable_set("@#{relation}", links[relation].get)
60
+ end
61
+ end
62
+ end
63
+
64
+ # rubocop:disable PredicateName
65
+ def has_one(relation)
66
+ # Better than class << self + alias_method?
67
+ belongs_to(relation)
68
+ end
69
+ # rubocop:enable PredicateName
70
+
71
+ def define_has_many_getter(relation)
72
+ define_method relation do
73
+ get unless loaded
74
+ if (memoized = instance_variable_get("@#{relation}"))
75
+ memoized
76
+ elsif links[relation]
77
+ instance_variable_set("@#{relation}", links[relation].entries)
78
+ end
79
+ end
80
+ end
81
+
82
+ def define_has_many_setter(relation)
83
+ define_method "create_#{relation.to_s.singularize}" do |params = {}|
84
+ get unless loaded
85
+ links[relation].create(self.class.normalize_params(params))
86
+ end
87
+ end
88
+
89
+ def normalize_params(params = {})
90
+ params_array = params.map do |key, value|
91
+ value.is_a?(HyperResource) ? [key, value.href] : [key, value]
92
+ end
93
+ Hash[params_array]
94
+ end
95
+ end
96
+
97
+ def delete
98
+ # HyperResource/Faraday choke on empty response bodies
99
+ super
100
+ rescue HyperResource::ResponseError
101
+ nil
102
+ end
103
+ alias_method :destroy, :delete
104
+
105
+ def update(params)
106
+ super(self.class.normalize_params(params))
107
+ end
108
+
109
+ # NOTE: The following does not update the object in-place
110
+ def reload
111
+ self.class.find_by_url(href, headers: headers)
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,10 @@
1
+ require 'aptible/resource/base'
2
+
3
+ # Skeleton class for token implementations to inherit from
4
+ module Aptible
5
+ module Resource
6
+ class Token < Base
7
+ attr_accessor :access_token
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ module Aptible
2
+ module Resource
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,302 @@
1
+ require 'hyper_resource/attributes'
2
+ require 'hyper_resource/exceptions'
3
+ require 'hyper_resource/link'
4
+ require 'hyper_resource/links'
5
+ require 'hyper_resource/objects'
6
+ require 'hyper_resource/response'
7
+ require 'hyper_resource/version'
8
+
9
+ require 'hyper_resource/adapter'
10
+ require 'hyper_resource/adapter/hal_json'
11
+
12
+ require 'hyper_resource/modules/http'
13
+ require 'hyper_resource/modules/internal_attributes'
14
+
15
+ require 'rubygems' if RUBY_VERSION[0..2] == '1.8'
16
+
17
+ require 'pp'
18
+
19
+ ## HyperResource is the main resource base class. Normally it will be used
20
+ ## through subclassing, though it may also be used directly.
21
+
22
+ class HyperResource
23
+
24
+ include HyperResource::Modules::HTTP
25
+ include HyperResource::Modules::InternalAttributes
26
+ include Enumerable
27
+
28
+ private
29
+
30
+ DEFAULT_HEADERS = { 'Accept' => 'application/json' }
31
+
32
+ public
33
+
34
+ ## Create a new HyperResource, given a hash of options. These options
35
+ ## include:
36
+ ##
37
+ ## [root] The root URL of the resource.
38
+ ##
39
+ ## [auth] Authentication information. Currently only +{basic:
40
+ ## ['key', 'secret']}+ is supported.
41
+ ##
42
+ ## [namespace] Class or class name, into which resources should be
43
+ ## instantiated.
44
+ ##
45
+ ## [headers] Headers to send along with requests for this resource (as
46
+ ## well as its eventual child resources, if any).
47
+ ##
48
+ ## [faraday_options] Configuration passed to +Faraday::Connection.initialize+,
49
+ ## such as +{request: {timeout: 30}}+.
50
+ ##
51
+ def initialize(opts={})
52
+ return init_from_resource(opts) if opts.kind_of?(HyperResource)
53
+
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 || {}
62
+
63
+ ## There's a little acrobatics in getting Attributes, Links, and Objects
64
+ ## into the correct subclass.
65
+ if self.class != HyperResource
66
+ if self.class::Attributes == HyperResource::Attributes
67
+ Object.module_eval(
68
+ "class #{self.class}::Attributes < HyperResource::Attributes; end"
69
+ )
70
+ end
71
+ if self.class::Links == HyperResource::Links
72
+ Object.module_eval(
73
+ "class #{self.class}::Links < HyperResource::Links; end"
74
+ )
75
+ end
76
+ if self.class::Objects == HyperResource::Objects
77
+ Object.module_eval(
78
+ "class #{self.class}::Objects < HyperResource::Objects; end"
79
+ )
80
+ end
81
+ end
82
+
83
+ self.attributes = self.class::Attributes.new(self)
84
+ self.links = self.class::Links.new(self)
85
+ self.objects = self.class::Objects.new(self)
86
+
87
+ self.loaded = false
88
+
89
+ self.adapter = opts[:adapter] || self.class.adapter ||
90
+ HyperResource::Adapter::HAL_JSON
91
+ end
92
+
93
+
94
+ ## Returns true if one or more of this object's attributes has been
95
+ ## reassigned.
96
+ def changed?(*args)
97
+ attributes.changed?(*args)
98
+ end
99
+
100
+
101
+ #### Filters
102
+
103
+ ## +incoming_body_filter+ filters a hash of attribute keys and values
104
+ ## on their way from a response body to a HyperResource. Override this
105
+ ## in a subclass of HyperResource to implement filters on incoming data.
106
+ def incoming_body_filter(attr_hash)
107
+ attr_hash
108
+ end
109
+
110
+ ## +outgoing_body_filter+ filters a hash of attribute keys and values
111
+ ## on their way from a HyperResource to a request body. Override this
112
+ ## in a subclass of HyperResource to implement filters on outgoing data.
113
+ def outgoing_body_filter(attr_hash)
114
+ attr_hash
115
+ end
116
+
117
+ ## +outgoing_uri_filter+ filters a hash of attribute keys and values
118
+ ## on their way from a HyperResource to a URL. Override this
119
+ ## in a subclass of HyperResource to implement filters on outgoing URI
120
+ ## parameters.
121
+ def outgoing_uri_filter(attr_hash)
122
+ attr_hash
123
+ end
124
+
125
+
126
+ #### Enumerable support
127
+
128
+ ## Returns the *i*th object in the first collection of objects embedded
129
+ ## in this resource. Returns nil on failure.
130
+ def [](i)
131
+ get unless loaded
132
+ self.objects.first[1][i] rescue nil
133
+ end
134
+
135
+ ## Iterates over the objects in the first collection of embedded objects
136
+ ## in this resource.
137
+ def each(&block)
138
+ get unless loaded
139
+ self.objects.first[1].each(&block) rescue nil
140
+ end
141
+
142
+ #### Magic
143
+
144
+ ## method_missing will load this resource if not yet loaded, then
145
+ ## attempt to delegate to +attributes+, then +objects+, then +links+.
146
+ ## Override with care.
147
+ def method_missing(method, *args)
148
+ self.get unless self.loaded
149
+
150
+ method = method.to_s
151
+ if method[-1,1] == '='
152
+ return attributes[method[0..-2]] = args.first if attributes[method[0..-2]]
153
+ else
154
+ return attributes[method] if attributes && attributes.has_key?(method)
155
+ return objects[method] if objects && objects[method]
156
+ if links && links[method]
157
+ if args.count > 0
158
+ return links[method].where(*args)
159
+ else
160
+ return links[method]
161
+ end
162
+ end
163
+ end
164
+
165
+ raise NoMethodError, "undefined method `#{method}' for #{self.inspect}"
166
+ end
167
+
168
+
169
+ def inspect # @private
170
+ "#<#{self.class}:0x#{"%x" % self.object_id} @root=#{self.root.inspect} "+
171
+ "@href=#{self.href.inspect} @loaded=#{self.loaded} "+
172
+ "@namespace=#{self.namespace.inspect} ...>"
173
+ end
174
+
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
+ namespaced_class(type_name, namespace)
227
+ end
228
+
229
+ def self.namespaced_class(type_name, namespace)
230
+ class_name = "#{namespace}::#{type_name}"
231
+ class_name.gsub!(/[^_0-9A-Za-z:]/, '') ## sanitize class_name
232
+
233
+ ## Return data type class if it exists
234
+ klass = eval(class_name) rescue :sorry_dude
235
+ return klass if klass.is_a?(Class)
236
+
237
+ ## Data type class didn't exist -- create namespace (if necessary),
238
+ ## then the data type class
239
+ if namespace != ''
240
+ nsc = eval(namespace) rescue :bzzzzzt
241
+ unless nsc.is_a?(Class)
242
+ Object.module_eval "class #{namespace} < #{self}; end"
243
+ end
244
+ end
245
+ Object.module_eval "class #{class_name} < #{namespace}; end"
246
+ eval(class_name)
247
+ end
248
+
249
+ def _hr_response_class # @private
250
+ self.namespace ||= self.class.to_s unless self.class.to_s=='HyperResource'
251
+ self.class.response_class(self.response, self.namespace)
252
+ end
253
+
254
+
255
+ ## Inspects the given Faraday::Response, and returns a string describing
256
+ ## this resource's data type.
257
+ ##
258
+ ## By default, this method looks for a +type=...+ modifier in the
259
+ ## response's +Content-type+ and returns that value, capitalized.
260
+ ##
261
+ ## Override this method in a subclass to alter HyperResource's behavior.
262
+ def self.get_data_type_from_response(response)
263
+ return nil unless response
264
+ return nil unless content_type = response['content-type']
265
+ return nil unless m=content_type.match(/;\s* type=([0-9A-Za-z:]+)/x)
266
+ m[1][0,1].upcase + m[1][1..-1]
267
+ end
268
+
269
+ ## Uses +HyperResource.get_response_data_type+ to determine the proper
270
+ ## data type for this object. Override to change behavior (though you
271
+ ## probably just want to override the class method).
272
+ def get_data_type_from_response
273
+ self.class.get_data_type_from_response(self.response)
274
+ end
275
+
276
+ private
277
+
278
+ ## Return this object, "cast" into its proper response class.
279
+ def to_response_class
280
+ response_class = self._hr_response_class
281
+ return self if self.class == response_class
282
+ response_class.new(self)
283
+ end
284
+
285
+ ## Use the given resource's data to initialize this one.
286
+ def init_from_resource(resource)
287
+ (self.class._hr_attributes - [:attributes, :links, :objects]).each do |attr|
288
+ self.send("#{attr}=".to_sym, resource.send(attr))
289
+ end
290
+ self.adapter.apply(self.body, self)
291
+ end
292
+
293
+
294
+ ## Show a deprecation message.
295
+ def self._hr_deprecate(message) # @private
296
+ STDERR.puts "#{message} (called from #{caller[2]})"
297
+ end
298
+
299
+ def _hr_deprecate(*args) # @private
300
+ self.class._hr_deprecate(*args)
301
+ end
302
+ end