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