graphiti-activegraph 1.3.1 → 1.3.3
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 +4 -4
- data/.github/workflows/specs.yml +58 -59
- data/.gitignore +59 -59
- data/.hound.yml +4 -0
- data/.rspec +1 -1
- data/.rubocop.yml +6 -0
- data/CHANGELOG.md +71 -54
- data/CHANGELOG_PRE_1.0.0.md +70 -70
- data/Gemfile +3 -3
- data/LICENSE.txt +21 -21
- data/README.md +130 -130
- data/docs/deserializer.md +40 -40
- data/graphiti-activegraph.gemspec +35 -34
- data/lib/graphiti/active_graph/adapters/active_graph/function_sideload.rb +7 -7
- data/lib/graphiti/active_graph/adapters/active_graph/has_many_sideload.rb +7 -7
- data/lib/graphiti/active_graph/adapters/active_graph/has_one_sideload.rb +7 -7
- data/lib/graphiti/active_graph/adapters/active_graph/polymorphic_belongs_to.rb +11 -11
- data/lib/graphiti/active_graph/adapters/active_graph/sideload.rb +26 -26
- data/lib/graphiti/active_graph/adapters/active_graph.rb +183 -183
- data/lib/graphiti/active_graph/concerns/path_relationships.rb +44 -44
- data/lib/graphiti/active_graph/concerns/relationships.rb +15 -15
- data/lib/graphiti/active_graph/deserializer.rb +138 -138
- data/lib/graphiti/active_graph/extensions/context.rb +17 -17
- data/lib/graphiti/active_graph/extensions/grouping/params.rb +101 -52
- data/lib/graphiti/active_graph/extensions/query_dsl/performer.rb +38 -38
- data/lib/graphiti/active_graph/extensions/query_dsl/query_generator.rb +20 -20
- data/lib/graphiti/active_graph/extensions/query_params.rb +27 -27
- data/lib/graphiti/active_graph/extensions/resources/authorizationable.rb +29 -29
- data/lib/graphiti/active_graph/extensions/resources/payload_combinable.rb +24 -24
- data/lib/graphiti/active_graph/extensions/resources/preloadable.rb +19 -19
- data/lib/graphiti/active_graph/extensions/resources/rel.rb +19 -19
- data/lib/graphiti/active_graph/jsonapi_ext/include_directive.rb +66 -66
- data/lib/graphiti/active_graph/jsonapi_ext/serializable/resource_ext.rb +8 -8
- data/lib/graphiti/active_graph/query.rb +76 -76
- data/lib/graphiti/active_graph/request_validators/validator.rb +9 -9
- data/lib/graphiti/active_graph/resource.rb +103 -103
- data/lib/graphiti/active_graph/resource_proxy.rb +86 -86
- data/lib/graphiti/active_graph/resources/interface.rb +14 -14
- data/lib/graphiti/active_graph/resources/persistence.rb +25 -25
- data/lib/graphiti/active_graph/runner.rb +39 -39
- data/lib/graphiti/active_graph/scope.rb +28 -28
- data/lib/graphiti/active_graph/scoping/association_eager_load.rb +35 -34
- data/lib/graphiti/active_graph/scoping/filter.rb +49 -49
- data/lib/graphiti/active_graph/scoping/filterable.rb +12 -12
- data/lib/graphiti/active_graph/scoping/include.rb +48 -48
- data/lib/graphiti/active_graph/scoping/internal/extra_field_normalizer.rb +76 -76
- data/lib/graphiti/active_graph/scoping/internal/include_normalizer.rb +82 -82
- data/lib/graphiti/active_graph/scoping/internal/path_descriptor.rb +94 -94
- data/lib/graphiti/active_graph/scoping/internal/sort_normalizer.rb +54 -54
- data/lib/graphiti/active_graph/scoping/internal/sorting_aliases.rb +35 -35
- data/lib/graphiti/active_graph/scoping/internal/sparse_fields_eagerloading.rb +28 -28
- data/lib/graphiti/active_graph/serializer.rb +15 -15
- data/lib/graphiti/active_graph/sideload_resolve.rb +119 -119
- data/lib/graphiti/active_graph/util/parsers/rel_chain.rb +27 -27
- data/lib/graphiti/active_graph/util/relationship_payload.rb +33 -33
- data/lib/graphiti/active_graph/util/serializer_attribute.rb +17 -17
- data/lib/graphiti/active_graph/util/serializer_relationship.rb +28 -28
- data/lib/graphiti/active_graph/util/transformers/relation_param.rb +56 -56
- data/lib/graphiti/active_graph/version.rb +5 -5
- data/lib/graphiti/sidepost_configuration.rb +9 -9
- data/lib/graphiti-activegraph.rb +43 -43
- metadata +21 -5
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
module Graphiti::ActiveGraph
|
|
2
|
-
module Scoping
|
|
3
|
-
module Filter
|
|
4
|
-
include Filterable
|
|
5
|
-
include Internal::SortingAliases
|
|
6
|
-
include Extensions::QueryDsl::Performer
|
|
7
|
-
|
|
8
|
-
attr_reader :scope
|
|
9
|
-
|
|
10
|
-
def apply
|
|
11
|
-
super
|
|
12
|
-
apply_query_dsl
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def each_filter
|
|
16
|
-
filter_param.each_pair do |param_name, param_value|
|
|
17
|
-
filter = find_filter!(param_name)
|
|
18
|
-
|
|
19
|
-
normalize_param(filter, param_value).each do |operator, value|
|
|
20
|
-
operator = operator.to_s.gsub("!", "not_").to_sym
|
|
21
|
-
|
|
22
|
-
# dynamic filters errors for validating and typecasting value below
|
|
23
|
-
# so they are skipped here without validation or typecast
|
|
24
|
-
filter_map = filter.values[0]
|
|
25
|
-
if filter_map[:dynamic_filter]
|
|
26
|
-
yield filter, operator, value
|
|
27
|
-
next
|
|
28
|
-
end
|
|
29
|
-
validate_operator(filter, operator)
|
|
30
|
-
|
|
31
|
-
type = ::Graphiti::Types[filter_map[:type]]
|
|
32
|
-
unless type[:canonical_name] == :hash || !value.is_a?(String)
|
|
33
|
-
value = parse_string_value(filter_map, value)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
check_deny_empty_filters!(resource, filter, value)
|
|
37
|
-
value = parse_string_null(filter_map, value)
|
|
38
|
-
validate_singular(resource, filter, value)
|
|
39
|
-
value = coerce_types(filter_map, param_name.to_sym, value)
|
|
40
|
-
validate_allowlist(resource, filter, value)
|
|
41
|
-
validate_denylist(resource, filter, value)
|
|
42
|
-
value = value[0] if filter_map[:single]
|
|
43
|
-
yield filter, operator, value
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
1
|
+
module Graphiti::ActiveGraph
|
|
2
|
+
module Scoping
|
|
3
|
+
module Filter
|
|
4
|
+
include Filterable
|
|
5
|
+
include Internal::SortingAliases
|
|
6
|
+
include Extensions::QueryDsl::Performer
|
|
7
|
+
|
|
8
|
+
attr_reader :scope
|
|
9
|
+
|
|
10
|
+
def apply
|
|
11
|
+
super
|
|
12
|
+
apply_query_dsl
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def each_filter
|
|
16
|
+
filter_param.each_pair do |param_name, param_value|
|
|
17
|
+
filter = find_filter!(param_name)
|
|
18
|
+
|
|
19
|
+
normalize_param(filter, param_value).each do |operator, value|
|
|
20
|
+
operator = operator.to_s.gsub("!", "not_").to_sym
|
|
21
|
+
|
|
22
|
+
# dynamic filters errors for validating and typecasting value below
|
|
23
|
+
# so they are skipped here without validation or typecast
|
|
24
|
+
filter_map = filter.values[0]
|
|
25
|
+
if filter_map[:dynamic_filter]
|
|
26
|
+
yield filter, operator, value
|
|
27
|
+
next
|
|
28
|
+
end
|
|
29
|
+
validate_operator(filter, operator)
|
|
30
|
+
|
|
31
|
+
type = ::Graphiti::Types[filter_map[:type]]
|
|
32
|
+
unless type[:canonical_name] == :hash || !value.is_a?(String)
|
|
33
|
+
value = parse_string_value(filter_map, value)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
check_deny_empty_filters!(resource, filter, value)
|
|
37
|
+
value = parse_string_null(filter_map, value)
|
|
38
|
+
validate_singular(resource, filter, value)
|
|
39
|
+
value = coerce_types(filter_map, param_name.to_sym, value)
|
|
40
|
+
validate_allowlist(resource, filter, value)
|
|
41
|
+
validate_denylist(resource, filter, value)
|
|
42
|
+
value = value[0] if filter_map[:single]
|
|
43
|
+
yield filter, operator, value
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
module Graphiti::ActiveGraph
|
|
2
|
-
module Scoping
|
|
3
|
-
module Filterable
|
|
4
|
-
def find_filter!(name)
|
|
5
|
-
val = resource.filters[name] || {
|
|
6
|
-
operators: {}, type: :string, single: false, dynamic_filter: true
|
|
7
|
-
}
|
|
8
|
-
{name => val}
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
end
|
|
12
|
-
end
|
|
1
|
+
module Graphiti::ActiveGraph
|
|
2
|
+
module Scoping
|
|
3
|
+
module Filterable
|
|
4
|
+
def find_filter!(name)
|
|
5
|
+
val = resource.filters[name] || {
|
|
6
|
+
operators: {}, type: :string, single: false, dynamic_filter: true
|
|
7
|
+
}
|
|
8
|
+
{name => val}
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
module Graphiti::ActiveGraph
|
|
2
|
-
module Scoping
|
|
3
|
-
# Handles sideloading via scoping instead of sideloading query as in original jsonapi_suite
|
|
4
|
-
# This avoids extra queries for fetching sideload
|
|
5
|
-
class Include < Graphiti::Scoping::Base
|
|
6
|
-
include Internal::SortingAliases
|
|
7
|
-
|
|
8
|
-
def custom_scope
|
|
9
|
-
nil
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def apply_standard_scope
|
|
13
|
-
return scope if normalized_includes.empty? && extra_fields_includes.empty?
|
|
14
|
-
|
|
15
|
-
self.scope = resource.handle_includes(scope, normalized_includes, normalized_sorts,
|
|
16
|
-
extra_fields_includes:, with_vars: with_vars_for_sort, paginate: paginate?)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
attr_accessor :scope
|
|
22
|
-
|
|
23
|
-
def query
|
|
24
|
-
@opts[:query_obj]
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def extra_fields_includes
|
|
28
|
-
@extra_fields_includes ||= Internal::ExtraFieldNormalizer.new(@query_hash[:extra_fields]).normalize(resource, normalized_includes)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def paginate?
|
|
32
|
-
Graphiti::Scoping::Paginate.new(@resource, @query_hash, scope, @opts).apply?
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def normalized_sorts
|
|
36
|
-
Internal::SortNormalizer.new(scope).normalize(normalized_includes, query.sorts, query.deep_sort)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
def include_normalizer
|
|
40
|
-
Internal::IncludeNormalizer
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def normalized_includes
|
|
44
|
-
@normalized_includes ||= include_normalizer.new(resource.class, scope, query_hash[:fields]).normalize(query.include_hash)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
end
|
|
1
|
+
module Graphiti::ActiveGraph
|
|
2
|
+
module Scoping
|
|
3
|
+
# Handles sideloading via scoping instead of sideloading query as in original jsonapi_suite
|
|
4
|
+
# This avoids extra queries for fetching sideload
|
|
5
|
+
class Include < Graphiti::Scoping::Base
|
|
6
|
+
include Internal::SortingAliases
|
|
7
|
+
|
|
8
|
+
def custom_scope
|
|
9
|
+
nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def apply_standard_scope
|
|
13
|
+
return scope if normalized_includes.empty? && extra_fields_includes.empty?
|
|
14
|
+
|
|
15
|
+
self.scope = resource.handle_includes(scope, normalized_includes, normalized_sorts,
|
|
16
|
+
extra_fields_includes:, with_vars: with_vars_for_sort, paginate: paginate?)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
attr_accessor :scope
|
|
22
|
+
|
|
23
|
+
def query
|
|
24
|
+
@opts[:query_obj]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def extra_fields_includes
|
|
28
|
+
@extra_fields_includes ||= Internal::ExtraFieldNormalizer.new(@query_hash[:extra_fields]).normalize(resource, normalized_includes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def paginate?
|
|
32
|
+
Graphiti::Scoping::Paginate.new(@resource, @query_hash, scope, @opts).apply?
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def normalized_sorts
|
|
36
|
+
Internal::SortNormalizer.new(scope).normalize(normalized_includes, query.sorts, query.deep_sort)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def include_normalizer
|
|
40
|
+
Internal::IncludeNormalizer
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def normalized_includes
|
|
44
|
+
@normalized_includes ||= include_normalizer.new(resource.class, scope, query_hash[:fields]).normalize(query.include_hash)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -1,76 +1,76 @@
|
|
|
1
|
-
module Graphiti::ActiveGraph
|
|
2
|
-
module Scoping
|
|
3
|
-
module Internal
|
|
4
|
-
class ExtraFieldNormalizer
|
|
5
|
-
|
|
6
|
-
def initialize(extra_fields)
|
|
7
|
-
@extra_fields = extra_fields
|
|
8
|
-
@extra_includes = []
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def normalize(resource, normalized_includes)
|
|
12
|
-
return [] if @extra_fields.blank?
|
|
13
|
-
|
|
14
|
-
process_extra_fields_for_assoc(resource, [], '')
|
|
15
|
-
collect_extra_field_paths(resource, normalized_includes) unless normalized_includes.blank?
|
|
16
|
-
@extra_includes.uniq
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def collect_extra_field_paths(resource, normalized_includes, parent_path = [])
|
|
22
|
-
normalized_includes.each do |assoc, nested_assoc|
|
|
23
|
-
assoc_resource = fetch_assoc_resource(resource, assoc)
|
|
24
|
-
next unless assoc_resource
|
|
25
|
-
|
|
26
|
-
process_extra_fields_for_assoc(assoc_resource, parent_path, assoc)
|
|
27
|
-
collect_extra_field_paths(assoc_resource, nested_assoc, parent_path + [assoc.to_s]) unless nested_assoc.empty?
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def fetch_assoc_resource(resource, assoc)
|
|
32
|
-
rel_name = Util::Transformers::RelationParam.new(assoc).rel_name_sym
|
|
33
|
-
resource.class&.sideload_resource_class(rel_name)&.new
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def process_extra_fields_for_assoc(assoc_resource, parent_path, assoc)
|
|
37
|
-
return unless @extra_fields.key?(assoc_resource.type)
|
|
38
|
-
|
|
39
|
-
Array(@extra_fields[assoc_resource.type]).each do |extra_field|
|
|
40
|
-
add_preload_paths_for_extra_field(extra_field_config(assoc_resource, extra_field), parent_path, assoc)
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def extra_field_config(assoc_resource, extra_field)
|
|
45
|
-
assoc_resource.class&.config&.dig(:extra_attributes, extra_field)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def add_preload_paths_for_extra_field(config, parent_path, assoc)
|
|
49
|
-
return unless config && config[:preload].present?
|
|
50
|
-
|
|
51
|
-
flatten_preload_hash(config[:preload]).each do |preload|
|
|
52
|
-
@extra_includes << construct_preload_path(parent_path, assoc, preload)
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def flatten_preload_hash(preload, prefix = [])
|
|
57
|
-
case preload
|
|
58
|
-
when Hash
|
|
59
|
-
preload.flat_map { |k, v| flatten_preload_hash(v, prefix + [k.to_s]) }
|
|
60
|
-
when Array
|
|
61
|
-
preload.flat_map { |v| flatten_preload_hash(v, prefix) }
|
|
62
|
-
else
|
|
63
|
-
value = preload.to_s
|
|
64
|
-
return [] if value.empty?
|
|
65
|
-
|
|
66
|
-
[(prefix + [value]).join('.')]
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
def construct_preload_path(parent_path, assoc, preload)
|
|
71
|
-
(parent_path + [assoc.to_s, preload.to_s]).compact_blank.join('.')
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
end
|
|
1
|
+
module Graphiti::ActiveGraph
|
|
2
|
+
module Scoping
|
|
3
|
+
module Internal
|
|
4
|
+
class ExtraFieldNormalizer
|
|
5
|
+
|
|
6
|
+
def initialize(extra_fields)
|
|
7
|
+
@extra_fields = extra_fields
|
|
8
|
+
@extra_includes = []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def normalize(resource, normalized_includes)
|
|
12
|
+
return [] if @extra_fields.blank?
|
|
13
|
+
|
|
14
|
+
process_extra_fields_for_assoc(resource, [], '')
|
|
15
|
+
collect_extra_field_paths(resource, normalized_includes) unless normalized_includes.blank?
|
|
16
|
+
@extra_includes.uniq
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def collect_extra_field_paths(resource, normalized_includes, parent_path = [])
|
|
22
|
+
normalized_includes.each do |assoc, nested_assoc|
|
|
23
|
+
assoc_resource = fetch_assoc_resource(resource, assoc)
|
|
24
|
+
next unless assoc_resource
|
|
25
|
+
|
|
26
|
+
process_extra_fields_for_assoc(assoc_resource, parent_path, assoc)
|
|
27
|
+
collect_extra_field_paths(assoc_resource, nested_assoc, parent_path + [assoc.to_s]) unless nested_assoc.empty?
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fetch_assoc_resource(resource, assoc)
|
|
32
|
+
rel_name = Util::Transformers::RelationParam.new(assoc).rel_name_sym
|
|
33
|
+
resource.class&.sideload_resource_class(rel_name)&.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def process_extra_fields_for_assoc(assoc_resource, parent_path, assoc)
|
|
37
|
+
return unless @extra_fields.key?(assoc_resource.type)
|
|
38
|
+
|
|
39
|
+
Array(@extra_fields[assoc_resource.type]).each do |extra_field|
|
|
40
|
+
add_preload_paths_for_extra_field(extra_field_config(assoc_resource, extra_field), parent_path, assoc)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def extra_field_config(assoc_resource, extra_field)
|
|
45
|
+
assoc_resource.class&.config&.dig(:extra_attributes, extra_field)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_preload_paths_for_extra_field(config, parent_path, assoc)
|
|
49
|
+
return unless config && config[:preload].present?
|
|
50
|
+
|
|
51
|
+
flatten_preload_hash(config[:preload]).each do |preload|
|
|
52
|
+
@extra_includes << construct_preload_path(parent_path, assoc, preload)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def flatten_preload_hash(preload, prefix = [])
|
|
57
|
+
case preload
|
|
58
|
+
when Hash
|
|
59
|
+
preload.flat_map { |k, v| flatten_preload_hash(v, prefix + [k.to_s]) }
|
|
60
|
+
when Array
|
|
61
|
+
preload.flat_map { |v| flatten_preload_hash(v, prefix) }
|
|
62
|
+
else
|
|
63
|
+
value = preload.to_s
|
|
64
|
+
return [] if value.empty?
|
|
65
|
+
|
|
66
|
+
[(prefix + [value]).join('.')]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def construct_preload_path(parent_path, assoc, preload)
|
|
71
|
+
(parent_path + [assoc.to_s, preload.to_s]).compact_blank.join('.')
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
module Graphiti::ActiveGraph
|
|
2
|
-
module Scoping
|
|
3
|
-
module Internal
|
|
4
|
-
class IncludeNormalizer
|
|
5
|
-
include SparseFieldsEagerloading
|
|
6
|
-
|
|
7
|
-
def initialize(resource_class, scope, fields)
|
|
8
|
-
@scope = scope
|
|
9
|
-
@resource_class = resource_class
|
|
10
|
-
@fields = fields
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def normalize(include_hash)
|
|
14
|
-
normalize_includes(@scope, include_hash, @resource_class)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def normalize_includes(scope, include_hash, resource_class)
|
|
20
|
-
includes_array = include_hash.map do |key, value|
|
|
21
|
-
normalize_include(scope, key, value, resource_class)
|
|
22
|
-
end
|
|
23
|
-
add_relationships_from_sparse_fields(scope, includes_array)
|
|
24
|
-
deep_merge_hashes(includes_array.compact).to_h
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def deep_merge_hashes(includes_array)
|
|
28
|
-
includes_array.each_with_object({}) do |(key, value), mapping|
|
|
29
|
-
mapping[key] = mapping[key] ? mapping[key].deep_merge(value) : value
|
|
30
|
-
end.to_a
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def normalize_include(scope, key, value, resource_class)
|
|
34
|
-
rel_name = rel_name_sym(key)
|
|
35
|
-
|
|
36
|
-
if scope.associations.key?(rel_name)
|
|
37
|
-
[key, normalize_includes(scope.send(rel_name), value, find_resource_class(resource_class, rel_name, scope))]
|
|
38
|
-
elsif (custom_eagerload = resource_class&.custom_eagerload(rel_name))
|
|
39
|
-
handle_custom_eagerload(scope, custom_eagerload)
|
|
40
|
-
else
|
|
41
|
-
include_for_rel(scope, rel_name, value, resource_class)
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def handle_custom_eagerload(_scope, custom_eagerload)
|
|
46
|
-
JSONAPI::IncludeDirective.new(custom_eagerload).to_hash
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def include_for_rel(scope, key, value, resource_class)
|
|
50
|
-
return unless association = PathDescriptor.association_for_relationship(scope.associations, rel_name: key.to_s)
|
|
51
|
-
|
|
52
|
-
limit_part = Graphiti::ActiveGraph::Util::Transformers::RelationParam.new(value.keys.first).rel_limit
|
|
53
|
-
association_name = "#{limit_part}#{association.first}".to_sym
|
|
54
|
-
normalize_include(scope, association_name, next_non_rel_value(value), resource_class_by_rel(resource_class, association, key, scope))
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def find_resource_class(resource_class, rel_name, scope)
|
|
58
|
-
target_class_name = scope.associations[rel_name]&.target_class&.model_name
|
|
59
|
-
|
|
60
|
-
resource_class&.sideload_resource_class(rel_name) ||
|
|
61
|
-
resource_class&.sideload_resource_class(target_class_name&.singular&.to_sym)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def resource_class_by_rel(resource_class, association, key, scope)
|
|
65
|
-
# in case of rel resource, for finding custom_eagerload defination
|
|
66
|
-
# if current resourceClass defination has direct association defined with opposite node of relResource
|
|
67
|
-
# then use current resourceClass, (giving direct resourceClass more preference than relResourceClass)
|
|
68
|
-
# else use relResourceClass
|
|
69
|
-
find_resource_class(resource_class, association.first, scope) ? resource_class : resource_class&.sideload_resource_class(rel_name_sym(key))
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def rel_name_sym(key)
|
|
73
|
-
Graphiti::ActiveGraph::Util::Transformers::RelationParam.new(key).rel_name_sym
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
def next_non_rel_value(value)
|
|
77
|
-
value.values.first || {}
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
1
|
+
module Graphiti::ActiveGraph
|
|
2
|
+
module Scoping
|
|
3
|
+
module Internal
|
|
4
|
+
class IncludeNormalizer
|
|
5
|
+
include SparseFieldsEagerloading
|
|
6
|
+
|
|
7
|
+
def initialize(resource_class, scope, fields)
|
|
8
|
+
@scope = scope
|
|
9
|
+
@resource_class = resource_class
|
|
10
|
+
@fields = fields
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def normalize(include_hash)
|
|
14
|
+
normalize_includes(@scope, include_hash, @resource_class)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def normalize_includes(scope, include_hash, resource_class)
|
|
20
|
+
includes_array = include_hash.map do |key, value|
|
|
21
|
+
normalize_include(scope, key, value, resource_class)
|
|
22
|
+
end
|
|
23
|
+
add_relationships_from_sparse_fields(scope, includes_array)
|
|
24
|
+
deep_merge_hashes(includes_array.compact).to_h
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def deep_merge_hashes(includes_array)
|
|
28
|
+
includes_array.each_with_object({}) do |(key, value), mapping|
|
|
29
|
+
mapping[key] = mapping[key] ? mapping[key].deep_merge(value) : value
|
|
30
|
+
end.to_a
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def normalize_include(scope, key, value, resource_class)
|
|
34
|
+
rel_name = rel_name_sym(key)
|
|
35
|
+
|
|
36
|
+
if scope.associations.key?(rel_name)
|
|
37
|
+
[key, normalize_includes(scope.send(rel_name), value, find_resource_class(resource_class, rel_name, scope))]
|
|
38
|
+
elsif (custom_eagerload = resource_class&.custom_eagerload(rel_name))
|
|
39
|
+
handle_custom_eagerload(scope, custom_eagerload)
|
|
40
|
+
else
|
|
41
|
+
include_for_rel(scope, rel_name, value, resource_class)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_custom_eagerload(_scope, custom_eagerload)
|
|
46
|
+
JSONAPI::IncludeDirective.new(custom_eagerload).to_hash
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def include_for_rel(scope, key, value, resource_class)
|
|
50
|
+
return unless association = PathDescriptor.association_for_relationship(scope.associations, rel_name: key.to_s)
|
|
51
|
+
|
|
52
|
+
limit_part = Graphiti::ActiveGraph::Util::Transformers::RelationParam.new(value.keys.first).rel_limit
|
|
53
|
+
association_name = "#{limit_part}#{association.first}".to_sym
|
|
54
|
+
normalize_include(scope, association_name, next_non_rel_value(value), resource_class_by_rel(resource_class, association, key, scope))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def find_resource_class(resource_class, rel_name, scope)
|
|
58
|
+
target_class_name = scope.associations[rel_name]&.target_class&.model_name
|
|
59
|
+
|
|
60
|
+
resource_class&.sideload_resource_class(rel_name) ||
|
|
61
|
+
resource_class&.sideload_resource_class(target_class_name&.singular&.to_sym)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def resource_class_by_rel(resource_class, association, key, scope)
|
|
65
|
+
# in case of rel resource, for finding custom_eagerload defination
|
|
66
|
+
# if current resourceClass defination has direct association defined with opposite node of relResource
|
|
67
|
+
# then use current resourceClass, (giving direct resourceClass more preference than relResourceClass)
|
|
68
|
+
# else use relResourceClass
|
|
69
|
+
find_resource_class(resource_class, association.first, scope) ? resource_class : resource_class&.sideload_resource_class(rel_name_sym(key))
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def rel_name_sym(key)
|
|
73
|
+
Graphiti::ActiveGraph::Util::Transformers::RelationParam.new(key).rel_name_sym
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def next_non_rel_value(value)
|
|
77
|
+
value.values.first || {}
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|