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.
@@ -10,37 +10,44 @@ module JsonApiClient
10
10
 
11
11
  # expects a record
12
12
  def create(record)
13
- request(:post, klass.path(record.attributes), {
14
- data: record.as_json_api
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(:patch, resource_path(record.attributes), {
20
- data: record.as_json_api
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.attributes), {})
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
- request(options.fetch(:request_method, :get), path, params)
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
- klass.parser.parse(klass, connection.run(type, path, params, klass.custom_headers))
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
@@ -11,7 +11,6 @@ module JsonApiClient
11
11
  def initialize(record_class, relations)
12
12
  @record_class = record_class
13
13
  self.attributes = relations
14
- clear_changes_information
15
14
  end
16
15
 
17
16
  def present?
@@ -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.associations = []
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
- # intialized with. Optionally, override this method in a subclass.
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
- self.links = self.class.linker.new(params.delete("links") || {})
307
- self.relationships = self.class.relationship_linker.new(self.class, params.delete("relationships") || {})
308
- self.attributes = params.merge(self.class.default_attributes)
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
- self.class.schema.each_property do |property|
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(:id, :type).tap do |h|
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(:id, :type).tap do |h|
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(:id, :type).as_json
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
- last_result_set.errors.each do |error|
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 !last_result_set.has_errors?
440
- self.attributes.clear
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
- protected
530
+ def request_includes(*includes)
531
+ self.request_params.add_includes(includes)
532
+ self
533
+ end
452
534
 
453
- def method_missing(method, *args)
454
- association = association_for(method)
535
+ def reset_request_includes!
536
+ self.request_params.reset_includes!
537
+ self
538
+ end
455
539
 
456
- return super unless association || (relationships && relationships.has_attribute?(method))
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
- return nil unless relationship_definitions = relationships[method]
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
- # look in included data
461
- if relationship_definitions.key?("data")
462
- return last_result_set.included.data_for(method, relationship_definitions)
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
- if association = association_for(method)
466
- # look for a defined relationship url
467
- if relationship_definitions["links"] && url = relationship_definitions["links"]["related"]
468
- return association.data(url)
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
- nil
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(*self.class.read_only_attributes).slice(*changed)
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.new(value)
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::Types.register money: MyMoneyCaster
70
+ # JsonApiClient::Schema::TypeFactory.register money: MyMoneyCaster
71
71
  #
72
72
  # You can setup several at once:
73
73
  #
74
- # JsonApiClient::Schema::Types.register money: MyMoneyCaster,
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
@@ -1,3 +1,3 @@
1
1
  module JsonApiClient
2
- VERSION = "1.5.2"
2
+ VERSION = "1.22.0"
3
3
  end
@@ -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'