carwow-json_api_client 1.19.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|