graphql_includable 0.1.4 → 0.1.5

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/graphql_includable.rb +75 -40
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 422e58558f8fad15867c7e3310cb466d1da7ad87
4
- data.tar.gz: d6872a079bc7fd67b241736ef7214a2f1aefe806
3
+ metadata.gz: 803a100d8819528d092fd114ab447f1eabcb3872
4
+ data.tar.gz: 18b7e345903158ed2bcf003852061a34d6ccaf04
5
5
  SHA512:
6
- metadata.gz: 0e27a2d8c049be3eda8248860aecc254e67944c908a32c5b06a9af5ceb7f4f748495620b9dee6725f9be5b04bfcb34e677625a1c935f59258fb30907e19e7680
7
- data.tar.gz: 509fdbaa84c06ce4e4bc8ccff1d653e53fc09f7b8fd82cb12b060d1d6b9a7ecf69a221d6f5d43c3d979e5cb0fdfb3d75e25dba9cb89c203b7fa31ee3dc17c4d2
6
+ metadata.gz: dd8826b97ddcb70b6701a4da2136593f0b8743f284b8f0115ea547f1b3b8f4cbdc1e25291a5b6efc30bac98a44e8641e1a5a0d2e641144003594f2cb61257b21
7
+ data.tar.gz: d5a44484141daeae5667cdc06c550623dd636a3324b273672a0ef5918f0bd65c33561f68af35d386f68a5481a56339393278eec22cf8dd45aa9a05389f4f2195
@@ -9,6 +9,7 @@ module GraphQLIncludable
9
9
  module ClassMethods
10
10
  def includes_from_graphql(query_context)
11
11
  generated_includes = GraphQLIncludable.generate_includes_from_graphql(query_context, self.model_name.to_s)
12
+ puts "final gen: #{generated_includes}"
12
13
  includes(generated_includes)
13
14
  end
14
15
 
@@ -28,66 +29,68 @@ module GraphQLIncludable
28
29
  private
29
30
 
30
31
  def self.generate_includes_from_graphql(query_context, model_name)
31
- matching_node = GraphQLIncludable.find_child_node_matching_model_name(query_context.irep_node, model_name)
32
- GraphQLIncludable.includes_from_irep_node(matching_node)
32
+ matching_node = GraphQLIncludable.find_child_returning_model_name(query_context.irep_node, model_name)
33
+ GraphQLIncludable.includes_from_graphql_field(matching_node)
33
34
  end
34
35
 
35
- def self.find_child_node_matching_model_name(node, model_name)
36
+ def self.find_child_returning_model_name(node, model_name)
36
37
  matching_node = nil
37
- return_type = unwrapped_type(node)
38
+ return_type = node_return_type(node)
38
39
  if return_type.to_s == model_name
39
40
  matching_node = node
40
41
  elsif node.respond_to? :scoped_children
41
42
  node.scoped_children[return_type].each do |child_name, child_node|
42
- matching_node = find_child_node_matching_model_name(child_node, model_name)
43
+ matching_node = find_child_returning_model_name(child_node, model_name)
43
44
  break if matching_node
44
45
  end
45
46
  end
46
47
  matching_node
47
48
  end
48
49
 
49
- def self.includes_from_irep_node(node)
50
+ def self.includes_from_graphql_field(node)
50
51
  includes = []
51
52
  nested_includes = {}
52
53
 
53
- return_type = unwrapped_type(node)
54
+ return_type = node_return_type(node)
54
55
  return_model = node_return_class(node)
55
56
  return [] unless node && return_type && return_model
56
57
 
57
- node.scoped_children[return_type].each do |child_name, child_node|
58
- specified_includes = child_node.definitions[0].metadata[:includes]
59
- raw_association_name = (child_node.definitions[0].property || child_name).to_sym
60
- raw_association_name = specified_includes if specified_includes.is_a?(Symbol)
61
- delegated_model_name = get_delegated_model(return_model, raw_association_name)
62
- association_name = delegated_model_name || raw_association_name
63
- association = return_model.reflect_on_association(association_name)
64
-
65
- if association
66
- child_includes = includes_from_irep_node(child_node)
67
- if node_has_active_record_children(child_node) && child_includes.size > 0
68
- child_key = delegated_model_name || association_name
69
- nested_includes[child_key] = wrap_delegate(child_includes, delegated_model_name, raw_association_name)
70
- else
71
- includes << wrap_delegate(specified_includes || association_name, delegated_model_name)
72
- end
73
- elsif specified_includes
74
- includes << specified_includes
58
+ node.scoped_children[return_type].each do |_child_name, child_node|
59
+ child_includes = includes_from_graphql_child(child_node, return_model)
60
+ if child_includes.is_a?(Hash)
61
+ nested_includes.merge!(child_includes)
62
+ elsif child_includes.is_a?(Array)
63
+ includes += child_includes
64
+ elsif child_includes
65
+ includes << child_includes
75
66
  end
76
67
  end
77
68
 
78
- includes << nested_includes if nested_includes.size > 0
69
+ includes << nested_includes unless nested_includes.empty?
79
70
  includes
80
71
  end
81
72
 
82
- def self.node_has_active_record_children(node)
83
- node.scoped_children[unwrapped_type(node)].each do |child_return_name, child_node|
84
- node_returns_active_record?(child_node)
73
+ def self.includes_from_graphql_child(child_node, return_model)
74
+ specified_includes = child_node.definitions[0].metadata[:includes]
75
+ attribute_name = node_predicted_association_name(child_node)
76
+ includes_chain = delegated_includes_chain(return_model, attribute_name)
77
+
78
+ if model_has_association?(return_model, attribute_name, includes_chain)
79
+ child_includes = includes_from_graphql_field(child_node)
80
+ includes_chain << (specified_includes || attribute_name)
81
+ includes_chain << child_includes unless child_includes.empty?
82
+ array_to_nested_hash(includes_chain)
83
+ else
84
+ includes = []
85
+ includes << array_to_nested_hash(includes_chain) unless includes_chain.empty?
86
+ includes << specified_includes if specified_includes
87
+ includes
85
88
  end
86
89
  end
87
90
 
88
91
  def self.node_return_class(node)
89
92
  begin
90
- Object.const_get(unwrapped_type(node).name)
93
+ Object.const_get(node_return_type(node).name)
91
94
  rescue NameError
92
95
  end
93
96
  end
@@ -97,24 +100,56 @@ module GraphQLIncludable
97
100
  klass && klass < ActiveRecord::Base
98
101
  end
99
102
 
100
- # return raw contents, or contents wrapped in a hash (for delegated associations)
101
- def self.wrap_delegate(contents, delegate, delegate_key = delegate)
102
- return contents unless delegate
103
-
104
- obj = {}
105
- obj[delegate_key] = contents
106
- obj
103
+ def self.node_predicted_association_name(node)
104
+ definition = node.definitions[0]
105
+ specified_includes = definition.metadata[:includes]
106
+ if specified_includes.is_a?(Symbol)
107
+ specified_includes
108
+ else
109
+ (definition.property || definition.name).to_sym
110
+ end
107
111
  end
108
112
 
109
113
  # unwrap GraphQL ListType and NonNullType wrappers
110
- def self.unwrapped_type(node)
114
+ def self.node_return_type(node)
111
115
  type = node.return_type
112
116
  type = type.of_type while type.respond_to? :of_type
113
117
  type
114
118
  end
115
119
 
116
- def self.get_delegated_model(model, method_name)
117
- model.instance_variable_get('@delegate_cache').try(:[], method_name.to_sym)
120
+ def self.model_has_association?(model, association_name, includes_chain)
121
+ delegated_model = model_name_to_class(includes_chain.last) unless includes_chain.empty?
122
+ (delegated_model || model).reflect_on_association(association_name)
123
+ end
124
+
125
+ # get a 1d array of the chain of delegated model names,
126
+ # so if model A delegates method B to model C, which delegates method B to model D,
127
+ # delegated_includes_chain(A, :B) => [:C, :D]
128
+ def self.delegated_includes_chain(model, method_name)
129
+ chain = []
130
+ delegated_model_name = model.instance_variable_get('@delegate_cache').try(:[], method_name.to_sym)
131
+ while delegated_model_name
132
+ chain << delegated_model_name
133
+ delegated_model = model_name_to_class(delegated_model_name)
134
+ delegated_model_name = delegated_model.instance_variable_get('@delegate_cache').try(:[], method_name.to_sym)
135
+ end
136
+ chain
118
137
  end
119
138
 
139
+ # convert a 1d array into a nested hash
140
+ # e.g. [:foo, :bar, :baz] => { :foo => { :bar => :baz }}
141
+ def self.array_to_nested_hash(arr)
142
+ arr.reverse.inject { |acc, item| { item => acc } }
143
+ end
144
+
145
+ # convert a model name into a class variable,
146
+ # e.g. :search_parameters -> SearchParameters
147
+ def self.model_name_to_class(model_name)
148
+ begin
149
+ model = model_name.to_s.camelize.constantize
150
+ rescue NameError
151
+ model = model_name.to_s.singularize.camelize.constantize
152
+ end
153
+ model
154
+ end
120
155
  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.1.4
4
+ version: 0.1.5
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: 2017-11-08 00:00:00.000000000 Z
12
+ date: 2017-11-10 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: