carwow-json_api_client 1.19.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 +7 -0
- data/LICENSE +20 -0
- data/README.md +706 -0
- data/Rakefile +32 -0
- data/lib/json_api_client.rb +30 -0
- data/lib/json_api_client/associations.rb +8 -0
- data/lib/json_api_client/associations/base_association.rb +33 -0
- data/lib/json_api_client/associations/belongs_to.rb +31 -0
- data/lib/json_api_client/associations/has_many.rb +8 -0
- data/lib/json_api_client/associations/has_one.rb +16 -0
- data/lib/json_api_client/connection.rb +41 -0
- data/lib/json_api_client/error_collector.rb +91 -0
- data/lib/json_api_client/errors.rb +107 -0
- data/lib/json_api_client/formatter.rb +145 -0
- data/lib/json_api_client/helpers.rb +9 -0
- data/lib/json_api_client/helpers/associatable.rb +88 -0
- data/lib/json_api_client/helpers/callbacks.rb +27 -0
- data/lib/json_api_client/helpers/dirty.rb +75 -0
- data/lib/json_api_client/helpers/dynamic_attributes.rb +78 -0
- data/lib/json_api_client/helpers/uri.rb +9 -0
- data/lib/json_api_client/implementation.rb +12 -0
- data/lib/json_api_client/included_data.rb +58 -0
- data/lib/json_api_client/linking.rb +6 -0
- data/lib/json_api_client/linking/links.rb +22 -0
- data/lib/json_api_client/linking/top_level_links.rb +39 -0
- data/lib/json_api_client/meta_data.rb +19 -0
- data/lib/json_api_client/middleware.rb +7 -0
- data/lib/json_api_client/middleware/json_request.rb +26 -0
- data/lib/json_api_client/middleware/parse_json.rb +31 -0
- data/lib/json_api_client/middleware/status.rb +67 -0
- data/lib/json_api_client/paginating.rb +6 -0
- data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
- data/lib/json_api_client/paginating/paginator.rb +89 -0
- data/lib/json_api_client/parsers.rb +5 -0
- data/lib/json_api_client/parsers/parser.rb +102 -0
- data/lib/json_api_client/query.rb +6 -0
- data/lib/json_api_client/query/builder.rb +239 -0
- data/lib/json_api_client/query/requestor.rb +73 -0
- data/lib/json_api_client/relationships.rb +6 -0
- data/lib/json_api_client/relationships/relations.rb +55 -0
- data/lib/json_api_client/relationships/top_level_relations.rb +30 -0
- data/lib/json_api_client/request_params.rb +57 -0
- data/lib/json_api_client/resource.rb +643 -0
- data/lib/json_api_client/result_set.rb +25 -0
- data/lib/json_api_client/schema.rb +154 -0
- data/lib/json_api_client/utils.rb +48 -0
- data/lib/json_api_client/version.rb +3 -0
- metadata +213 -0
@@ -0,0 +1,643 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'active_support/all'
|
3
|
+
require 'active_model'
|
4
|
+
|
5
|
+
module JsonApiClient
|
6
|
+
class Resource
|
7
|
+
extend ActiveModel::Naming
|
8
|
+
extend ActiveModel::Translation
|
9
|
+
include ActiveModel::Validations
|
10
|
+
include ActiveModel::Conversion
|
11
|
+
include ActiveModel::Serialization
|
12
|
+
|
13
|
+
include Helpers::DynamicAttributes
|
14
|
+
include Helpers::Dirty
|
15
|
+
include Helpers::Associatable
|
16
|
+
|
17
|
+
attr_accessor :last_result_set,
|
18
|
+
:links,
|
19
|
+
:relationships,
|
20
|
+
:request_params
|
21
|
+
class_attribute :site,
|
22
|
+
:primary_key,
|
23
|
+
:parser,
|
24
|
+
:paginator,
|
25
|
+
:connection_class,
|
26
|
+
:connection_object,
|
27
|
+
:connection_options,
|
28
|
+
:query_builder,
|
29
|
+
:linker,
|
30
|
+
:relationship_linker,
|
31
|
+
:read_only_attributes,
|
32
|
+
:requestor_class,
|
33
|
+
:json_key_format,
|
34
|
+
:route_format,
|
35
|
+
:request_params_class,
|
36
|
+
:keep_request_params,
|
37
|
+
:search_included_in_result_set,
|
38
|
+
:custom_type_to_class,
|
39
|
+
:raise_on_blank_find_param,
|
40
|
+
instance_accessor: false
|
41
|
+
class_attribute :add_defaults_to_changes,
|
42
|
+
instance_writer: false
|
43
|
+
|
44
|
+
class_attribute :_immutable,
|
45
|
+
instance_writer: false,
|
46
|
+
default: false
|
47
|
+
|
48
|
+
self.primary_key = :id
|
49
|
+
self.parser = Parsers::Parser
|
50
|
+
self.paginator = Paginating::Paginator
|
51
|
+
self.connection_class = Connection
|
52
|
+
self.connection_options = {}
|
53
|
+
self.query_builder = Query::Builder
|
54
|
+
self.linker = Linking::Links
|
55
|
+
self.relationship_linker = Relationships::Relations
|
56
|
+
self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
|
57
|
+
self.requestor_class = Query::Requestor
|
58
|
+
self.request_params_class = RequestParams
|
59
|
+
self.keep_request_params = false
|
60
|
+
self.add_defaults_to_changes = false
|
61
|
+
self.search_included_in_result_set = false
|
62
|
+
self.custom_type_to_class = {}
|
63
|
+
self.raise_on_blank_find_param = false
|
64
|
+
|
65
|
+
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
66
|
+
self.json_key_format = :underscored_key
|
67
|
+
|
68
|
+
#:underscored_route, :camelized_route, :dasherized_route, or custom
|
69
|
+
self.route_format = :underscored_route
|
70
|
+
|
71
|
+
class << self
|
72
|
+
extend Forwardable
|
73
|
+
def_delegators :_new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :with_params, :first, :find, :last
|
74
|
+
|
75
|
+
def resolve_custom_type(type_name, class_name)
|
76
|
+
classified_type = key_formatter.unformat(type_name.to_s).singularize.classify
|
77
|
+
self.custom_type_to_class = custom_type_to_class.merge(classified_type => class_name.to_s)
|
78
|
+
end
|
79
|
+
|
80
|
+
# The table name for this resource. i.e. Article -> articles, Person -> people
|
81
|
+
#
|
82
|
+
# @return [String] The table name for this resource
|
83
|
+
def table_name
|
84
|
+
route_formatter.format(resource_name.pluralize)
|
85
|
+
end
|
86
|
+
|
87
|
+
# The name of a single resource. i.e. Article -> article, Person -> person
|
88
|
+
#
|
89
|
+
# @return [String]
|
90
|
+
def resource_name
|
91
|
+
name.demodulize.underscore
|
92
|
+
end
|
93
|
+
|
94
|
+
# Specifies the JSON API resource type. By default this is inferred
|
95
|
+
# from the resource class name.
|
96
|
+
#
|
97
|
+
# @return [String] Resource path
|
98
|
+
def type
|
99
|
+
table_name
|
100
|
+
end
|
101
|
+
|
102
|
+
# Indicates whether this resource is mutable or immutable;
|
103
|
+
# by default, all resources are mutable.
|
104
|
+
#
|
105
|
+
# @return [Boolean]
|
106
|
+
def immutable(flag = true)
|
107
|
+
self._immutable = flag
|
108
|
+
end
|
109
|
+
|
110
|
+
def inherited(subclass)
|
111
|
+
subclass._immutable = false
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
# Specifies the relative path that should be used for this resource;
|
116
|
+
# by default, this is inferred from the resource class name.
|
117
|
+
#
|
118
|
+
# @return [String] Resource path
|
119
|
+
def resource_path
|
120
|
+
table_name
|
121
|
+
end
|
122
|
+
|
123
|
+
# Load a resource object from attributes and consider it persisted
|
124
|
+
#
|
125
|
+
# @return [Resource] Persisted resource object
|
126
|
+
def load(params)
|
127
|
+
new(params).tap do |resource|
|
128
|
+
resource.mark_as_persisted!
|
129
|
+
resource.clear_changes_information
|
130
|
+
resource.relationships.clear_changes_information
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Return/build a connection object
|
135
|
+
#
|
136
|
+
# @return [Connection] The connection to the json api server
|
137
|
+
def connection(rebuild = false, &block)
|
138
|
+
_build_connection(rebuild, &block)
|
139
|
+
connection_object
|
140
|
+
end
|
141
|
+
|
142
|
+
# Param names that will be considered path params. They will be used
|
143
|
+
# to build the resource path rather than treated as attributes
|
144
|
+
#
|
145
|
+
# @return [Array] Param name symbols of parameters that will be treated as path parameters
|
146
|
+
def prefix_params
|
147
|
+
_belongs_to_associations.map(&:param)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Return the path or path pattern for this resource
|
151
|
+
def path(params = nil)
|
152
|
+
parts = [resource_path]
|
153
|
+
if params && _prefix_path.present?
|
154
|
+
path_params = params.delete(:path) || params
|
155
|
+
parts.unshift(_set_prefix_path(path_params.symbolize_keys))
|
156
|
+
else
|
157
|
+
parts.unshift(_prefix_path)
|
158
|
+
end
|
159
|
+
parts.reject!(&:blank?)
|
160
|
+
File.join(*parts)
|
161
|
+
rescue KeyError
|
162
|
+
raise ArgumentError, "Not all prefix parameters specified"
|
163
|
+
end
|
164
|
+
|
165
|
+
# Create a new instance of this resource class
|
166
|
+
#
|
167
|
+
# @param attributes [Hash] The attributes to create this resource with
|
168
|
+
# @return [Resource] The instance you tried to create. You will have to check the persisted state or errors on this object to see success/failure.
|
169
|
+
def create(attributes = {})
|
170
|
+
new(attributes).tap do |resource|
|
171
|
+
resource.save
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Within the given block, add these headers to all requests made by
|
176
|
+
# the resource class
|
177
|
+
#
|
178
|
+
# @param headers [Hash] The headers to send along
|
179
|
+
# @param block [Block] The block where headers will be set for
|
180
|
+
def with_headers(headers)
|
181
|
+
self._custom_headers = headers
|
182
|
+
yield
|
183
|
+
ensure
|
184
|
+
self._custom_headers = {}
|
185
|
+
end
|
186
|
+
|
187
|
+
# The current custom headers to send with any request made by this
|
188
|
+
# resource class
|
189
|
+
#
|
190
|
+
# @return [Hash] Headers
|
191
|
+
def custom_headers
|
192
|
+
return _header_store.to_h if superclass == Object
|
193
|
+
|
194
|
+
superclass.custom_headers.merge(_header_store.to_h)
|
195
|
+
end
|
196
|
+
|
197
|
+
# Returns the requestor for this resource class
|
198
|
+
#
|
199
|
+
# @return [Requestor] The requestor for this resource class
|
200
|
+
def requestor
|
201
|
+
@requestor ||= requestor_class.new(self)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Default attributes that every instance of this resource should be
|
205
|
+
# initialized with. Optionally, override this method in a subclass.
|
206
|
+
#
|
207
|
+
# @return [Hash] Default attributes
|
208
|
+
def default_attributes
|
209
|
+
{type: type}
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns the schema for this resource class
|
213
|
+
#
|
214
|
+
# @return [Schema] The schema for this resource class
|
215
|
+
def schema
|
216
|
+
@schema ||= Schema.new
|
217
|
+
end
|
218
|
+
|
219
|
+
def key_formatter
|
220
|
+
JsonApiClient::Formatter.formatter_for(json_key_format)
|
221
|
+
end
|
222
|
+
|
223
|
+
def route_formatter
|
224
|
+
JsonApiClient::Formatter.formatter_for(route_format)
|
225
|
+
end
|
226
|
+
|
227
|
+
protected
|
228
|
+
|
229
|
+
# Declares a new class/instance method that acts on the collection/member
|
230
|
+
#
|
231
|
+
# @param name [Symbol] the name of the endpoint
|
232
|
+
# @param options [Hash] endpoint options
|
233
|
+
# @option [Symbol] :on One of [:collection or :member] to decide whether it's a collect or member method
|
234
|
+
# @option [Symbol] :request_method The request method (:get, :post, etc)
|
235
|
+
def custom_endpoint(name, options = {})
|
236
|
+
if _immutable
|
237
|
+
request_method = options.fetch(:request_method, :get).to_sym
|
238
|
+
raise JsonApiClient::Errors::ResourceImmutableError if request_method != :get
|
239
|
+
end
|
240
|
+
|
241
|
+
if :collection == options.delete(:on)
|
242
|
+
collection_endpoint(name, options)
|
243
|
+
else
|
244
|
+
member_endpoint(name, options)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
# Declares a new class method that acts on the collection
|
249
|
+
#
|
250
|
+
# @param name [Symbol] the name of the endpoint and the method name
|
251
|
+
# @param options [Hash] endpoint options
|
252
|
+
# @option options [Symbol] :request_method The request method (:get, :post, etc)
|
253
|
+
def collection_endpoint(name, options = {})
|
254
|
+
metaclass = class << self
|
255
|
+
self
|
256
|
+
end
|
257
|
+
metaclass.instance_eval do
|
258
|
+
define_method(name) do |*params|
|
259
|
+
request_params = params.first || {}
|
260
|
+
requestor.custom(name, options, request_params)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Declares a new instance method that acts on the member object
|
266
|
+
#
|
267
|
+
# @param name [Symbol] the name of the endpoint and the method name
|
268
|
+
# @param options [Hash] endpoint options
|
269
|
+
# @option options [Symbol] :request_method The request method (:get, :post, etc)
|
270
|
+
def member_endpoint(name, options = {})
|
271
|
+
define_method name do |*params|
|
272
|
+
request_params = params.first || {}
|
273
|
+
request_params[self.class.primary_key] = attributes.fetch(self.class.primary_key)
|
274
|
+
self.class.requestor.custom(name, options, request_params)
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# Declares a new property by name
|
279
|
+
#
|
280
|
+
# @param name [Symbol] the name of the property
|
281
|
+
# @param options [Hash] property options
|
282
|
+
# @option options [Symbol] :type The property type
|
283
|
+
# @option options [Symbol] :default The default value for the property
|
284
|
+
def property(name, options = {})
|
285
|
+
schema.add(name, options)
|
286
|
+
define_method(name) do
|
287
|
+
attributes[name]
|
288
|
+
end
|
289
|
+
define_method("#{name}=") do |value|
|
290
|
+
set_attribute(name, value)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
# Declare multiple properties with the same optional options
|
295
|
+
#
|
296
|
+
# @param [Array<Symbol>] names
|
297
|
+
# @param options [Hash] property options
|
298
|
+
# @option options [Symbol] :type The property type
|
299
|
+
# @option options [Symbol] :default The default value for the property
|
300
|
+
def properties(*names)
|
301
|
+
options = names.last.is_a?(Hash) ? names.pop : {}
|
302
|
+
names.each do |name|
|
303
|
+
property name, options
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def _belongs_to_associations
|
308
|
+
associations.select{|association| association.is_a?(Associations::BelongsTo::Association) }
|
309
|
+
end
|
310
|
+
|
311
|
+
def _prefix_path
|
312
|
+
paths = _belongs_to_associations.map do |a|
|
313
|
+
a.to_prefix_path(route_formatter)
|
314
|
+
end
|
315
|
+
|
316
|
+
paths.compact.join("/")
|
317
|
+
end
|
318
|
+
|
319
|
+
def _set_prefix_path(attrs)
|
320
|
+
paths = _belongs_to_associations.map do |a|
|
321
|
+
a.set_prefix_path(attrs, route_formatter)
|
322
|
+
end
|
323
|
+
|
324
|
+
paths.compact.join("/")
|
325
|
+
end
|
326
|
+
|
327
|
+
def _new_scope
|
328
|
+
query_builder.new(self)
|
329
|
+
end
|
330
|
+
|
331
|
+
def _custom_headers=(headers)
|
332
|
+
_header_store.replace(headers)
|
333
|
+
end
|
334
|
+
|
335
|
+
def _header_store
|
336
|
+
Thread.current["json_api_client-#{resource_name}"] ||= {}
|
337
|
+
end
|
338
|
+
|
339
|
+
def _build_connection(rebuild = false)
|
340
|
+
return connection_object unless connection_object.nil? || rebuild
|
341
|
+
self.connection_object = connection_class.new(connection_options.merge(site: site)).tap do |conn|
|
342
|
+
yield(conn) if block_given?
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
# Instantiate a new resource object
|
348
|
+
#
|
349
|
+
# @param params [Hash] Attributes, links, and relationships
|
350
|
+
def initialize(params = {})
|
351
|
+
params = params.symbolize_keys
|
352
|
+
@persisted = nil
|
353
|
+
@destroyed = nil
|
354
|
+
self.links = self.class.linker.new(params.delete(:links) || {})
|
355
|
+
self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {})
|
356
|
+
self.attributes = self.class.default_attributes.merge params.except(*self.class.prefix_params)
|
357
|
+
self.forget_change!(:type)
|
358
|
+
self.__belongs_to_params = params.slice(*self.class.prefix_params)
|
359
|
+
|
360
|
+
setup_default_properties
|
361
|
+
|
362
|
+
self.class.associations.each do |association|
|
363
|
+
if params.has_key?(association.attr_name.to_s)
|
364
|
+
set_attribute(association.attr_name, params[association.attr_name.to_s])
|
365
|
+
end
|
366
|
+
end
|
367
|
+
self.request_params = self.class.request_params_class.new(self.class)
|
368
|
+
end
|
369
|
+
|
370
|
+
# Set the current attributes and try to save them
|
371
|
+
#
|
372
|
+
# @param attrs [Hash] Attributes to update
|
373
|
+
# @return [Boolean] Whether the update succeeded or not
|
374
|
+
def update_attributes(attrs = {})
|
375
|
+
self.attributes = attrs
|
376
|
+
save
|
377
|
+
end
|
378
|
+
|
379
|
+
# Alias to update_attributes
|
380
|
+
#
|
381
|
+
# @param attrs [Hash] Attributes to update
|
382
|
+
# @return [Boolean] Whether the update succeeded or not
|
383
|
+
def update(attrs = {})
|
384
|
+
update_attributes(attrs)
|
385
|
+
end
|
386
|
+
|
387
|
+
# Mark the record as persisted
|
388
|
+
def mark_as_persisted!
|
389
|
+
@persisted = true
|
390
|
+
end
|
391
|
+
|
392
|
+
# Whether or not this record has been persisted to the database previously
|
393
|
+
#
|
394
|
+
# @return [Boolean]
|
395
|
+
def persisted?
|
396
|
+
!!@persisted && !destroyed? && has_attribute?(self.class.primary_key)
|
397
|
+
end
|
398
|
+
|
399
|
+
# Mark the record as destroyed
|
400
|
+
def mark_as_destroyed!
|
401
|
+
@destroyed = true
|
402
|
+
end
|
403
|
+
|
404
|
+
# Whether or not this record has been destroyed to the database previously
|
405
|
+
#
|
406
|
+
# @return [Boolean]
|
407
|
+
def destroyed?
|
408
|
+
!!@destroyed
|
409
|
+
end
|
410
|
+
|
411
|
+
# Returns true if this is a new record (never persisted to the database)
|
412
|
+
#
|
413
|
+
# @return [Boolean]
|
414
|
+
def new_record?
|
415
|
+
!persisted? && !destroyed?
|
416
|
+
end
|
417
|
+
|
418
|
+
# When we represent this resource as a relationship, we do so with id & type
|
419
|
+
#
|
420
|
+
# @return [Hash] Representation of this object as a relation
|
421
|
+
def as_relation
|
422
|
+
attributes.slice(:type, self.class.primary_key)
|
423
|
+
end
|
424
|
+
|
425
|
+
# When we represent this resource for serialization (create/update), we do so
|
426
|
+
# with this implementation
|
427
|
+
#
|
428
|
+
# @return [Hash] Representation of this object as JSONAPI object
|
429
|
+
def as_json_api(*)
|
430
|
+
attributes.slice(self.class.primary_key, :type).tap do |h|
|
431
|
+
relationships_for_serialization.tap do |r|
|
432
|
+
h[:relationships] = self.class.key_formatter.format_keys(r) unless r.empty?
|
433
|
+
end
|
434
|
+
h[:attributes] = self.class.key_formatter.format_keys(attributes_for_serialization)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
def as_json(*)
|
439
|
+
attributes.slice(self.class.primary_key, :type).tap do |h|
|
440
|
+
relationships.as_json.tap do |r|
|
441
|
+
h[:relationships] = r unless r.empty?
|
442
|
+
end
|
443
|
+
h[:attributes] = attributes.except(self.class.primary_key, :type).as_json
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Mark all attributes for this record as dirty
|
448
|
+
def set_all_dirty!
|
449
|
+
set_all_attributes_dirty
|
450
|
+
relationships.set_all_attributes_dirty if relationships
|
451
|
+
end
|
452
|
+
|
453
|
+
def valid?(context = nil)
|
454
|
+
context ||= (new_record? ? :create : :update)
|
455
|
+
super(context)
|
456
|
+
end
|
457
|
+
|
458
|
+
# Commit the current changes to the resource to the remote server.
|
459
|
+
# If the resource was previously loaded from the server, we will
|
460
|
+
# try to update the record. Otherwise if it's a new record, then
|
461
|
+
# we will try to create it
|
462
|
+
#
|
463
|
+
# @return [Boolean] Whether or not the save succeeded
|
464
|
+
def save
|
465
|
+
return false unless valid?
|
466
|
+
raise JsonApiClient::Errors::ResourceImmutableError if _immutable
|
467
|
+
|
468
|
+
self.last_result_set = if persisted?
|
469
|
+
self.class.requestor.update(self)
|
470
|
+
else
|
471
|
+
self.class.requestor.create(self)
|
472
|
+
end
|
473
|
+
|
474
|
+
if last_result_set.has_errors?
|
475
|
+
fill_errors
|
476
|
+
false
|
477
|
+
else
|
478
|
+
self.errors.clear if self.errors
|
479
|
+
self.request_params.clear unless self.class.keep_request_params
|
480
|
+
mark_as_persisted!
|
481
|
+
if updated = last_result_set.first
|
482
|
+
self.attributes = updated.attributes
|
483
|
+
self.links.attributes = updated.links.attributes
|
484
|
+
self.relationships.attributes = updated.relationships.attributes
|
485
|
+
clear_changes_information
|
486
|
+
self.relationships.clear_changes_information
|
487
|
+
_clear_cached_relationships
|
488
|
+
end
|
489
|
+
true
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Try to destroy this resource
|
494
|
+
#
|
495
|
+
# @return [Boolean] Whether or not the destroy succeeded
|
496
|
+
def destroy
|
497
|
+
raise JsonApiClient::Errors::ResourceImmutableError if _immutable
|
498
|
+
|
499
|
+
self.last_result_set = self.class.requestor.destroy(self)
|
500
|
+
if last_result_set.has_errors?
|
501
|
+
fill_errors
|
502
|
+
false
|
503
|
+
else
|
504
|
+
mark_as_destroyed!
|
505
|
+
_clear_cached_relationships
|
506
|
+
_clear_belongs_to_params
|
507
|
+
true
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
def inspect
|
512
|
+
"#<#{self.class.name}:@attributes=#{attributes.inspect}>"
|
513
|
+
end
|
514
|
+
|
515
|
+
def request_includes(*includes)
|
516
|
+
self.request_params.add_includes(includes)
|
517
|
+
self
|
518
|
+
end
|
519
|
+
|
520
|
+
def reset_request_includes!
|
521
|
+
self.request_params.reset_includes!
|
522
|
+
self
|
523
|
+
end
|
524
|
+
|
525
|
+
def request_select(*fields)
|
526
|
+
fields_by_type = fields.extract_options!
|
527
|
+
fields_by_type[type.to_sym] = fields if fields.any?
|
528
|
+
fields_by_type.each do |field_type, field_names|
|
529
|
+
self.request_params.set_fields(field_type, field_names)
|
530
|
+
end
|
531
|
+
self
|
532
|
+
end
|
533
|
+
|
534
|
+
def reset_request_select!(*resource_types)
|
535
|
+
resource_types = self.request_params.field_types if resource_types.empty?
|
536
|
+
resource_types.each { |resource_type| self.request_params.remove_fields(resource_type) }
|
537
|
+
self
|
538
|
+
end
|
539
|
+
|
540
|
+
def path_attributes
|
541
|
+
_belongs_to_params.merge attributes.slice( self.class.primary_key ).symbolize_keys
|
542
|
+
end
|
543
|
+
|
544
|
+
protected
|
545
|
+
|
546
|
+
def setup_default_properties
|
547
|
+
self.class.schema.each_property do |property|
|
548
|
+
unless attributes.has_key?(property.name) || property.default.nil?
|
549
|
+
attribute_will_change!(property.name) if add_defaults_to_changes
|
550
|
+
attributes[property.name] = property.default
|
551
|
+
end
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
def relationship_definition_for(name)
|
556
|
+
relationships[name] if relationships && relationships.has_attribute?(name)
|
557
|
+
end
|
558
|
+
|
559
|
+
def included_data_for(name, relationship_definition)
|
560
|
+
last_result_set.included.data_for(name, relationship_definition)
|
561
|
+
end
|
562
|
+
|
563
|
+
def relationship_data_for(name, relationship_definition)
|
564
|
+
# look in included data
|
565
|
+
if relationship_definition.key?("data")
|
566
|
+
if relationships.attribute_changed?(name)
|
567
|
+
return relation_objects_for(name, relationship_definition)
|
568
|
+
else
|
569
|
+
return included_data_for(name, relationship_definition)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
|
573
|
+
return unless links = relationship_definition["links"]
|
574
|
+
return unless url = links["related"]
|
575
|
+
|
576
|
+
association_for(name).data(url)
|
577
|
+
end
|
578
|
+
|
579
|
+
def relation_objects_for(name, relationship_definition)
|
580
|
+
data = relationship_definition["data"]
|
581
|
+
assoc = association_for(name)
|
582
|
+
return if data.nil? || assoc.nil?
|
583
|
+
assoc.load_records(data)
|
584
|
+
end
|
585
|
+
|
586
|
+
def method_missing(method, *args)
|
587
|
+
relationship_definition = relationship_definition_for(method)
|
588
|
+
|
589
|
+
return super unless relationship_definition
|
590
|
+
|
591
|
+
relationship_data_for(method, relationship_definition)
|
592
|
+
end
|
593
|
+
|
594
|
+
def respond_to_missing?(symbol, include_all = false)
|
595
|
+
return true if relationships && relationships.has_attribute?(symbol)
|
596
|
+
return true if association_for(symbol)
|
597
|
+
super
|
598
|
+
end
|
599
|
+
|
600
|
+
def set_attribute(name, value)
|
601
|
+
property = property_for(name)
|
602
|
+
value = property.cast(value) if property
|
603
|
+
super(name, value)
|
604
|
+
end
|
605
|
+
|
606
|
+
def has_attribute?(attr_name)
|
607
|
+
!!property_for(attr_name) || super
|
608
|
+
end
|
609
|
+
|
610
|
+
def property_for(name)
|
611
|
+
self.class.schema.find(name)
|
612
|
+
end
|
613
|
+
|
614
|
+
def association_for(name)
|
615
|
+
self.class.associations.detect do |association|
|
616
|
+
association.attr_name.to_s == self.class.key_formatter.unformat(name)
|
617
|
+
end
|
618
|
+
end
|
619
|
+
|
620
|
+
def non_serializing_attributes
|
621
|
+
self.class.read_only_attributes
|
622
|
+
end
|
623
|
+
|
624
|
+
def attributes_for_serialization
|
625
|
+
attributes.except(*non_serializing_attributes).slice(*changed)
|
626
|
+
end
|
627
|
+
|
628
|
+
def relationships_for_serialization
|
629
|
+
relationships.as_json_api
|
630
|
+
end
|
631
|
+
|
632
|
+
def error_message_for(error)
|
633
|
+
error.error_msg
|
634
|
+
end
|
635
|
+
|
636
|
+
def fill_errors
|
637
|
+
last_result_set.errors.each do |error|
|
638
|
+
key = self.class.key_formatter.unformat(error.error_key)
|
639
|
+
errors.add(key, error_message_for(error))
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|