scorpio 0.0.1
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 +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
|