json_api_client 1.5.2 → 1.22.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 +5 -5
- data/README.md +206 -9
- data/lib/json_api_client/associations/base_association.rb +7 -0
- data/lib/json_api_client/associations/belongs_to.rb +11 -10
- data/lib/json_api_client/associations/has_many.rb +0 -8
- data/lib/json_api_client/associations/has_one.rb +6 -9
- data/lib/json_api_client/connection.rb +8 -3
- data/lib/json_api_client/error_collector.rb +19 -10
- data/lib/json_api_client/errors.rb +86 -17
- data/lib/json_api_client/helpers/associatable.rb +88 -0
- data/lib/json_api_client/helpers/dirty.rb +5 -1
- data/lib/json_api_client/helpers/dynamic_attributes.rb +19 -11
- data/lib/json_api_client/helpers.rb +1 -0
- data/lib/json_api_client/included_data.rb +24 -12
- data/lib/json_api_client/middleware/json_request.rb +16 -1
- data/lib/json_api_client/middleware/status.rb +32 -6
- data/lib/json_api_client/paginating/nested_param_paginator.rb +140 -0
- data/lib/json_api_client/paginating/paginator.rb +1 -1
- data/lib/json_api_client/paginating.rb +2 -1
- data/lib/json_api_client/query/builder.rb +77 -49
- data/lib/json_api_client/query/requestor.rb +21 -13
- data/lib/json_api_client/relationships/relations.rb +0 -1
- data/lib/json_api_client/request_params.rb +57 -0
- data/lib/json_api_client/resource.rb +196 -46
- data/lib/json_api_client/schema.rb +3 -3
- data/lib/json_api_client/utils.rb +26 -1
- data/lib/json_api_client/version.rb +1 -1
- data/lib/json_api_client.rb +2 -1
- metadata +44 -16
|
@@ -10,37 +10,44 @@ module JsonApiClient
|
|
|
10
10
|
|
|
11
11
|
# expects a record
|
|
12
12
|
def create(record)
|
|
13
|
-
request(
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
request(
|
|
14
|
+
:post,
|
|
15
|
+
klass.path(record.path_attributes),
|
|
16
|
+
body: { data: record.as_json_api },
|
|
17
|
+
params: record.request_params.to_params
|
|
18
|
+
)
|
|
16
19
|
end
|
|
17
20
|
|
|
18
21
|
def update(record)
|
|
19
|
-
request(
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
request(
|
|
23
|
+
:patch,
|
|
24
|
+
resource_path(record.path_attributes),
|
|
25
|
+
body: { data: record.as_json_api },
|
|
26
|
+
params: record.request_params.to_params
|
|
27
|
+
)
|
|
22
28
|
end
|
|
23
29
|
|
|
24
30
|
def get(params = {})
|
|
25
31
|
path = resource_path(params)
|
|
26
32
|
params.delete(klass.primary_key)
|
|
27
|
-
request(:get, path, params)
|
|
33
|
+
request(:get, path, params: params)
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
def destroy(record)
|
|
31
|
-
request(:delete, resource_path(record.
|
|
37
|
+
request(:delete, resource_path(record.path_attributes))
|
|
32
38
|
end
|
|
33
39
|
|
|
34
40
|
def linked(path)
|
|
35
|
-
request(:get, path
|
|
41
|
+
request(:get, path)
|
|
36
42
|
end
|
|
37
43
|
|
|
38
44
|
def custom(method_name, options, params)
|
|
39
45
|
path = resource_path(params)
|
|
40
46
|
params.delete(klass.primary_key)
|
|
41
47
|
path = File.join(path, method_name.to_s)
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
request_method = options.fetch(:request_method, :get).to_sym
|
|
49
|
+
query_params, body_params = [:get, :delete].include?(request_method) ? [params, nil] : [nil, params]
|
|
50
|
+
request(request_method, path, params: query_params, body: body_params)
|
|
44
51
|
end
|
|
45
52
|
|
|
46
53
|
protected
|
|
@@ -56,8 +63,9 @@ module JsonApiClient
|
|
|
56
63
|
end
|
|
57
64
|
end
|
|
58
65
|
|
|
59
|
-
def request(type, path, params)
|
|
60
|
-
|
|
66
|
+
def request(type, path, params: nil, body: nil)
|
|
67
|
+
response = connection.run(type, path, params: params, body: body, headers: klass.custom_headers)
|
|
68
|
+
klass.parser.parse(klass, response)
|
|
61
69
|
end
|
|
62
70
|
|
|
63
71
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module JsonApiClient
|
|
2
|
+
class RequestParams
|
|
3
|
+
attr_reader :klass, :includes, :fields
|
|
4
|
+
|
|
5
|
+
def initialize(klass, includes: [], fields: {})
|
|
6
|
+
@klass = klass
|
|
7
|
+
@includes = includes
|
|
8
|
+
@fields = fields
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_includes(includes)
|
|
12
|
+
Utils.parse_includes(klass, *includes).each do |name|
|
|
13
|
+
name = name.to_sym
|
|
14
|
+
self.includes.push(name) unless self.includes.include?(name)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def reset_includes!
|
|
19
|
+
@includes = []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def set_fields(type, field_names)
|
|
23
|
+
self.fields[type.to_sym] = field_names.map(&:to_sym)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def remove_fields(type)
|
|
27
|
+
self.fields.delete(type.to_sym)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def field_types
|
|
31
|
+
self.fields.keys
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def clear
|
|
35
|
+
reset_includes!
|
|
36
|
+
@fields = {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def to_params
|
|
40
|
+
return nil if field_types.empty? && includes.empty?
|
|
41
|
+
parsed_fields.merge(parsed_includes)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def parsed_includes
|
|
47
|
+
return {} if includes.empty?
|
|
48
|
+
{include: includes.join(",")}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def parsed_fields
|
|
52
|
+
return {} if field_types.empty?
|
|
53
|
+
{fields: fields.map { |type, names| [type, names.join(",")] }.to_h}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -12,10 +12,12 @@ module JsonApiClient
|
|
|
12
12
|
|
|
13
13
|
include Helpers::DynamicAttributes
|
|
14
14
|
include Helpers::Dirty
|
|
15
|
+
include Helpers::Associatable
|
|
15
16
|
|
|
16
17
|
attr_accessor :last_result_set,
|
|
17
18
|
:links,
|
|
18
|
-
:relationships
|
|
19
|
+
:relationships,
|
|
20
|
+
:request_params
|
|
19
21
|
class_attribute :site,
|
|
20
22
|
:primary_key,
|
|
21
23
|
:parser,
|
|
@@ -28,10 +30,21 @@ module JsonApiClient
|
|
|
28
30
|
:relationship_linker,
|
|
29
31
|
:read_only_attributes,
|
|
30
32
|
:requestor_class,
|
|
31
|
-
:associations,
|
|
32
33
|
:json_key_format,
|
|
33
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,
|
|
34
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
|
+
|
|
35
48
|
self.primary_key = :id
|
|
36
49
|
self.parser = Parsers::Parser
|
|
37
50
|
self.paginator = Paginating::Paginator
|
|
@@ -42,7 +55,12 @@ module JsonApiClient
|
|
|
42
55
|
self.relationship_linker = Relationships::Relations
|
|
43
56
|
self.read_only_attributes = [:id, :type, :links, :meta, :relationships]
|
|
44
57
|
self.requestor_class = Query::Requestor
|
|
45
|
-
self.
|
|
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
|
|
46
64
|
|
|
47
65
|
#:underscored_key, :camelized_key, :dasherized_key, or custom
|
|
48
66
|
self.json_key_format = :underscored_key
|
|
@@ -50,14 +68,15 @@ module JsonApiClient
|
|
|
50
68
|
#:underscored_route, :camelized_route, :dasherized_route, or custom
|
|
51
69
|
self.route_format = :underscored_route
|
|
52
70
|
|
|
53
|
-
include Associations::BelongsTo
|
|
54
|
-
include Associations::HasMany
|
|
55
|
-
include Associations::HasOne
|
|
56
|
-
|
|
57
71
|
class << self
|
|
58
72
|
extend Forwardable
|
|
59
73
|
def_delegators :_new_scope, :where, :order, :includes, :select, :all, :paginate, :page, :with_params, :first, :find, :last
|
|
60
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
|
+
|
|
61
80
|
# The table name for this resource. i.e. Article -> articles, Person -> people
|
|
62
81
|
#
|
|
63
82
|
# @return [String] The table name for this resource
|
|
@@ -80,6 +99,19 @@ module JsonApiClient
|
|
|
80
99
|
table_name
|
|
81
100
|
end
|
|
82
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
|
+
|
|
83
115
|
# Specifies the relative path that should be used for this resource;
|
|
84
116
|
# by default, this is inferred from the resource class name.
|
|
85
117
|
#
|
|
@@ -95,6 +127,7 @@ module JsonApiClient
|
|
|
95
127
|
new(params).tap do |resource|
|
|
96
128
|
resource.mark_as_persisted!
|
|
97
129
|
resource.clear_changes_information
|
|
130
|
+
resource.relationships.clear_changes_information
|
|
98
131
|
end
|
|
99
132
|
end
|
|
100
133
|
|
|
@@ -139,6 +172,12 @@ module JsonApiClient
|
|
|
139
172
|
end
|
|
140
173
|
end
|
|
141
174
|
|
|
175
|
+
def create!(attributes = {})
|
|
176
|
+
new(attributes).tap do |resource|
|
|
177
|
+
raise(Errors::RecordNotSaved.new("Failed to save the record", resource)) unless resource.save
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
142
181
|
# Within the given block, add these headers to all requests made by
|
|
143
182
|
# the resource class
|
|
144
183
|
#
|
|
@@ -156,7 +195,9 @@ module JsonApiClient
|
|
|
156
195
|
#
|
|
157
196
|
# @return [Hash] Headers
|
|
158
197
|
def custom_headers
|
|
159
|
-
_header_store.to_h
|
|
198
|
+
return _header_store.to_h if superclass == Object
|
|
199
|
+
|
|
200
|
+
superclass.custom_headers.merge(_header_store.to_h)
|
|
160
201
|
end
|
|
161
202
|
|
|
162
203
|
# Returns the requestor for this resource class
|
|
@@ -167,7 +208,7 @@ module JsonApiClient
|
|
|
167
208
|
end
|
|
168
209
|
|
|
169
210
|
# Default attributes that every instance of this resource should be
|
|
170
|
-
#
|
|
211
|
+
# initialized with. Optionally, override this method in a subclass.
|
|
171
212
|
#
|
|
172
213
|
# @return [Hash] Default attributes
|
|
173
214
|
def default_attributes
|
|
@@ -198,6 +239,11 @@ module JsonApiClient
|
|
|
198
239
|
# @option [Symbol] :on One of [:collection or :member] to decide whether it's a collect or member method
|
|
199
240
|
# @option [Symbol] :request_method The request method (:get, :post, etc)
|
|
200
241
|
def custom_endpoint(name, options = {})
|
|
242
|
+
if _immutable
|
|
243
|
+
request_method = options.fetch(:request_method, :get).to_sym
|
|
244
|
+
raise JsonApiClient::Errors::ResourceImmutableError if request_method != :get
|
|
245
|
+
end
|
|
246
|
+
|
|
201
247
|
if :collection == options.delete(:on)
|
|
202
248
|
collection_endpoint(name, options)
|
|
203
249
|
else
|
|
@@ -243,6 +289,12 @@ module JsonApiClient
|
|
|
243
289
|
# @option options [Symbol] :default The default value for the property
|
|
244
290
|
def property(name, options = {})
|
|
245
291
|
schema.add(name, options)
|
|
292
|
+
define_method(name) do
|
|
293
|
+
attributes[name]
|
|
294
|
+
end
|
|
295
|
+
define_method("#{name}=") do |value|
|
|
296
|
+
set_attribute(name, value)
|
|
297
|
+
end
|
|
246
298
|
end
|
|
247
299
|
|
|
248
300
|
# Declare multiple properties with the same optional options
|
|
@@ -272,7 +324,7 @@ module JsonApiClient
|
|
|
272
324
|
|
|
273
325
|
def _set_prefix_path(attrs)
|
|
274
326
|
paths = _belongs_to_associations.map do |a|
|
|
275
|
-
a.set_prefix_path(attrs, route_formatter)
|
|
327
|
+
a.set_prefix_path(attrs, route_formatter)
|
|
276
328
|
end
|
|
277
329
|
|
|
278
330
|
paths.join("/")
|
|
@@ -302,20 +354,23 @@ module JsonApiClient
|
|
|
302
354
|
#
|
|
303
355
|
# @param params [Hash] Attributes, links, and relationships
|
|
304
356
|
def initialize(params = {})
|
|
357
|
+
params = params.with_indifferent_access
|
|
305
358
|
@persisted = nil
|
|
306
|
-
|
|
307
|
-
self.
|
|
308
|
-
self.
|
|
359
|
+
@destroyed = nil
|
|
360
|
+
self.links = self.class.linker.new(params.delete(:links) || {})
|
|
361
|
+
self.relationships = self.class.relationship_linker.new(self.class, params.delete(:relationships) || {})
|
|
362
|
+
self.attributes = self.class.default_attributes.merge params.except(*self.class.prefix_params)
|
|
363
|
+
self.forget_change!(:type)
|
|
364
|
+
self.__belongs_to_params = params.slice(*self.class.prefix_params)
|
|
309
365
|
|
|
310
|
-
|
|
311
|
-
attributes[property.name] = property.default unless attributes.has_key?(property.name) || property.default.nil?
|
|
312
|
-
end
|
|
366
|
+
setup_default_properties
|
|
313
367
|
|
|
314
368
|
self.class.associations.each do |association|
|
|
315
369
|
if params.has_key?(association.attr_name.to_s)
|
|
316
370
|
set_attribute(association.attr_name, params[association.attr_name.to_s])
|
|
317
371
|
end
|
|
318
372
|
end
|
|
373
|
+
self.request_params = self.class.request_params_class.new(self.class)
|
|
319
374
|
end
|
|
320
375
|
|
|
321
376
|
# Set the current attributes and try to save them
|
|
@@ -327,6 +382,11 @@ module JsonApiClient
|
|
|
327
382
|
save
|
|
328
383
|
end
|
|
329
384
|
|
|
385
|
+
def update_attributes!(attrs = {})
|
|
386
|
+
self.attributes = attrs
|
|
387
|
+
save ? true : raise(Errors::RecordNotSaved.new("Failed to update the record", self))
|
|
388
|
+
end
|
|
389
|
+
|
|
330
390
|
# Alias to update_attributes
|
|
331
391
|
#
|
|
332
392
|
# @param attrs [Hash] Attributes to update
|
|
@@ -335,6 +395,10 @@ module JsonApiClient
|
|
|
335
395
|
update_attributes(attrs)
|
|
336
396
|
end
|
|
337
397
|
|
|
398
|
+
def update!(attrs = {})
|
|
399
|
+
update_attributes!(attrs)
|
|
400
|
+
end
|
|
401
|
+
|
|
338
402
|
# Mark the record as persisted
|
|
339
403
|
def mark_as_persisted!
|
|
340
404
|
@persisted = true
|
|
@@ -344,14 +408,26 @@ module JsonApiClient
|
|
|
344
408
|
#
|
|
345
409
|
# @return [Boolean]
|
|
346
410
|
def persisted?
|
|
347
|
-
!!@persisted && has_attribute?(self.class.primary_key)
|
|
411
|
+
!!@persisted && !destroyed? && has_attribute?(self.class.primary_key)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Mark the record as destroyed
|
|
415
|
+
def mark_as_destroyed!
|
|
416
|
+
@destroyed = true
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Whether or not this record has been destroyed to the database previously
|
|
420
|
+
#
|
|
421
|
+
# @return [Boolean]
|
|
422
|
+
def destroyed?
|
|
423
|
+
!!@destroyed
|
|
348
424
|
end
|
|
349
425
|
|
|
350
426
|
# Returns true if this is a new record (never persisted to the database)
|
|
351
427
|
#
|
|
352
428
|
# @return [Boolean]
|
|
353
429
|
def new_record?
|
|
354
|
-
!persisted?
|
|
430
|
+
!persisted? && !destroyed?
|
|
355
431
|
end
|
|
356
432
|
|
|
357
433
|
# When we represent this resource as a relationship, we do so with id & type
|
|
@@ -366,7 +442,7 @@ module JsonApiClient
|
|
|
366
442
|
#
|
|
367
443
|
# @return [Hash] Representation of this object as JSONAPI object
|
|
368
444
|
def as_json_api(*)
|
|
369
|
-
attributes.slice(
|
|
445
|
+
attributes.slice(self.class.primary_key, :type).tap do |h|
|
|
370
446
|
relationships_for_serialization.tap do |r|
|
|
371
447
|
h[:relationships] = self.class.key_formatter.format_keys(r) unless r.empty?
|
|
372
448
|
end
|
|
@@ -375,11 +451,11 @@ module JsonApiClient
|
|
|
375
451
|
end
|
|
376
452
|
|
|
377
453
|
def as_json(*)
|
|
378
|
-
attributes.slice(
|
|
454
|
+
attributes.slice(self.class.primary_key, :type).tap do |h|
|
|
379
455
|
relationships.as_json.tap do |r|
|
|
380
456
|
h[:relationships] = r unless r.empty?
|
|
381
457
|
end
|
|
382
|
-
h[:attributes] = attributes.except(
|
|
458
|
+
h[:attributes] = attributes.except(self.class.primary_key, :type).as_json
|
|
383
459
|
end
|
|
384
460
|
end
|
|
385
461
|
|
|
@@ -402,6 +478,7 @@ module JsonApiClient
|
|
|
402
478
|
# @return [Boolean] Whether or not the save succeeded
|
|
403
479
|
def save
|
|
404
480
|
return false unless valid?
|
|
481
|
+
raise JsonApiClient::Errors::ResourceImmutableError if _immutable
|
|
405
482
|
|
|
406
483
|
self.last_result_set = if persisted?
|
|
407
484
|
self.class.requestor.update(self)
|
|
@@ -410,22 +487,19 @@ module JsonApiClient
|
|
|
410
487
|
end
|
|
411
488
|
|
|
412
489
|
if last_result_set.has_errors?
|
|
413
|
-
|
|
414
|
-
if error.source_parameter
|
|
415
|
-
errors.add(self.class.key_formatter.unformat(error.source_parameter), error.title || error.detail)
|
|
416
|
-
else
|
|
417
|
-
errors.add(:base, error.title || error.detail)
|
|
418
|
-
end
|
|
419
|
-
end
|
|
490
|
+
fill_errors
|
|
420
491
|
false
|
|
421
492
|
else
|
|
422
493
|
self.errors.clear if self.errors
|
|
494
|
+
self.request_params.clear unless self.class.keep_request_params
|
|
423
495
|
mark_as_persisted!
|
|
424
496
|
if updated = last_result_set.first
|
|
425
497
|
self.attributes = updated.attributes
|
|
426
498
|
self.links.attributes = updated.links.attributes
|
|
427
499
|
self.relationships.attributes = updated.relationships.attributes
|
|
428
500
|
clear_changes_information
|
|
501
|
+
self.relationships.clear_changes_information
|
|
502
|
+
_clear_cached_relationships
|
|
429
503
|
end
|
|
430
504
|
true
|
|
431
505
|
end
|
|
@@ -435,12 +509,17 @@ module JsonApiClient
|
|
|
435
509
|
#
|
|
436
510
|
# @return [Boolean] Whether or not the destroy succeeded
|
|
437
511
|
def destroy
|
|
512
|
+
raise JsonApiClient::Errors::ResourceImmutableError if _immutable
|
|
513
|
+
|
|
438
514
|
self.last_result_set = self.class.requestor.destroy(self)
|
|
439
|
-
if
|
|
440
|
-
|
|
441
|
-
true
|
|
442
|
-
else
|
|
515
|
+
if last_result_set.has_errors?
|
|
516
|
+
fill_errors
|
|
443
517
|
false
|
|
518
|
+
else
|
|
519
|
+
mark_as_destroyed!
|
|
520
|
+
_clear_cached_relationships
|
|
521
|
+
_clear_belongs_to_params
|
|
522
|
+
true
|
|
444
523
|
end
|
|
445
524
|
end
|
|
446
525
|
|
|
@@ -448,27 +527,83 @@ module JsonApiClient
|
|
|
448
527
|
"#<#{self.class.name}:@attributes=#{attributes.inspect}>"
|
|
449
528
|
end
|
|
450
529
|
|
|
451
|
-
|
|
530
|
+
def request_includes(*includes)
|
|
531
|
+
self.request_params.add_includes(includes)
|
|
532
|
+
self
|
|
533
|
+
end
|
|
452
534
|
|
|
453
|
-
def
|
|
454
|
-
|
|
535
|
+
def reset_request_includes!
|
|
536
|
+
self.request_params.reset_includes!
|
|
537
|
+
self
|
|
538
|
+
end
|
|
455
539
|
|
|
456
|
-
|
|
540
|
+
def request_select(*fields)
|
|
541
|
+
fields_by_type = fields.extract_options!
|
|
542
|
+
fields_by_type[type.to_sym] = fields if fields.any?
|
|
543
|
+
fields_by_type.each do |field_type, field_names|
|
|
544
|
+
self.request_params.set_fields(field_type, field_names)
|
|
545
|
+
end
|
|
546
|
+
self
|
|
547
|
+
end
|
|
457
548
|
|
|
458
|
-
|
|
549
|
+
def reset_request_select!(*resource_types)
|
|
550
|
+
resource_types = self.request_params.field_types if resource_types.empty?
|
|
551
|
+
resource_types.each { |resource_type| self.request_params.remove_fields(resource_type) }
|
|
552
|
+
self
|
|
553
|
+
end
|
|
459
554
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
555
|
+
def path_attributes
|
|
556
|
+
_belongs_to_params.merge attributes.slice( self.class.primary_key ).with_indifferent_access
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
protected
|
|
560
|
+
|
|
561
|
+
def setup_default_properties
|
|
562
|
+
self.class.schema.each_property do |property|
|
|
563
|
+
unless attributes.has_key?(property.name) || property.default.nil?
|
|
564
|
+
attribute_will_change!(property.name) if add_defaults_to_changes
|
|
565
|
+
attributes[property.name] = property.default
|
|
566
|
+
end
|
|
463
567
|
end
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
def relationship_definition_for(name)
|
|
571
|
+
relationships[name] if relationships && relationships.has_attribute?(name)
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
def included_data_for(name, relationship_definition)
|
|
575
|
+
last_result_set.included.data_for(name, relationship_definition)
|
|
576
|
+
end
|
|
464
577
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
578
|
+
def relationship_data_for(name, relationship_definition)
|
|
579
|
+
# look in included data
|
|
580
|
+
if relationship_definition.key?("data")
|
|
581
|
+
if relationships.attribute_changed?(name)
|
|
582
|
+
return relation_objects_for(name, relationship_definition)
|
|
583
|
+
else
|
|
584
|
+
return included_data_for(name, relationship_definition)
|
|
469
585
|
end
|
|
470
586
|
end
|
|
471
|
-
|
|
587
|
+
|
|
588
|
+
return unless links = relationship_definition["links"]
|
|
589
|
+
return unless url = links["related"]
|
|
590
|
+
|
|
591
|
+
association_for(name).data(url)
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def relation_objects_for(name, relationship_definition)
|
|
595
|
+
data = relationship_definition["data"]
|
|
596
|
+
assoc = association_for(name)
|
|
597
|
+
return if data.nil? || assoc.nil?
|
|
598
|
+
assoc.load_records(data)
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def method_missing(method, *args)
|
|
602
|
+
relationship_definition = relationship_definition_for(method)
|
|
603
|
+
|
|
604
|
+
return super unless relationship_definition
|
|
605
|
+
|
|
606
|
+
relationship_data_for(method, relationship_definition)
|
|
472
607
|
end
|
|
473
608
|
|
|
474
609
|
def respond_to_missing?(symbol, include_all = false)
|
|
@@ -497,12 +632,27 @@ module JsonApiClient
|
|
|
497
632
|
end
|
|
498
633
|
end
|
|
499
634
|
|
|
635
|
+
def non_serializing_attributes
|
|
636
|
+
self.class.read_only_attributes
|
|
637
|
+
end
|
|
638
|
+
|
|
500
639
|
def attributes_for_serialization
|
|
501
|
-
attributes.except(*
|
|
640
|
+
attributes.except(*non_serializing_attributes).slice(*changed)
|
|
502
641
|
end
|
|
503
642
|
|
|
504
643
|
def relationships_for_serialization
|
|
505
644
|
relationships.as_json_api
|
|
506
645
|
end
|
|
646
|
+
|
|
647
|
+
def error_message_for(error)
|
|
648
|
+
error.error_msg
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
def fill_errors
|
|
652
|
+
last_result_set.errors.each do |error|
|
|
653
|
+
key = self.class.key_formatter.unformat(error.error_key)
|
|
654
|
+
errors.add(key, error_message_for(error))
|
|
655
|
+
end
|
|
656
|
+
end
|
|
507
657
|
end
|
|
508
658
|
end
|
|
@@ -29,7 +29,7 @@ module JsonApiClient
|
|
|
29
29
|
|
|
30
30
|
class Decimal
|
|
31
31
|
def self.cast(value, _)
|
|
32
|
-
BigDecimal
|
|
32
|
+
BigDecimal(value)
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -67,11 +67,11 @@ module JsonApiClient
|
|
|
67
67
|
# end
|
|
68
68
|
# end
|
|
69
69
|
#
|
|
70
|
-
# JsonApiClient::Schema::
|
|
70
|
+
# JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster
|
|
71
71
|
#
|
|
72
72
|
# You can setup several at once:
|
|
73
73
|
#
|
|
74
|
-
# JsonApiClient::Schema::
|
|
74
|
+
# JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster,
|
|
75
75
|
# date: MyJsonDateTypeCaster
|
|
76
76
|
#
|
|
77
77
|
#
|
|
@@ -2,10 +2,16 @@ module JsonApiClient
|
|
|
2
2
|
module Utils
|
|
3
3
|
|
|
4
4
|
def self.compute_type(klass, type_name)
|
|
5
|
+
return klass.custom_type_to_class.fetch(type_name).constantize if klass.custom_type_to_class.key?(type_name)
|
|
5
6
|
# If the type is prefixed with a scope operator then we assume that
|
|
6
7
|
# the type_name is an absolute reference.
|
|
7
8
|
return type_name.constantize if type_name.match(/^::/)
|
|
8
9
|
|
|
10
|
+
# Check the klass association definitions
|
|
11
|
+
association_klass_match = klass.associations.find { |a| a.attr_name.to_s.singularize == type_name.underscore }
|
|
12
|
+
association_klass = association_klass_match.options[:class] if association_klass_match
|
|
13
|
+
return association_klass if association_klass
|
|
14
|
+
|
|
9
15
|
# Build a list of candidates to search for
|
|
10
16
|
candidates = []
|
|
11
17
|
klass.name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
|
|
@@ -24,5 +30,24 @@ module JsonApiClient
|
|
|
24
30
|
raise NameError, "uninitialized constant #{candidates.first}"
|
|
25
31
|
end
|
|
26
32
|
|
|
33
|
+
def self.parse_includes(klass, *tables)
|
|
34
|
+
tables.map do |table|
|
|
35
|
+
case table
|
|
36
|
+
when Hash
|
|
37
|
+
table.map do |k, v|
|
|
38
|
+
parse_includes(klass, *v).map do |sub|
|
|
39
|
+
"#{k}.#{sub}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
when Array
|
|
43
|
+
table.map do |v|
|
|
44
|
+
parse_includes(klass, *v)
|
|
45
|
+
end
|
|
46
|
+
else
|
|
47
|
+
klass.key_formatter.format(table)
|
|
48
|
+
end
|
|
49
|
+
end.flatten
|
|
50
|
+
end
|
|
51
|
+
|
|
27
52
|
end
|
|
28
|
-
end
|
|
53
|
+
end
|
data/lib/json_api_client.rb
CHANGED
|
@@ -2,6 +2,7 @@ require 'faraday'
|
|
|
2
2
|
require 'faraday_middleware'
|
|
3
3
|
require 'json'
|
|
4
4
|
require 'addressable/uri'
|
|
5
|
+
require 'json_api_client/formatter'
|
|
5
6
|
|
|
6
7
|
module JsonApiClient
|
|
7
8
|
autoload :Associations, 'json_api_client/associations'
|
|
@@ -9,7 +10,6 @@ module JsonApiClient
|
|
|
9
10
|
autoload :Connection, 'json_api_client/connection'
|
|
10
11
|
autoload :Errors, 'json_api_client/errors'
|
|
11
12
|
autoload :ErrorCollector, 'json_api_client/error_collector'
|
|
12
|
-
autoload :Formatter, 'json_api_client/formatter'
|
|
13
13
|
autoload :Helpers, 'json_api_client/helpers'
|
|
14
14
|
autoload :Implementation, 'json_api_client/implementation'
|
|
15
15
|
autoload :IncludedData, 'json_api_client/included_data'
|
|
@@ -21,6 +21,7 @@ module JsonApiClient
|
|
|
21
21
|
autoload :Paginating, 'json_api_client/paginating'
|
|
22
22
|
autoload :Parsers, 'json_api_client/parsers'
|
|
23
23
|
autoload :Query, 'json_api_client/query'
|
|
24
|
+
autoload :RequestParams, 'json_api_client/request_params'
|
|
24
25
|
autoload :Resource, 'json_api_client/resource'
|
|
25
26
|
autoload :ResultSet, 'json_api_client/result_set'
|
|
26
27
|
autoload :Schema, 'json_api_client/schema'
|