active_shopify_graphql 0.3.0 → 0.5.0
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/.rubocop.yml +4 -0
- data/README.md +187 -56
- data/lib/active_shopify_graphql/configuration.rb +2 -15
- data/lib/active_shopify_graphql/connections/connection_loader.rb +141 -0
- data/lib/active_shopify_graphql/gid_helper.rb +2 -0
- data/lib/active_shopify_graphql/graphql_associations.rb +245 -0
- data/lib/active_shopify_graphql/loader.rb +147 -126
- data/lib/active_shopify_graphql/loader_context.rb +2 -47
- data/lib/active_shopify_graphql/loader_proxy.rb +67 -0
- data/lib/active_shopify_graphql/loaders/admin_api_loader.rb +1 -17
- data/lib/active_shopify_graphql/loaders/customer_account_api_loader.rb +10 -26
- data/lib/active_shopify_graphql/model/associations.rb +94 -0
- data/lib/active_shopify_graphql/model/attributes.rb +48 -0
- data/lib/active_shopify_graphql/model/connections.rb +174 -0
- data/lib/active_shopify_graphql/model/finder_methods.rb +155 -0
- data/lib/active_shopify_graphql/model/graphql_type_resolver.rb +89 -0
- data/lib/active_shopify_graphql/model/loader_switchable.rb +79 -0
- data/lib/active_shopify_graphql/model/metafield_attributes.rb +59 -0
- data/lib/active_shopify_graphql/{base.rb → model.rb} +30 -16
- data/lib/active_shopify_graphql/model_builder.rb +53 -0
- data/lib/active_shopify_graphql/query/node/collection.rb +78 -0
- data/lib/active_shopify_graphql/query/node/connection.rb +16 -0
- data/lib/active_shopify_graphql/query/node/current_customer.rb +37 -0
- data/lib/active_shopify_graphql/query/node/field.rb +23 -0
- data/lib/active_shopify_graphql/query/node/fragment.rb +17 -0
- data/lib/active_shopify_graphql/query/node/nested_connection.rb +79 -0
- data/lib/active_shopify_graphql/query/node/raw.rb +15 -0
- data/lib/active_shopify_graphql/query/node/root_connection.rb +76 -0
- data/lib/active_shopify_graphql/query/node/single_record.rb +36 -0
- data/lib/active_shopify_graphql/query/node/singular.rb +16 -0
- data/lib/active_shopify_graphql/query/node.rb +95 -0
- data/lib/active_shopify_graphql/query/query_builder.rb +290 -0
- data/lib/active_shopify_graphql/query/relation.rb +424 -0
- data/lib/active_shopify_graphql/query/scope.rb +219 -0
- data/lib/active_shopify_graphql/response/page_info.rb +40 -0
- data/lib/active_shopify_graphql/response/paginated_result.rb +114 -0
- data/lib/active_shopify_graphql/response/response_mapper.rb +242 -0
- data/lib/active_shopify_graphql/search_query/hash_condition_formatter.rb +107 -0
- data/lib/active_shopify_graphql/search_query/parameter_binder.rb +68 -0
- data/lib/active_shopify_graphql/search_query/value_sanitizer.rb +24 -0
- data/lib/active_shopify_graphql/search_query.rb +34 -84
- data/lib/active_shopify_graphql/version.rb +1 -1
- data/lib/active_shopify_graphql.rb +30 -28
- metadata +47 -15
- data/lib/active_shopify_graphql/associations.rb +0 -90
- data/lib/active_shopify_graphql/attributes.rb +0 -50
- data/lib/active_shopify_graphql/connection_loader.rb +0 -96
- data/lib/active_shopify_graphql/connections.rb +0 -198
- data/lib/active_shopify_graphql/finder_methods.rb +0 -159
- data/lib/active_shopify_graphql/fragment_builder.rb +0 -228
- data/lib/active_shopify_graphql/graphql_type_resolver.rb +0 -91
- data/lib/active_shopify_graphql/includes_scope.rb +0 -48
- data/lib/active_shopify_graphql/loader_switchable.rb +0 -180
- data/lib/active_shopify_graphql/metafield_attributes.rb +0 -61
- data/lib/active_shopify_graphql/query_node.rb +0 -173
- data/lib/active_shopify_graphql/query_tree.rb +0 -225
- data/lib/active_shopify_graphql/response_mapper.rb +0 -249
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
module Query
|
|
5
|
+
class Node
|
|
6
|
+
# Renders a single record query by ID.
|
|
7
|
+
# Example:
|
|
8
|
+
# query getCustomer($id: ID!) {
|
|
9
|
+
# customer(id: $id) {
|
|
10
|
+
# ...CustomerFragment
|
|
11
|
+
# }
|
|
12
|
+
# }
|
|
13
|
+
class SingleRecord < Node
|
|
14
|
+
attr_reader :model_type, :query_name, :fragment_name, :fragments
|
|
15
|
+
|
|
16
|
+
def initialize(model_type:, query_name:, fragment_name:, fragments: [])
|
|
17
|
+
@model_type = model_type
|
|
18
|
+
@query_name = query_name
|
|
19
|
+
@fragment_name = fragment_name
|
|
20
|
+
@fragments = fragments
|
|
21
|
+
super(name: query_name)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def to_s
|
|
25
|
+
"#{fragments_string} query get#{model_type}($id: ID!) { #{query_name}(id: $id) { ...#{fragment_name} } }"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def fragments_string
|
|
31
|
+
@fragments.map(&:to_s).join(' ')
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
module Query
|
|
5
|
+
class Node
|
|
6
|
+
# Renders singular association fields (has_one relationships).
|
|
7
|
+
# Example: `defaultAddress { city country zip }`
|
|
8
|
+
class Singular < Node
|
|
9
|
+
def to_s
|
|
10
|
+
nested_fields = render_children
|
|
11
|
+
"#{field_name_with_alias}#{format_arguments} { #{nested_fields.join(' ')} }"
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
module Query
|
|
5
|
+
# Abstract base class for all GraphQL query nodes.
|
|
6
|
+
# Provides shared attributes and helper methods for node rendering.
|
|
7
|
+
#
|
|
8
|
+
# Subclasses:
|
|
9
|
+
# - Node::Field - Simple and nested fields
|
|
10
|
+
# - Node::Singular - Singular associations (has_one)
|
|
11
|
+
# - Node::Connection - Collection associations using nodes pattern
|
|
12
|
+
# - Node::Fragment - Fragment definitions
|
|
13
|
+
# - Node::Raw - Raw GraphQL strings
|
|
14
|
+
# - Node::SingleRecord - Single record queries by ID
|
|
15
|
+
# - Node::CurrentCustomer - ID-less queries (Customer Account API)
|
|
16
|
+
# - Node::Collection - Collection queries with optional pagination
|
|
17
|
+
# - Node::NestedConnection - Nested connection queries
|
|
18
|
+
# - Node::RootConnection - Root-level connection queries
|
|
19
|
+
class Node
|
|
20
|
+
# Shared constants for query formatting
|
|
21
|
+
PAGE_INFO_FIELDS = "pageInfo { hasNextPage hasPreviousPage startCursor endCursor }"
|
|
22
|
+
STRING_KEYS_NEEDING_QUOTES = %i[query after before].freeze
|
|
23
|
+
attr_reader :name, :alias_name, :arguments, :children
|
|
24
|
+
|
|
25
|
+
# @param name [String] The field name (e.g., 'id', 'displayName', 'orders')
|
|
26
|
+
# @param alias_name [String] Optional field alias (e.g., 'myAlias' for 'myAlias: fieldName')
|
|
27
|
+
# @param arguments [Hash] Field arguments (e.g., { first: 10, sortKey: 'CREATED_AT' })
|
|
28
|
+
# @param children [Array<Query::Node>] Child nodes for nested structures
|
|
29
|
+
def initialize(name:, alias_name: nil, arguments: {}, children: [])
|
|
30
|
+
@name = name
|
|
31
|
+
@alias_name = alias_name
|
|
32
|
+
@arguments = arguments
|
|
33
|
+
@children = children
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Add a child node
|
|
37
|
+
def add_child(node)
|
|
38
|
+
@children << node
|
|
39
|
+
node
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Check if node has children
|
|
43
|
+
def has_children?
|
|
44
|
+
@children.any?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Convert node to GraphQL string - must be implemented by subclasses
|
|
48
|
+
def to_s
|
|
49
|
+
raise NotImplementedError, "#{self.class} must implement #to_s"
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def field_name_with_alias
|
|
55
|
+
@alias_name ? "#{@alias_name}: #{@name}" : @name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def format_arguments
|
|
59
|
+
return "" if @arguments.empty?
|
|
60
|
+
|
|
61
|
+
# Filter out internal arguments (like :on for fragments)
|
|
62
|
+
graphql_args = @arguments.except(:on)
|
|
63
|
+
return "" if graphql_args.empty?
|
|
64
|
+
|
|
65
|
+
args = graphql_args.map do |key, value|
|
|
66
|
+
graphql_key = key.to_s.camelize(:lower)
|
|
67
|
+
formatted_value = format_argument_value(key, value)
|
|
68
|
+
"#{graphql_key}: #{formatted_value}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
"(#{args.join(', ')})"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def format_argument_value(key, value)
|
|
75
|
+
case value
|
|
76
|
+
when String
|
|
77
|
+
# Keys that need quoted string values
|
|
78
|
+
if %i[namespace key query].include?(key.to_sym)
|
|
79
|
+
"\"#{value}\""
|
|
80
|
+
else
|
|
81
|
+
value
|
|
82
|
+
end
|
|
83
|
+
when Symbol
|
|
84
|
+
value.to_s
|
|
85
|
+
else
|
|
86
|
+
value
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def render_children
|
|
91
|
+
@children.map(&:to_s)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveShopifyGraphQL
|
|
4
|
+
module Query
|
|
5
|
+
# Builds complete GraphQL queries from a LoaderContext.
|
|
6
|
+
# Handles both fragment construction and query wrapping.
|
|
7
|
+
# Delegates rendering to polymorphic Node subclasses.
|
|
8
|
+
class QueryBuilder
|
|
9
|
+
attr_reader :context
|
|
10
|
+
|
|
11
|
+
def initialize(context)
|
|
12
|
+
@context = context
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# === Class-level factory methods for building complete queries ===
|
|
16
|
+
|
|
17
|
+
def self.build_single_record_query(context)
|
|
18
|
+
builder = new(context)
|
|
19
|
+
fragment = builder.build_fragment
|
|
20
|
+
|
|
21
|
+
Node::SingleRecord.new(
|
|
22
|
+
model_type: context.graphql_type,
|
|
23
|
+
query_name: context.query_name,
|
|
24
|
+
fragment_name: context.fragment_name,
|
|
25
|
+
fragments: [fragment]
|
|
26
|
+
).to_s
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Build a query that doesn't require an ID parameter (e.g., Customer Account API's current customer)
|
|
30
|
+
def self.build_current_customer_query(context, query_name: nil)
|
|
31
|
+
builder = new(context)
|
|
32
|
+
fragment = builder.build_fragment
|
|
33
|
+
|
|
34
|
+
Node::CurrentCustomer.new(
|
|
35
|
+
model_type: context.graphql_type,
|
|
36
|
+
query_name: query_name || context.query_name,
|
|
37
|
+
fragment_name: context.fragment_name,
|
|
38
|
+
fragments: [fragment]
|
|
39
|
+
).to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.build_collection_query(context, query_name:, variables:, include_page_info: false)
|
|
43
|
+
builder = new(context)
|
|
44
|
+
fragment = builder.build_fragment
|
|
45
|
+
|
|
46
|
+
Node::Collection.new(
|
|
47
|
+
model_type: context.graphql_type,
|
|
48
|
+
query_name: query_name,
|
|
49
|
+
fragment_name: context.fragment_name,
|
|
50
|
+
variables: variables,
|
|
51
|
+
fragments: [fragment],
|
|
52
|
+
include_page_info: include_page_info
|
|
53
|
+
).to_s
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Build a paginated collection query that includes pageInfo for cursor-based pagination
|
|
57
|
+
def self.build_paginated_collection_query(context, query_name:, variables:)
|
|
58
|
+
build_collection_query(context, query_name: query_name, variables: variables, include_page_info: true)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.build_connection_query(context, query_name:, variables:, parent_query: nil, singular: false)
|
|
62
|
+
builder = new(context)
|
|
63
|
+
fragment = builder.build_fragment
|
|
64
|
+
|
|
65
|
+
if parent_query
|
|
66
|
+
Node::NestedConnection.new(
|
|
67
|
+
query_name: query_name,
|
|
68
|
+
fragment_name: context.fragment_name,
|
|
69
|
+
variables: variables,
|
|
70
|
+
parent_query: parent_query,
|
|
71
|
+
fragments: [fragment],
|
|
72
|
+
singular: singular
|
|
73
|
+
).to_s
|
|
74
|
+
else
|
|
75
|
+
Node::RootConnection.new(
|
|
76
|
+
query_name: query_name,
|
|
77
|
+
fragment_name: context.fragment_name,
|
|
78
|
+
variables: variables,
|
|
79
|
+
fragments: [fragment],
|
|
80
|
+
singular: singular
|
|
81
|
+
).to_s
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def self.normalize_includes(includes)
|
|
86
|
+
includes = Array(includes)
|
|
87
|
+
includes.each_with_object({}) do |inc, normalized|
|
|
88
|
+
case inc
|
|
89
|
+
when Hash
|
|
90
|
+
inc.each do |key, value|
|
|
91
|
+
key = key.to_sym
|
|
92
|
+
normalized[key] ||= []
|
|
93
|
+
case value
|
|
94
|
+
when Hash then normalized[key] << value
|
|
95
|
+
when Array then normalized[key].concat(value)
|
|
96
|
+
else normalized[key] << value
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
when Symbol, String
|
|
100
|
+
normalized[inc.to_sym] ||= []
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def self.fragment_name(graphql_type)
|
|
106
|
+
"#{graphql_type}Fragment"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# === Instance methods for building fragments ===
|
|
110
|
+
|
|
111
|
+
# Build a complete fragment node with all fields and connections
|
|
112
|
+
def build_fragment
|
|
113
|
+
raise NotImplementedError, "#{@context.loader_class} must define attributes" if @context.defined_attributes.empty?
|
|
114
|
+
|
|
115
|
+
fragment_node = Node::Fragment.new(
|
|
116
|
+
name: @context.fragment_name,
|
|
117
|
+
arguments: { on: @context.graphql_type }
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
# Add field nodes from attributes
|
|
121
|
+
build_field_nodes.each { |node| fragment_node.add_child(node) }
|
|
122
|
+
|
|
123
|
+
# Add connection nodes
|
|
124
|
+
build_connection_nodes.each { |node| fragment_node.add_child(node) }
|
|
125
|
+
|
|
126
|
+
fragment_node
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Build field nodes from attribute definitions
|
|
130
|
+
def build_field_nodes
|
|
131
|
+
path_tree = {}
|
|
132
|
+
metafield_aliases = {}
|
|
133
|
+
raw_graphql_nodes = []
|
|
134
|
+
aliased_field_nodes = []
|
|
135
|
+
|
|
136
|
+
# Build a tree structure for nested paths
|
|
137
|
+
@context.defined_attributes.each do |attr_name, config|
|
|
138
|
+
if config[:raw_graphql]
|
|
139
|
+
raw_graphql_nodes << build_raw_graphql_node(attr_name, config[:raw_graphql])
|
|
140
|
+
elsif config[:is_metafield]
|
|
141
|
+
store_metafield_config(metafield_aliases, config)
|
|
142
|
+
else
|
|
143
|
+
path = config[:path]
|
|
144
|
+
if path.include?('.')
|
|
145
|
+
# Nested path - use tree structure (shared prefixes)
|
|
146
|
+
build_path_tree(path_tree, path)
|
|
147
|
+
else
|
|
148
|
+
# Simple path - add aliased field node
|
|
149
|
+
aliased_field_nodes << build_aliased_field_node(attr_name, path)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Convert tree to Node objects
|
|
155
|
+
nodes_from_tree(path_tree) + aliased_field_nodes + metafield_nodes(metafield_aliases) + raw_graphql_nodes
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Build Node objects for all connections
|
|
159
|
+
def build_connection_nodes
|
|
160
|
+
return [] if @context.included_connections.empty?
|
|
161
|
+
|
|
162
|
+
connections = @context.connections
|
|
163
|
+
return [] if connections.empty?
|
|
164
|
+
|
|
165
|
+
normalized_includes = self.class.normalize_includes(@context.included_connections)
|
|
166
|
+
|
|
167
|
+
normalized_includes.filter_map do |connection_name, nested_includes|
|
|
168
|
+
connection_config = connections[connection_name]
|
|
169
|
+
next unless connection_config
|
|
170
|
+
|
|
171
|
+
build_connection_node(connection_config, nested_includes)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
def store_metafield_config(metafield_aliases, config)
|
|
178
|
+
alias_name = config[:metafield_alias]
|
|
179
|
+
value_field = config[:type] == :json ? 'jsonValue' : 'value'
|
|
180
|
+
|
|
181
|
+
metafield_aliases[alias_name] = {
|
|
182
|
+
namespace: config[:metafield_namespace],
|
|
183
|
+
key: config[:metafield_key],
|
|
184
|
+
value_field: value_field
|
|
185
|
+
}
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def build_raw_graphql_node(attr_name, raw_graphql)
|
|
189
|
+
# Prepend alias to raw GraphQL for predictable response mapping
|
|
190
|
+
aliased_raw_graphql = "#{attr_name}: #{raw_graphql}"
|
|
191
|
+
Node::Raw.new(
|
|
192
|
+
name: "raw",
|
|
193
|
+
arguments: { raw_graphql: aliased_raw_graphql }
|
|
194
|
+
)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def build_aliased_field_node(attr_name, path)
|
|
198
|
+
alias_name = attr_name.to_s
|
|
199
|
+
# Only add alias if the attr_name differs from the GraphQL field name
|
|
200
|
+
alias_name = nil if alias_name == path
|
|
201
|
+
Node::Field.new(name: path, alias_name: alias_name)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def build_path_tree(path_tree, path)
|
|
205
|
+
path_parts = path.split('.')
|
|
206
|
+
current_level = path_tree
|
|
207
|
+
|
|
208
|
+
path_parts.each_with_index do |part, index|
|
|
209
|
+
if index == path_parts.length - 1
|
|
210
|
+
current_level[part] = true
|
|
211
|
+
else
|
|
212
|
+
current_level[part] ||= {}
|
|
213
|
+
current_level = current_level[part]
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def nodes_from_tree(tree)
|
|
219
|
+
tree.map do |key, value|
|
|
220
|
+
if value == true
|
|
221
|
+
Node::Field.new(name: key)
|
|
222
|
+
else
|
|
223
|
+
children = nodes_from_tree(value)
|
|
224
|
+
Node::Field.new(name: key, children: children)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def metafield_nodes(metafield_aliases)
|
|
230
|
+
metafield_aliases.map do |alias_name, config|
|
|
231
|
+
value_node = Node::Field.new(name: config[:value_field])
|
|
232
|
+
Node::Field.new(
|
|
233
|
+
name: "metafield",
|
|
234
|
+
alias_name: alias_name,
|
|
235
|
+
arguments: { namespace: config[:namespace], key: config[:key] },
|
|
236
|
+
children: [value_node]
|
|
237
|
+
)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def build_connection_node(connection_config, nested_includes)
|
|
242
|
+
target_class = connection_config[:class_name].constantize
|
|
243
|
+
target_context = @context.for_model(target_class, new_connections: nested_includes)
|
|
244
|
+
|
|
245
|
+
# Build child nodes for the target model
|
|
246
|
+
child_nodes = build_target_field_nodes(target_context, nested_includes)
|
|
247
|
+
|
|
248
|
+
query_name = connection_config[:query_name]
|
|
249
|
+
original_name = connection_config[:original_name]
|
|
250
|
+
connection_type = connection_config[:type] || :connection
|
|
251
|
+
formatted_args = (connection_config[:default_arguments] || {}).transform_keys(&:to_sym)
|
|
252
|
+
|
|
253
|
+
# Add alias if the connection name differs from the query name
|
|
254
|
+
alias_name = original_name.to_s == query_name ? nil : original_name.to_s
|
|
255
|
+
|
|
256
|
+
if connection_type == :singular
|
|
257
|
+
Node::Singular.new(
|
|
258
|
+
name: query_name,
|
|
259
|
+
alias_name: alias_name,
|
|
260
|
+
arguments: formatted_args,
|
|
261
|
+
children: child_nodes
|
|
262
|
+
)
|
|
263
|
+
else
|
|
264
|
+
Node::Connection.new(
|
|
265
|
+
name: query_name,
|
|
266
|
+
alias_name: alias_name,
|
|
267
|
+
arguments: formatted_args,
|
|
268
|
+
children: child_nodes
|
|
269
|
+
)
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def build_target_field_nodes(target_context, nested_includes)
|
|
274
|
+
# Build attribute nodes
|
|
275
|
+
attribute_nodes = if target_context.defined_attributes.any?
|
|
276
|
+
QueryBuilder.new(target_context.for_model(target_context.model_class, new_connections: [])).build_field_nodes
|
|
277
|
+
else
|
|
278
|
+
[Node::Field.new(name: "id")]
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
# Build nested connection nodes
|
|
282
|
+
return attribute_nodes if nested_includes.empty?
|
|
283
|
+
|
|
284
|
+
nested_builder = QueryBuilder.new(target_context)
|
|
285
|
+
nested_connection_nodes = nested_builder.build_connection_nodes
|
|
286
|
+
attribute_nodes + nested_connection_nodes
|
|
287
|
+
end
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
end
|