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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e0eb7cacc7d8e3cd78f249e9fe3c11a320b1632b
4
- data.tar.gz: 972f6e0a2415a2826e34c819b14272462941e81e
3
+ metadata.gz: 3fb8b1ec2e2a339a4fe78952b2426ef79dc588a8
4
+ data.tar.gz: 0a528542daf9f1723fdc1c64619b68591f2e7c78
5
5
  SHA512:
6
- metadata.gz: e550a9ec0fc0abfc72eb76a8948b8e26185fdd98b8898e76b10b3139e2a44e34a9013c81c8e5ddcac87b95a2b6d02d29452ad5852ba3775e37c69b4203742be8
7
- data.tar.gz: 61ebedb72c0dd0380248eb8765cb0bc9f4ad6d9417bb92285eeefa9a90a81af528b7e65e7de63c534f8030db926b81ea562a49fa5984cec02d76289a8048a1fa
6
+ metadata.gz: 3e9b99fc48e697dcdda9a1fc821c6f310bb38cc555f91c4fd068ea40cfb9683b09c0e01115d9f4b005879c6942474027745cc56e984ef3c2c87e8b9cabf17bea
7
+ data.tar.gz: 8a896215e28522ef35f9862e7e2f89db6c228a7d20390dc52343e0f43b489d0f19a9afd00b8e6b9e9a5345ceba1bb095a82522a12ac96bf20f37a05f6a11919d
@@ -10,6 +10,7 @@ module GraphQLIncludable
10
10
  generated_includes = Resolver.includes_for_node(node)
11
11
  includes(generated_includes)
12
12
  rescue => e
13
+ Rails.logger.error(e)
13
14
  self
14
15
  end
15
16
 
@@ -1,72 +1,78 @@
1
1
  module GraphQLIncludable
2
2
  class Edge < GraphQL::Relay::Edge
3
- def edge_record
4
- return @edge_record if @edge_record.present?
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
- 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? }
12
-
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))
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
- @edge_record = selector
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
- return super unless edge_record.respond_to?(method_name)
23
- edge_record.send(method_name, *args, &block)
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
- edge_record.respond_to?(method_name) || super
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
- @associations = associations
41
+ def str_to_class(str)
42
+ str.to_s.singularize.camelize.constantize
43
+ rescue
43
44
  end
44
45
 
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)
46
+ def class_to_str(klass)
47
+ klass.name.downcase
58
48
  end
59
49
 
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
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
- 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
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
- # 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
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
- 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)
30
+ children.each_value do |child_node|
31
+ child_includes = includes_for_child(child_node, return_model)
36
32
 
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
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
- # TODO: specified includes?
48
- [array_to_nested_hash(interceding_includes)].reject(&:blank?)
38
+ includes << child_includes
49
39
  end
50
40
  end
51
41
 
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
42
+ includes << nested_includes if nested_includes.present?
43
+ includes.uniq
44
+ end
67
45
 
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
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
- # 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
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
- def node_predicted_association_name(node)
85
- definition = node.definitions.first
86
- definition.metadata[:includes] || (definition.property || definition.name).to_sym
87
- end
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
- # 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
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
- # Right-reduce an array into a nested hash
104
- def array_to_nested_hash(arr)
105
- arr.reverse.inject { |acc, item| { item => acc } } || {}
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
- # def node_is_relay_connection?(node)
109
- # node.return_type.unwrap.name =~ /Connection$/
110
- # end
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
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.7
4
+ version: 0.2.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dan Rouse