aptible-resource 0.1.0

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