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,9 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Helpers
|
3
|
+
autoload :Callbacks, 'json_api_client/helpers/callbacks'
|
4
|
+
autoload :Dirty, 'json_api_client/helpers/dirty'
|
5
|
+
autoload :DynamicAttributes, 'json_api_client/helpers/dynamic_attributes'
|
6
|
+
autoload :URI, 'json_api_client/helpers/uri'
|
7
|
+
autoload :Associatable, 'json_api_client/helpers/associatable'
|
8
|
+
end
|
9
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Helpers
|
3
|
+
module Associatable
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :associations, instance_accessor: false
|
8
|
+
self.associations = []
|
9
|
+
attr_accessor :__cached_associations
|
10
|
+
attr_accessor :__belongs_to_params
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def _define_association(attr_name, association_klass, options = {})
|
15
|
+
attr_name = attr_name.to_sym
|
16
|
+
association = association_klass.new(attr_name, self, options)
|
17
|
+
self.associations += [association]
|
18
|
+
end
|
19
|
+
|
20
|
+
def _define_relationship_methods(attr_name)
|
21
|
+
attr_name = attr_name.to_sym
|
22
|
+
|
23
|
+
define_method(attr_name) do
|
24
|
+
_cached_relationship(attr_name) do
|
25
|
+
relationship_definition = relationship_definition_for(attr_name)
|
26
|
+
return unless relationship_definition
|
27
|
+
relationship_data_for(attr_name, relationship_definition)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method("#{attr_name}=") do |value|
|
32
|
+
_clear_cached_relationship(attr_name)
|
33
|
+
relationships.public_send("#{attr_name}=", value)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def belongs_to(attr_name, options = {})
|
38
|
+
_define_association(attr_name, JsonApiClient::Associations::BelongsTo::Association, options)
|
39
|
+
|
40
|
+
param = associations.last.param
|
41
|
+
define_method(param) do
|
42
|
+
_belongs_to_params[param]
|
43
|
+
end
|
44
|
+
|
45
|
+
define_method(:"#{param}=") do |value|
|
46
|
+
_belongs_to_params[param] = value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def has_many(attr_name, options = {})
|
51
|
+
_define_association(attr_name, JsonApiClient::Associations::HasMany::Association, options)
|
52
|
+
_define_relationship_methods(attr_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def has_one(attr_name, options = {})
|
56
|
+
_define_association(attr_name, JsonApiClient::Associations::HasOne::Association, options)
|
57
|
+
_define_relationship_methods(attr_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def _belongs_to_params
|
62
|
+
self.__belongs_to_params ||= {}
|
63
|
+
end
|
64
|
+
|
65
|
+
def _clear_belongs_to_params
|
66
|
+
self.__belongs_to_params = {}
|
67
|
+
end
|
68
|
+
|
69
|
+
def _cached_associations
|
70
|
+
self.__cached_associations ||= {}
|
71
|
+
end
|
72
|
+
|
73
|
+
def _clear_cached_relationships
|
74
|
+
self.__cached_associations = {}
|
75
|
+
end
|
76
|
+
|
77
|
+
def _clear_cached_relationship(attr_name)
|
78
|
+
_cached_associations.delete(attr_name)
|
79
|
+
end
|
80
|
+
|
81
|
+
def _cached_relationship(attr_name)
|
82
|
+
return _cached_associations[attr_name] if _cached_associations.has_key?(attr_name)
|
83
|
+
_cached_associations[attr_name] = yield
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Helpers
|
3
|
+
module Callbacks
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
extend ActiveModel::Callbacks
|
8
|
+
define_model_callbacks :save, :destroy, :create, :update
|
9
|
+
end
|
10
|
+
|
11
|
+
def save
|
12
|
+
run_callbacks :save do
|
13
|
+
run_callbacks (persisted? ? :update : :create) do
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def destroy
|
20
|
+
run_callbacks :destroy do
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Helpers
|
3
|
+
module Dirty
|
4
|
+
|
5
|
+
def changed?
|
6
|
+
changed_attributes.present?
|
7
|
+
end
|
8
|
+
|
9
|
+
def changed
|
10
|
+
changed_attributes.keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def changed_attributes
|
14
|
+
@changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def clear_changes_information
|
18
|
+
@changed_attributes = ActiveSupport::HashWithIndifferentAccess.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def forget_change!(attr)
|
22
|
+
@changed_attributes.delete(attr.to_s)
|
23
|
+
end
|
24
|
+
|
25
|
+
def set_all_attributes_dirty
|
26
|
+
attributes.each do |k, v|
|
27
|
+
set_attribute_was(k, v)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def attribute_will_change!(attr)
|
32
|
+
return if attribute_changed?(attr)
|
33
|
+
set_attribute_was(attr, attributes[attr])
|
34
|
+
end
|
35
|
+
|
36
|
+
def set_attribute_was(attr, value)
|
37
|
+
begin
|
38
|
+
value = value.duplicable? ? value.clone : value
|
39
|
+
changed_attributes[attr] = value
|
40
|
+
rescue TypeError, NoMethodError
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def attribute_was(attr) # :nodoc:
|
45
|
+
attribute_changed?(attr) ? changed_attributes[attr] : attributes[attr]
|
46
|
+
end
|
47
|
+
|
48
|
+
def attribute_changed?(attr)
|
49
|
+
changed.include?(attr.to_s)
|
50
|
+
end
|
51
|
+
|
52
|
+
def attribute_change(attr)
|
53
|
+
[changed_attributes[attr], attributes[attr]] if attribute_changed?(attr)
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
def method_missing(method, *args, &block)
|
59
|
+
if method.to_s =~ /^(.*)_changed\?$/
|
60
|
+
has_attribute?($1) ? attribute_changed?($1) : nil
|
61
|
+
elsif method.to_s =~ /^(.*)_was$/
|
62
|
+
has_attribute?($1) ? attribute_was($1) : nil
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def set_attribute(name, value)
|
69
|
+
attribute_will_change!(name) if value != attributes[name] || !attributes.has_key?(name)
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Helpers
|
3
|
+
module DynamicAttributes
|
4
|
+
|
5
|
+
def attributes
|
6
|
+
@attributes
|
7
|
+
end
|
8
|
+
|
9
|
+
def attributes=(attrs = {})
|
10
|
+
@attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
11
|
+
|
12
|
+
return @attributes unless attrs.present?
|
13
|
+
attrs.each do |key, value|
|
14
|
+
send("#{key}=", value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
read_attribute(key)
|
20
|
+
end
|
21
|
+
|
22
|
+
def []=(key, value)
|
23
|
+
set_attribute(key, value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond_to_missing?(method, include_private = false)
|
27
|
+
if has_attribute?(method) || method.to_s.end_with?('=')
|
28
|
+
true
|
29
|
+
else
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def has_attribute?(attr_name)
|
35
|
+
attributes.has_key?(attr_name)
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def method_missing(method, *args, &block)
|
41
|
+
if has_attribute?(method)
|
42
|
+
return attributes[method]
|
43
|
+
end
|
44
|
+
|
45
|
+
normalized_method = safe_key_formatter.unformat(method.to_s)
|
46
|
+
|
47
|
+
if normalized_method.end_with?('=')
|
48
|
+
set_attribute(normalized_method[0..-2], args.first)
|
49
|
+
else
|
50
|
+
super
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def read_attribute(name)
|
55
|
+
attributes.fetch(name, nil)
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_attribute(name, value)
|
59
|
+
attributes[name] = value
|
60
|
+
end
|
61
|
+
|
62
|
+
def safe_key_formatter
|
63
|
+
@safe_key_formatter ||= (key_formatter || DefaultKeyFormatter.new)
|
64
|
+
end
|
65
|
+
|
66
|
+
def key_formatter
|
67
|
+
self.class.respond_to?(:key_formatter) && self.class.key_formatter
|
68
|
+
end
|
69
|
+
|
70
|
+
class DefaultKeyFormatter
|
71
|
+
def unformat(method)
|
72
|
+
method.to_s
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
class Implementation
|
3
|
+
attr_reader :version, :meta
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
# If the version member is not present, clients should assume the server implements at least version 1.0 of the specification.
|
7
|
+
@version = data.fetch("version", "1.0")
|
8
|
+
|
9
|
+
@meta = MetaData.new(data.fetch("meta", {}))
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
class IncludedData
|
3
|
+
attr_reader :data
|
4
|
+
|
5
|
+
def initialize(result_set, data)
|
6
|
+
record_class = result_set.record_class
|
7
|
+
grouped_data = data.group_by{|datum| datum["type"]}
|
8
|
+
grouped_included_set = grouped_data.each_with_object({}) do |(type, records), h|
|
9
|
+
klass = Utils.compute_type(record_class, record_class.key_formatter.unformat(type).singularize.classify)
|
10
|
+
h[type] = records.map do |record|
|
11
|
+
params = klass.parser.parameters_from_resource(record)
|
12
|
+
klass.load(params).tap do |resource|
|
13
|
+
resource.last_result_set = result_set
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
if record_class.search_included_in_result_set
|
19
|
+
# deep_merge overrides the nested Arrays o_O
|
20
|
+
# {a: [1,2]}.deep_merge(a: [3,4]) # => {a: [3,4]}
|
21
|
+
grouped_included_set.merge!(result_set.group_by(&:type)) do |_, resources1, resources2|
|
22
|
+
resources1 + resources2
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
grouped_included_set.each do |type, resources|
|
27
|
+
grouped_included_set[type] = resources.index_by(&:id)
|
28
|
+
end
|
29
|
+
|
30
|
+
@data = grouped_included_set
|
31
|
+
end
|
32
|
+
|
33
|
+
def data_for(method_name, definition)
|
34
|
+
# If data is defined, pull the record from the included data
|
35
|
+
return nil unless data = definition["data"]
|
36
|
+
|
37
|
+
if data.is_a?(Array)
|
38
|
+
# has_many link
|
39
|
+
data.map(&method(:record_for)).compact
|
40
|
+
else
|
41
|
+
# has_one link
|
42
|
+
record_for(data)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def has_link?(name)
|
47
|
+
data.has_key?(name.to_s)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# should return a resource record of some type for this linked document
|
53
|
+
def record_for(link_def)
|
54
|
+
record = data[link_def["type"]]
|
55
|
+
record[link_def["id"]] if record
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Linking
|
3
|
+
class Links
|
4
|
+
include Helpers::DynamicAttributes
|
5
|
+
|
6
|
+
def initialize(links)
|
7
|
+
self.attributes = links
|
8
|
+
end
|
9
|
+
|
10
|
+
def present?
|
11
|
+
attributes.present?
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def set_attribute(name, value)
|
17
|
+
attributes[name] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
module Linking
|
3
|
+
class TopLevelLinks
|
4
|
+
|
5
|
+
attr_reader :links, :record_class
|
6
|
+
|
7
|
+
def initialize(record_class, links)
|
8
|
+
@links = links
|
9
|
+
@record_class = record_class
|
10
|
+
end
|
11
|
+
|
12
|
+
def respond_to_missing?(method, include_private = false)
|
13
|
+
links.has_key?(method.to_s) || super
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method, *args)
|
17
|
+
if respond_to_missing?(method)
|
18
|
+
fetch_link(method)
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def link_url_for(link_name)
|
25
|
+
link_definition = links.fetch(link_name.to_s)
|
26
|
+
if link_definition.is_a?(Hash)
|
27
|
+
link_definition["href"]
|
28
|
+
else
|
29
|
+
link_definition
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch_link(link_name)
|
34
|
+
return unless respond_to_missing?(link_name)
|
35
|
+
record_class.requestor.linked(link_url_for(link_name))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module JsonApiClient
|
2
|
+
class MetaData
|
3
|
+
include Helpers::DynamicAttributes
|
4
|
+
|
5
|
+
attr_accessor :record_class
|
6
|
+
|
7
|
+
def initialize(data, record_class = nil)
|
8
|
+
self.record_class = record_class
|
9
|
+
self.attributes = data
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
|
14
|
+
def key_formatter
|
15
|
+
record_class && record_class.key_formatter
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|