graphql_includable 0.2.7 → 0.2.8
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/lib/graphql_includable/concern.rb +1 -0
- data/lib/graphql_includable/edge.rb +56 -50
- data/lib/graphql_includable/resolver.rb +88 -88
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fb8b1ec2e2a339a4fe78952b2426ef79dc588a8
|
4
|
+
data.tar.gz: 0a528542daf9f1723fdc1c64619b68591f2e7c78
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e9b99fc48e697dcdda9a1fc821c6f310bb38cc555f91c4fd068ea40cfb9683b09c0e01115d9f4b005879c6942474027745cc56e984ef3c2c87e8b9cabf17bea
|
7
|
+
data.tar.gz: 8a896215e28522ef35f9862e7e2f89db6c228a7d20390dc52343e0f43b489d0f19a9afd00b8e6b9e9a5345ceba1bb095a82522a12ac96bf20f37a05f6a11919d
|
@@ -1,72 +1,78 @@
|
|
1
1
|
module GraphQLIncludable
|
2
2
|
class Edge < GraphQL::Relay::Edge
|
3
|
-
def
|
4
|
-
return @
|
3
|
+
def edge
|
4
|
+
return @edge if @edge
|
5
|
+
join_chain = joins_along_edge
|
6
|
+
edge_class_name = join_chain.shift
|
7
|
+
edge_class = str_to_class(edge_class_name)
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
selector = selector.find_by(where_hash_for_edge(edge_class, all_association_names))
|
9
|
+
root_association_key = class_to_str(parent.class)
|
10
|
+
unless edge_class.reflections.keys.include?(root_association_key)
|
11
|
+
is_polymorphic = true
|
12
|
+
root_association_key = edge_class.reflections.select { |k, r| r.polymorphic? }.keys.first
|
13
|
+
end
|
14
|
+
root_node = { root_association_key.to_sym => [parent] }
|
15
|
+
terminal_node = { class_to_str(node.class) => node }
|
16
|
+
join_chain.reverse.each do |rel_name|
|
17
|
+
terminal_node = { rel_name.to_s.pluralize => terminal_node }
|
18
|
+
end
|
17
19
|
|
18
|
-
|
20
|
+
search_hash = root_node.merge(terminal_node)
|
21
|
+
edge_includes = join_chain.map { |s| s.to_s.singularize }
|
22
|
+
edge_class = edge_class.includes(*edge_includes) unless edge_includes.empty?
|
23
|
+
edge_class = edge_class.joins(root_association_key.to_sym) unless is_polymorphic
|
24
|
+
@edge ||= edge_class.find_by(search_hash)
|
19
25
|
end
|
20
26
|
|
21
27
|
def method_missing(method_name, *args, &block)
|
22
|
-
|
23
|
-
|
28
|
+
if edge.respond_to?(method_name)
|
29
|
+
edge.send(method_name, *args, &block)
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
24
33
|
end
|
25
34
|
|
26
35
|
def respond_to_missing(method_name, include_private = false)
|
27
|
-
|
36
|
+
edge.respond_to?(method_name) || super
|
28
37
|
end
|
29
38
|
|
30
39
|
private
|
31
|
-
def associations_between_node_and_parent
|
32
|
-
return @associations if @associations.present?
|
33
|
-
|
34
|
-
associations = []
|
35
|
-
association_name = node.class.name.pluralize.downcase.to_sym
|
36
|
-
association = parent.class.reflect_on_association(association_name)
|
37
|
-
while association.is_a?(ActiveRecord::Reflection::ThroughReflection)
|
38
|
-
associations.unshift(association.options[:through])
|
39
|
-
association = parent.class.reflect_on_association(association.options[:through])
|
40
|
-
end
|
41
40
|
|
42
|
-
|
41
|
+
def str_to_class(str)
|
42
|
+
str.to_s.singularize.camelize.constantize
|
43
|
+
rescue
|
43
44
|
end
|
44
45
|
|
45
|
-
def
|
46
|
-
|
47
|
-
unless edge_class.reflections.keys.include?(root_association_key)
|
48
|
-
root_association_key = edge_class.reflections.select { |k, r| r.polymorphic? }.keys.first
|
49
|
-
end
|
50
|
-
root_include = { root_association_key.to_sym => [parent] }
|
51
|
-
|
52
|
-
terminal_include = { self.class.class_to_str(node.class) => node }
|
53
|
-
association_names.reverse.each do |rel_name|
|
54
|
-
terminal_include = { rel_name.to_s.pluralize => terminal_include }
|
55
|
-
end
|
56
|
-
|
57
|
-
root_include.merge(terminal_include)
|
46
|
+
def class_to_str(klass)
|
47
|
+
klass.name.downcase
|
58
48
|
end
|
59
49
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
50
|
+
def joins_along_edge
|
51
|
+
# node.edges
|
52
|
+
# edge.node
|
53
|
+
# edge.parent
|
54
|
+
# parent.edges
|
55
|
+
# node.parents
|
56
|
+
# parent.nodes
|
57
|
+
edge_association_name = node.class.name.pluralize.downcase.to_sym
|
58
|
+
edge_association = parent.class.reflect_on_association(edge_association_name)
|
59
|
+
edge_joins = []
|
60
|
+
while edge_association.is_a? ActiveRecord::Reflection::ThroughReflection
|
61
|
+
edge_joins.unshift edge_association.options[:through]
|
62
|
+
edge_association = parent.class.reflect_on_association(edge_association.options[:through])
|
69
63
|
end
|
64
|
+
edge_joins
|
65
|
+
# join_chain = []
|
66
|
+
# starting_class = parent.class
|
67
|
+
# node_relationship_name = class_to_str(node.class)
|
68
|
+
# while starting_class
|
69
|
+
# reflection = starting_class.reflect_on_association(node_relationship_name)
|
70
|
+
# association_name = reflection&.options&.try(:[], :through)
|
71
|
+
# join_chain << association_name if association_name
|
72
|
+
# starting_class = str_to_class(association_name)
|
73
|
+
# node_relationship_name = node_relationship_name.singularize
|
74
|
+
# end
|
75
|
+
# join_chain
|
70
76
|
end
|
71
77
|
end
|
72
78
|
end
|
@@ -1,113 +1,113 @@
|
|
1
1
|
module GraphQLIncludable
|
2
2
|
class Resolver
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
find_node_by_return_type(child_node, desired_return_type)
|
13
|
-
end
|
14
|
-
break if matching_node
|
3
|
+
# Returns the first node in the tree which returns a specific type
|
4
|
+
def self.find_node_by_return_type(node, desired_return_type)
|
5
|
+
return_type = node.return_type.unwrap.to_s
|
6
|
+
return node if return_type == desired_return_type
|
7
|
+
if node.respond_to?(:scoped_children)
|
8
|
+
matching_node = nil
|
9
|
+
node.scoped_children.values.each do |selections|
|
10
|
+
matching_node = selections.values.find do |child_node|
|
11
|
+
find_node_by_return_type(child_node, desired_return_type)
|
15
12
|
end
|
16
|
-
matching_node
|
13
|
+
break if matching_node
|
17
14
|
end
|
15
|
+
matching_node
|
18
16
|
end
|
17
|
+
end
|
19
18
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
19
|
+
# Translate a node's selections into `includes` values
|
20
|
+
# Combine and format children values
|
21
|
+
# Noop on nodes that don't return AR (so no associations to include)
|
22
|
+
def self.includes_for_node(node)
|
23
|
+
return_model = node_return_model(node)
|
24
|
+
return [] if return_model.blank?
|
25
|
+
|
26
|
+
includes = []
|
27
|
+
children = node.scoped_children[node.return_type.unwrap]
|
28
|
+
nested_includes = {}
|
28
29
|
|
29
|
-
|
30
|
-
|
31
|
-
delegated_through = includes_delegated_through(parent_model, attribute_name)
|
32
|
-
delegated_model = model_name_to_class(delegated_through.last) if delegated_through.present?
|
33
|
-
association = (delegated_model || parent_model).reflect_on_association(attribute_name)
|
34
|
-
interceding_includes = []
|
35
|
-
# association = get_model_association(parent_model, attribute_name, interceding_includes)
|
30
|
+
children.each_value do |child_node|
|
31
|
+
child_includes = includes_for_child(child_node, return_model)
|
36
32
|
|
37
|
-
if
|
38
|
-
child_includes
|
39
|
-
|
40
|
-
|
41
|
-
# join_name = association.options[:through]
|
42
|
-
# edge_includes_chain = [association.name]
|
43
|
-
# edge_includes_chain << child_includes.pop[association.name.to_s.singularize.to_sym] if child_includes.last&.is_a?(Hash)
|
44
|
-
# edge_includes = array_to_nested_hash(edge_includes_chain)
|
45
|
-
# end
|
33
|
+
if child_includes.is_a?(Hash)
|
34
|
+
nested_includes.merge!(child_includes)
|
35
|
+
elsif child_includes.is_a?(Array)
|
36
|
+
includes += child_includes
|
46
37
|
else
|
47
|
-
|
48
|
-
[array_to_nested_hash(interceding_includes)].reject(&:blank?)
|
38
|
+
includes << child_includes
|
49
39
|
end
|
50
40
|
end
|
51
41
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
child_includes.each do |child|
|
56
|
-
if child.is_a?(Hash)
|
57
|
-
nested_includes.merge!(child)
|
58
|
-
elsif child.is_a?(Array)
|
59
|
-
includes += child
|
60
|
-
else
|
61
|
-
includes << child
|
62
|
-
end
|
63
|
-
end
|
64
|
-
includes << nested_includes if nested_includes.present?
|
65
|
-
includes.uniq
|
66
|
-
end
|
42
|
+
includes << nested_includes if nested_includes.present?
|
43
|
+
includes.uniq
|
44
|
+
end
|
67
45
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
46
|
+
def self.includes_for_child(node, parent_model)
|
47
|
+
attribute_name = node_predicted_association_name(node)
|
48
|
+
delegated_through = includes_delegated_through(parent_model, attribute_name)
|
49
|
+
delegated_model = model_name_to_class(delegated_through.last) if delegated_through.present?
|
50
|
+
association = (delegated_model || parent_model).reflect_on_association(attribute_name)
|
51
|
+
interceding_includes = []
|
52
|
+
# association = get_model_association(parent_model, attribute_name, interceding_includes)
|
53
|
+
|
54
|
+
if association
|
55
|
+
child_includes = includes_for_node(node)
|
56
|
+
array_to_nested_hash(interceding_includes + [attribute_name, child_includes].reject(&:blank?))
|
57
|
+
# if node_is_relay_connection?(node)
|
58
|
+
# join_name = association.options[:through]
|
59
|
+
# edge_includes_chain = [association.name]
|
60
|
+
# edge_includes_chain << child_includes.pop[association.name.to_s.singularize.to_sym] if child_includes.last&.is_a?(Hash)
|
61
|
+
# edge_includes = array_to_nested_hash(edge_includes_chain)
|
62
|
+
# end
|
63
|
+
else
|
64
|
+
# TODO: specified includes?
|
65
|
+
[array_to_nested_hash(interceding_includes)].reject(&:blank?)
|
75
66
|
end
|
67
|
+
end
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
model if model < ActiveRecord::Base
|
69
|
+
def self.model_name_to_class(model_name)
|
70
|
+
begin
|
71
|
+
model_name.to_s.camelize.constantize
|
81
72
|
rescue NameError
|
73
|
+
model_name.to_s.singularize.camelize.constantize
|
82
74
|
end
|
75
|
+
rescue
|
76
|
+
end
|
83
77
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
78
|
+
# Translate a node's return type to an ActiveRecord model
|
79
|
+
def self.node_return_model(node)
|
80
|
+
model = Object.const_get(node.return_type.unwrap.name.gsub(/(^SquareFoot|Edge$|Connection$)/, ''))
|
81
|
+
model if model < ActiveRecord::Base
|
82
|
+
rescue NameError
|
83
|
+
end
|
88
84
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
method = method_name.to_sym
|
94
|
-
model_name = base_model.instance_variable_get(:@delegate_cache).try(:[], method)
|
95
|
-
while model_name
|
96
|
-
chain << model_name
|
97
|
-
model = model_name_to_class(model_name)
|
98
|
-
model_name = model.instance_variable_get(:@delegate_cache).try(:[], method)
|
99
|
-
end
|
100
|
-
chain
|
101
|
-
end
|
85
|
+
def self.node_predicted_association_name(node)
|
86
|
+
definition = node.definitions.first
|
87
|
+
definition.metadata[:includes] || (definition.property || definition.name).to_sym
|
88
|
+
end
|
102
89
|
|
103
|
-
|
104
|
-
|
105
|
-
|
90
|
+
# If method_name is delegated from base_model, return an array of
|
91
|
+
# associations through which those methods can be delegated
|
92
|
+
def self.includes_delegated_through(base_model, method_name)
|
93
|
+
chain = []
|
94
|
+
method = method_name.to_sym
|
95
|
+
model_name = base_model.instance_variable_get(:@delegate_cache).try(:[], method)
|
96
|
+
while model_name
|
97
|
+
chain << model_name
|
98
|
+
model = model_name_to_class(model_name)
|
99
|
+
model_name = model.instance_variable_get(:@delegate_cache).try(:[], method)
|
106
100
|
end
|
101
|
+
chain
|
102
|
+
end
|
107
103
|
|
108
|
-
|
109
|
-
|
110
|
-
|
104
|
+
# Right-reduce an array into a nested hash
|
105
|
+
def self.array_to_nested_hash(arr)
|
106
|
+
arr.reverse.inject { |acc, item| { item => acc } } || {}
|
111
107
|
end
|
108
|
+
|
109
|
+
# def self.node_is_relay_connection?(node)
|
110
|
+
# node.return_type.unwrap.name =~ /Connection$/
|
111
|
+
# end
|
112
112
|
end
|
113
113
|
end
|