scorpio 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.simplecov +1 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/Rakefile +10 -0
- data/getRest.yml +544 -0
- data/lib/scorpio.rb +65 -0
- data/lib/scorpio/model.rb +415 -0
- data/lib/scorpio/pickle_adapter.rb +53 -0
- data/lib/scorpio/version.rb +3 -0
- data/scorpio.gemspec +40 -0
- metadata +293 -0
data/lib/scorpio.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require "scorpio/version"
|
2
|
+
|
3
|
+
module Scorpio
|
4
|
+
proc { |v| define_singleton_method(:error_classes_by_status) { v } }.call({})
|
5
|
+
class Error < StandardError; end
|
6
|
+
class HTTPError < Error
|
7
|
+
define_singleton_method(:status) { |status| Scorpio.error_classes_by_status[status] = self }
|
8
|
+
attr_accessor :response
|
9
|
+
end
|
10
|
+
class ClientError < HTTPError; end
|
11
|
+
class ServerError < HTTPError; end
|
12
|
+
|
13
|
+
class BadRequest400Error < ClientError; status(400); end
|
14
|
+
class Unauthorized401Error < ClientError; status(401); end
|
15
|
+
class PaymentRequired402Error < ClientError; status(402); end
|
16
|
+
class Forbidden403Error < ClientError; status(403); end
|
17
|
+
class NotFound404Error < ClientError; status(404); end
|
18
|
+
class MethodNotAllowed405Error < ClientError; status(405); end
|
19
|
+
class NotAcceptable406Error < ClientError; status(406); end
|
20
|
+
class ProxyAuthenticationRequired407Error < ClientError; status(407); end
|
21
|
+
class RequestTimeout408Error < ClientError; status(408); end
|
22
|
+
class Conflict409Error < ClientError; status(409); end
|
23
|
+
class Gone410Error < ClientError; status(410); end
|
24
|
+
class LengthRequired411Error < ClientError; status(411); end
|
25
|
+
class PreconditionFailed412Error < ClientError; status(412); end
|
26
|
+
class PayloadTooLarge413Error < ClientError; status(413); end
|
27
|
+
class URITooLong414Error < ClientError; status(414); end
|
28
|
+
class UnsupportedMediaType415Error < ClientError; status(415); end
|
29
|
+
class RangeNotSatisfiable416Error < ClientError; status(416); end
|
30
|
+
class ExpectationFailed417Error < ClientError; status(417); end
|
31
|
+
class ImaTeapot418Error < ClientError; status(418); end
|
32
|
+
class MisdirectedRequest421Error < ClientError; status(421); end
|
33
|
+
class UnprocessableEntity422Error < ClientError; status(422); end
|
34
|
+
class Locked423Error < ClientError; status(423); end
|
35
|
+
class FailedDependency424Error < ClientError; status(424); end
|
36
|
+
class UpgradeRequired426Error < ClientError; status(426); end
|
37
|
+
class PreconditionRequired428Error < ClientError; status(428); end
|
38
|
+
class TooManyRequests429Error < ClientError; status(429); end
|
39
|
+
class RequestHeaderFieldsTooLarge431Error < ClientError; status(431); end
|
40
|
+
class UnavailableForLegalReasons451Error < ClientError; status(451); end
|
41
|
+
|
42
|
+
class InternalServerError500Error < ServerError; status(500); end
|
43
|
+
class NotImplemented501Error < ServerError; status(501); end
|
44
|
+
class BadGateway502Error < ServerError; status(502); end
|
45
|
+
class ServiceUnavailable503Error < ServerError; status(503); end
|
46
|
+
class GatewayTimeout504Error < ServerError; status(504); end
|
47
|
+
class HTTPVersionNotSupported505Error < ServerError; status(505); end
|
48
|
+
class VariantAlsoNegotiates506Error < ServerError; status(506); end
|
49
|
+
class InsufficientStorage507Error < ServerError; status(507); end
|
50
|
+
class LoopDetected508Error < ServerError; status(508); end
|
51
|
+
class NotExtended510Error < ServerError; status(510); end
|
52
|
+
class NetworkAuthenticationRequired511Error < ServerError; status(511); end
|
53
|
+
error_classes_by_status.freeze
|
54
|
+
|
55
|
+
autoload :Model, 'scorpio/model'
|
56
|
+
|
57
|
+
class << self
|
58
|
+
def stringify_symbol_keys(hash)
|
59
|
+
unless hash.is_a?(Hash)
|
60
|
+
raise ArgumentError, "expected argument to be a Hash; got #{hash.class}: #{hash.inspect}"
|
61
|
+
end
|
62
|
+
hash.map { |k,v| {k.is_a?(Symbol) ? k.to_s : k => v} }.inject({}, &:update)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,415 @@
|
|
1
|
+
require 'addressable/template'
|
2
|
+
require 'json-schema'
|
3
|
+
|
4
|
+
module Scorpio
|
5
|
+
# see also Faraday::Env::MethodsWithBodies
|
6
|
+
METHODS_WITH_BODIES = %w(post put patch options)
|
7
|
+
|
8
|
+
class Model
|
9
|
+
class << self
|
10
|
+
def define_inheritable_accessor(accessor, options = {})
|
11
|
+
if options[:default_getter]
|
12
|
+
define_singleton_method(accessor, &options[:default_getter])
|
13
|
+
else
|
14
|
+
default_value = options[:default_value]
|
15
|
+
define_singleton_method(accessor) { default_value }
|
16
|
+
end
|
17
|
+
define_singleton_method(:"#{accessor}=") do |value|
|
18
|
+
singleton_class.instance_exec(value, self) do |value_, klass|
|
19
|
+
begin
|
20
|
+
remove_method(accessor)
|
21
|
+
rescue NameError
|
22
|
+
end
|
23
|
+
define_method(accessor) { value_ }
|
24
|
+
if options[:on_set]
|
25
|
+
klass.instance_exec(&options[:on_set])
|
26
|
+
end
|
27
|
+
end
|
28
|
+
if options[:update_methods]
|
29
|
+
update_dynamic_methods
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
define_inheritable_accessor(:api_description_class)
|
35
|
+
define_inheritable_accessor(:api_description, on_set: proc { self.api_description_class = self })
|
36
|
+
define_inheritable_accessor(:resource_name, update_methods: true)
|
37
|
+
define_inheritable_accessor(:schema_keys, default_value: [], update_methods: true, on_set: proc do
|
38
|
+
schema_keys.each do |key|
|
39
|
+
api_description_class.models_by_schema_id = api_description_class.models_by_schema_id.merge(schemas_by_key[key]['id'] => self)
|
40
|
+
api_description_class.models_by_schema_key = api_description_class.models_by_schema_key.merge(key => self)
|
41
|
+
end
|
42
|
+
end)
|
43
|
+
define_inheritable_accessor(:schemas_by_key, default_value: {})
|
44
|
+
define_inheritable_accessor(:schemas_by_id, default_value: {})
|
45
|
+
define_inheritable_accessor(:models_by_schema_id, default_value: {})
|
46
|
+
define_inheritable_accessor(:models_by_schema_key, default_value: {})
|
47
|
+
define_inheritable_accessor(:base_url)
|
48
|
+
|
49
|
+
define_inheritable_accessor(:faraday_request_middleware, default_value: [])
|
50
|
+
define_inheritable_accessor(:faraday_adapter, default_getter: proc { Faraday.default_adapter })
|
51
|
+
define_inheritable_accessor(:faraday_response_middleware, default_value: [])
|
52
|
+
class << self
|
53
|
+
def api_description_schema
|
54
|
+
@api_description_schema ||= begin
|
55
|
+
rest = YAML.load_file(Pathname.new(__FILE__).join('../../../getRest.yml'))
|
56
|
+
rest['schemas'].each do |name, schema_hash|
|
57
|
+
# URI hax because google doesn't put a URI in the id field properly
|
58
|
+
schema = JSON::Schema.new(schema_hash, Addressable::URI.parse(''))
|
59
|
+
JSON::Validator.add_schema(schema)
|
60
|
+
end
|
61
|
+
rest['schemas']['RestDescription']
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_api_description(api_description)
|
66
|
+
JSON::Validator.validate!(api_description_schema, api_description)
|
67
|
+
self.api_description = api_description
|
68
|
+
(api_description['schemas'] || {}).each do |schema_key, schema|
|
69
|
+
unless schema['id']
|
70
|
+
raise ArgumentError, "schema #{schema_key} did not contain an id"
|
71
|
+
end
|
72
|
+
schemas_by_id[schema['id']] = schema
|
73
|
+
schemas_by_key[schema_key] = schema
|
74
|
+
end
|
75
|
+
update_dynamic_methods
|
76
|
+
end
|
77
|
+
|
78
|
+
def update_dynamic_methods
|
79
|
+
update_class_and_instance_api_methods
|
80
|
+
update_instance_accessors
|
81
|
+
end
|
82
|
+
|
83
|
+
def all_schema_properties
|
84
|
+
schemas_by_key.select { |k, _| schema_keys.include?(k) }.map do |schema_key, schema|
|
85
|
+
unless schema['type'] == 'object'
|
86
|
+
raise "schema key #{schema_key} for #{self} is not of type object - type must be object for Scorpio Model to represent this schema" # TODO class
|
87
|
+
end
|
88
|
+
schema['properties'].keys
|
89
|
+
end.inject([], &:|)
|
90
|
+
end
|
91
|
+
|
92
|
+
def update_instance_accessors
|
93
|
+
all_schema_properties.each do |property_name|
|
94
|
+
unless method_defined?(property_name)
|
95
|
+
define_method(property_name) do
|
96
|
+
self[property_name]
|
97
|
+
end
|
98
|
+
end
|
99
|
+
unless method_defined?(:"#{property_name}=")
|
100
|
+
define_method(:"#{property_name}=") do |value|
|
101
|
+
self[property_name] = value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def update_class_and_instance_api_methods
|
108
|
+
if self.resource_name && api_description
|
109
|
+
resource_api_methods = ((api_description['resources'] || {})[resource_name] || {})['methods'] || {}
|
110
|
+
resource_api_methods.each do |method_name, method_desc|
|
111
|
+
# class method
|
112
|
+
unless respond_to?(method_name)
|
113
|
+
define_singleton_method(method_name) do |call_params = nil|
|
114
|
+
call_api_method(method_name, call_params: call_params)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# instance method
|
119
|
+
unless method_defined?(method_name)
|
120
|
+
request_schema = deref_schema(method_desc['request'])
|
121
|
+
|
122
|
+
# define an instance method if the request schema is for this model
|
123
|
+
request_resource_is_self = request_schema &&
|
124
|
+
request_schema['id'] &&
|
125
|
+
schemas_by_key.any? { |key, as| as['id'] == request_schema['id'] && schema_keys.include?(key) }
|
126
|
+
|
127
|
+
# also define an instance method depending on certain attributes the request description
|
128
|
+
# might have in common with the model's schema attributes
|
129
|
+
request_attributes = []
|
130
|
+
# if the path has attributes in common with model schema attributes, we'll define on
|
131
|
+
# instance method
|
132
|
+
request_attributes |= Addressable::Template.new(method_desc['path']).variables
|
133
|
+
# TODO if the method request schema has attributes in common with the model schema attributes,
|
134
|
+
# should we define an instance method?
|
135
|
+
#request_attributes |= request_schema && request_schema['type'] == 'object' && request_schema['properties'] ?
|
136
|
+
# request_schema['properties'].keys : []
|
137
|
+
# TODO if the method parameters have attributes in common with the model schema attributes,
|
138
|
+
# should we define an instance method?
|
139
|
+
#request_attributes |= method_desc['parameters'] ? method_desc['parameters'].keys : []
|
140
|
+
|
141
|
+
schema_attributes = schema_keys.map do |schema_key|
|
142
|
+
schema = schemas_by_key[schema_key]
|
143
|
+
schema['type'] == 'object' && schema['properties'] ? schema['properties'].keys : []
|
144
|
+
end.inject([], &:|)
|
145
|
+
|
146
|
+
if request_resource_is_self || (request_attributes & schema_attributes).any?
|
147
|
+
define_method(method_name) do |call_params = nil|
|
148
|
+
call_api_method(method_name, call_params: call_params)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def deref_schema(schema)
|
157
|
+
schema && schemas_by_id[schema['$ref']] || schema
|
158
|
+
end
|
159
|
+
|
160
|
+
MODULES_FOR_JSON_SCHEMA_TYPES = {
|
161
|
+
'object' => [Hash],
|
162
|
+
'array' => [Array, Set],
|
163
|
+
'string' => [String],
|
164
|
+
'integer' => [Integer],
|
165
|
+
'number' => [Numeric],
|
166
|
+
'boolean' => [TrueClass, FalseClass],
|
167
|
+
'null' => [NilClass],
|
168
|
+
}
|
169
|
+
|
170
|
+
def connection
|
171
|
+
Faraday.new do |c|
|
172
|
+
unless faraday_request_middleware.any? { |m| [*m].first == :json }
|
173
|
+
c.request :json
|
174
|
+
end
|
175
|
+
faraday_request_middleware.each do |m|
|
176
|
+
c.request(*m)
|
177
|
+
end
|
178
|
+
c.adapter(*faraday_adapter)
|
179
|
+
faraday_response_middleware.each do |m|
|
180
|
+
c.response(*m)
|
181
|
+
end
|
182
|
+
unless faraday_response_middleware.any? { |m| [*m].first == :json }
|
183
|
+
c.response :json, :content_type => /\bjson$/, :preserve_raw => true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def call_api_method(method_name, call_params: nil, model_attributes: nil)
|
189
|
+
call_params = Scorpio.stringify_symbol_keys(call_params || {})
|
190
|
+
model_attributes = Scorpio.stringify_symbol_keys(model_attributes || {})
|
191
|
+
method_desc = api_description['resources'][self.resource_name]['methods'][method_name]
|
192
|
+
http_method = method_desc['httpMethod'].downcase.to_sym
|
193
|
+
path_template = Addressable::Template.new(method_desc['path'])
|
194
|
+
template_params = model_attributes.merge(call_params)
|
195
|
+
missing_variables = path_template.variables - call_params.keys - model_attributes.keys
|
196
|
+
if missing_variables.any?
|
197
|
+
raise(ArgumentError, "path #{method_desc['path']} for method #{method_name} requires attributes " +
|
198
|
+
"which were missing: #{missing_variables.inspect}")
|
199
|
+
end
|
200
|
+
empty_variables = path_template.variables.select { |v| template_params[v].to_s.empty? }
|
201
|
+
if empty_variables.any?
|
202
|
+
raise(ArgumentError, "path #{method_desc['path']} for method #{method_name} requires attributes " +
|
203
|
+
"which were empty: #{empty_variables.inspect}")
|
204
|
+
end
|
205
|
+
path = path_template.expand(template_params)
|
206
|
+
url = Addressable::URI.parse(base_url) + path
|
207
|
+
# assume that call_params must be included somewhere. model_attributes are a source of required things
|
208
|
+
# but not required to be here.
|
209
|
+
other_params = call_params.reject { |k, _| path_template.variables.include?(k) }
|
210
|
+
|
211
|
+
method_desc = (((api_description['resources'] || {})[resource_name] || {})['methods'] || {})[method_name]
|
212
|
+
request_schema = deref_schema(method_desc['request'])
|
213
|
+
if request_schema
|
214
|
+
# TODO deal with model_attributes / call_params better in nested whatever
|
215
|
+
body = request_body_for_schema(model_attributes.merge(call_params), request_schema)
|
216
|
+
body.update(call_params)
|
217
|
+
else
|
218
|
+
if other_params.any?
|
219
|
+
if METHODS_WITH_BODIES.any? { |m| m == http_method.downcase }
|
220
|
+
body = other_params
|
221
|
+
else
|
222
|
+
# TODO pay more attention to 'parameters' api method attribute
|
223
|
+
url.query_values = other_params
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
response = connection.run_request(http_method, url, body, nil).tap do |response|
|
229
|
+
error_class = Scorpio.error_classes_by_status[response.status]
|
230
|
+
error_class ||= if (400..499).include?(response.status)
|
231
|
+
ClientError
|
232
|
+
elsif (500..599).include?(response.status)
|
233
|
+
ServerError
|
234
|
+
elsif !response.success?
|
235
|
+
HTTPError
|
236
|
+
end
|
237
|
+
if error_class
|
238
|
+
message = "Error calling #{method_name} on #{self}:\n" + (response.env[:raw_body] || response.env.body)
|
239
|
+
raise error_class.new(message).tap { |e| e.response = response }
|
240
|
+
end
|
241
|
+
end
|
242
|
+
response_schema = method_desc['response']
|
243
|
+
response_object_to_instances(response.body, response_schema, 'persisted' => true)
|
244
|
+
end
|
245
|
+
|
246
|
+
def request_body_for_schema(object, schema)
|
247
|
+
schema = deref_schema(schema)
|
248
|
+
if object.is_a?(Scorpio::Model)
|
249
|
+
# TODO request_schema_fail unless schema is for given model type
|
250
|
+
request_body_for_schema(object.represent_for_schema(schema), schema)
|
251
|
+
else
|
252
|
+
if object.is_a?(Hash)
|
253
|
+
object.map do |key, value|
|
254
|
+
if schema
|
255
|
+
if schema['type'] == 'object'
|
256
|
+
# TODO code dup with response_object_to_instances
|
257
|
+
if schema['properties'] && schema['properties'][key]
|
258
|
+
subschema = schema['properties'][key]
|
259
|
+
include_pair = true
|
260
|
+
else
|
261
|
+
if schema['patternProperties']
|
262
|
+
_, pattern_schema = schema['patternProperties'].detect do |pattern, _|
|
263
|
+
key =~ Regexp.new(pattern) # TODO map pattern to ruby syntax
|
264
|
+
end
|
265
|
+
end
|
266
|
+
if pattern_schema
|
267
|
+
subschema = pattern_schema
|
268
|
+
include_pair = true
|
269
|
+
else
|
270
|
+
if schema['additionalProperties'] == false
|
271
|
+
include_pair = false
|
272
|
+
elsif schema['additionalProperties'] == nil
|
273
|
+
# TODO decide on this (can combine with `else` if treating nil same as schema present)
|
274
|
+
include_pair = true
|
275
|
+
subschema = nil
|
276
|
+
else
|
277
|
+
include_pair = true
|
278
|
+
subschema = schema['additionalProperties']
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
elsif schema['type']
|
283
|
+
request_schema_fail(object, schema)
|
284
|
+
else
|
285
|
+
# TODO not sure
|
286
|
+
include_pair = true
|
287
|
+
subschema = nil
|
288
|
+
end
|
289
|
+
end
|
290
|
+
if include_pair
|
291
|
+
{key => request_body_for_schema(value, subschema)}
|
292
|
+
else
|
293
|
+
{}
|
294
|
+
end
|
295
|
+
end.inject({}, &:update)
|
296
|
+
elsif object.is_a?(Array) || object.is_a?(Set)
|
297
|
+
object.map do |el|
|
298
|
+
if schema
|
299
|
+
if schema['type'] == 'array'
|
300
|
+
# TODO index based subschema or whatever else works for array
|
301
|
+
subschema = schema['items']
|
302
|
+
else
|
303
|
+
request_schema_fail(object, schema)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
request_body_for_schema(el, subschema)
|
307
|
+
end
|
308
|
+
else
|
309
|
+
# TODO maybe raise on anything not jsonifiable
|
310
|
+
# TODO check conformance to schema, request_schema_fail if not
|
311
|
+
object
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def request_schema_fail(object, schema)
|
317
|
+
raise("object does not conform to schema.\nobject = #{object.inspect}\nschema = #{JSON.pretty_generate(schema, quirks_mode: true)}")
|
318
|
+
end
|
319
|
+
|
320
|
+
def response_object_to_instances(object, schema, initialize_options = {})
|
321
|
+
schema = deref_schema(schema)
|
322
|
+
if schema
|
323
|
+
if schema['type'] == 'object' && MODULES_FOR_JSON_SCHEMA_TYPES['object'].any? { |m| object.is_a?(m) }
|
324
|
+
out = object.map do |key, value|
|
325
|
+
schema_for_value = schema['properties'] && schema['properties'][key] ||
|
326
|
+
if schema['patternProperties']
|
327
|
+
_, pattern_schema = schema['patternProperties'].detect do |pattern, _|
|
328
|
+
key =~ Regexp.new(pattern)
|
329
|
+
end
|
330
|
+
pattern_schema
|
331
|
+
end ||
|
332
|
+
schema['additionalProperties']
|
333
|
+
{key => response_object_to_instances(value, schema_for_value)}
|
334
|
+
end.inject(object.class.new, &:update)
|
335
|
+
model = models_by_schema_id[schema['id']]
|
336
|
+
if model
|
337
|
+
model.new(out, initialize_options)
|
338
|
+
else
|
339
|
+
out
|
340
|
+
end
|
341
|
+
elsif schema['type'] == 'array' && MODULES_FOR_JSON_SCHEMA_TYPES['array'].any? { |m| object.is_a?(m) }
|
342
|
+
object.map do |element|
|
343
|
+
response_object_to_instances(element, schema['items'])
|
344
|
+
end
|
345
|
+
else
|
346
|
+
object
|
347
|
+
end
|
348
|
+
else
|
349
|
+
object
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def initialize(attributes = {}, options = {})
|
355
|
+
@attributes = Scorpio.stringify_symbol_keys(attributes)
|
356
|
+
@options = Scorpio.stringify_symbol_keys(options)
|
357
|
+
@persisted = !!@options['persisted']
|
358
|
+
end
|
359
|
+
|
360
|
+
attr_reader :attributes
|
361
|
+
attr_reader :options
|
362
|
+
|
363
|
+
def persisted?
|
364
|
+
@persisted
|
365
|
+
end
|
366
|
+
|
367
|
+
def [](key)
|
368
|
+
@attributes[key]
|
369
|
+
end
|
370
|
+
|
371
|
+
def []=(key, value)
|
372
|
+
@attributes[key] = value
|
373
|
+
end
|
374
|
+
|
375
|
+
def ==(other)
|
376
|
+
@attributes == other.instance_eval { @attributes }
|
377
|
+
end
|
378
|
+
|
379
|
+
def call_api_method(method_name, call_params: nil)
|
380
|
+
response = self.class.call_api_method(method_name, call_params: call_params, model_attributes: self.attributes)
|
381
|
+
|
382
|
+
# if we're making a POST or PUT and the request schema is this resource, we'll assume that
|
383
|
+
# the request is persisting this resource
|
384
|
+
api_method = self.class.api_description['resources'][self.class.resource_name]['methods'][method_name]
|
385
|
+
request_schema = self.class.deref_schema(api_method['request'])
|
386
|
+
request_resource_is_self = request_schema &&
|
387
|
+
request_schema['id'] &&
|
388
|
+
self.class.schemas_by_key.any? { |key, as| as['id'] == request_schema['id'] && self.class.schema_keys.include?(key) }
|
389
|
+
response_schema = self.class.deref_schema(api_method['response'])
|
390
|
+
response_resource_is_self = response_schema &&
|
391
|
+
response_schema['id'] &&
|
392
|
+
self.class.schemas_by_key.any? { |key, as| as['id'] == response_schema['id'] && self.class.schema_keys.include?(key) }
|
393
|
+
if request_resource_is_self && %w(PUT POST).include?(api_method['httpMethod'])
|
394
|
+
@persisted = true
|
395
|
+
|
396
|
+
if response_resource_is_self
|
397
|
+
@attributes = response.attributes
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
response
|
402
|
+
end
|
403
|
+
|
404
|
+
# TODO
|
405
|
+
def represent_for_schema(schema)
|
406
|
+
@attributes
|
407
|
+
end
|
408
|
+
|
409
|
+
alias eql? ==
|
410
|
+
|
411
|
+
def hash
|
412
|
+
@attributes.hash
|
413
|
+
end
|
414
|
+
end
|
415
|
+
end
|