Class: ActiveShopifyGraphQL::Query::QueryBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/active_shopify_graphql/query/query_builder.rb

Overview

Builds complete GraphQL queries from a LoaderContext. Handles both fragment construction and query wrapping. Delegates rendering to polymorphic Node subclasses.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(context) ⇒ QueryBuilder

Returns a new instance of QueryBuilder.



11
12
13
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 11

def initialize(context)
  @context = context
end

Instance Attribute Details

#contextObject (readonly)

Returns the value of attribute context.



9
10
11
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 9

def context
  @context
end

Class Method Details

.build_collection_query(context, query_name:, variables:, include_page_info: false) ⇒ Object



42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 42

def self.build_collection_query(context, query_name:, variables:, include_page_info: false)
  builder = new(context)
  fragment = builder.build_fragment

  Node::Collection.new(
    model_type: context.graphql_type,
    query_name: query_name,
    fragment_name: context.fragment_name,
    variables: variables,
    fragments: [fragment],
    include_page_info: include_page_info
  ).to_s
end

.build_connection_query(context, query_name:, variables:, parent_query: nil, singular: false) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 61

def self.build_connection_query(context, query_name:, variables:, parent_query: nil, singular: false)
  builder = new(context)
  fragment = builder.build_fragment

  if parent_query
    Node::NestedConnection.new(
      query_name: query_name,
      fragment_name: context.fragment_name,
      variables: variables,
      parent_query: parent_query,
      fragments: [fragment],
      singular: singular
    ).to_s
  else
    Node::RootConnection.new(
      query_name: query_name,
      fragment_name: context.fragment_name,
      variables: variables,
      fragments: [fragment],
      singular: singular
    ).to_s
  end
end

.build_current_customer_query(context, query_name: nil) ⇒ Object

Build a query that doesn't require an ID parameter (e.g., Customer Account API's current customer)



30
31
32
33
34
35
36
37
38
39
40
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 30

def self.build_current_customer_query(context, query_name: nil)
  builder = new(context)
  fragment = builder.build_fragment

  Node::CurrentCustomer.new(
    model_type: context.graphql_type,
    query_name: query_name || context.query_name,
    fragment_name: context.fragment_name,
    fragments: [fragment]
  ).to_s
end

.build_paginated_collection_query(context, query_name:, variables:) ⇒ Object

Build a paginated collection query that includes pageInfo for cursor-based pagination



57
58
59
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 57

def self.build_paginated_collection_query(context, query_name:, variables:)
  build_collection_query(context, query_name: query_name, variables: variables, include_page_info: true)
end

.build_single_record_query(context) ⇒ Object

Class-level factory methods for building complete queries ===



17
18
19
20
21
22
23
24
25
26
27
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 17

def self.build_single_record_query(context)
  builder = new(context)
  fragment = builder.build_fragment

  Node::SingleRecord.new(
    model_type: context.graphql_type,
    query_name: context.query_name,
    fragment_name: context.fragment_name,
    fragments: [fragment]
  ).to_s
end

.fragment_name(graphql_type) ⇒ Object



105
106
107
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 105

def self.fragment_name(graphql_type)
  "#{graphql_type}Fragment"
end

.normalize_includes(includes) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 85

def self.normalize_includes(includes)
  includes = Array(includes)
  includes.each_with_object({}) do |inc, normalized|
    case inc
    when Hash
      inc.each do |key, value|
        key = key.to_sym
        normalized[key] ||= []
        case value
        when Hash then normalized[key] << value
        when Array then normalized[key].concat(value)
        else normalized[key] << value
        end
      end
    when Symbol, String
      normalized[inc.to_sym] ||= []
    end
  end
end

Instance Method Details

#build_connection_nodesObject

Build Node objects for all connections



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 159

def build_connection_nodes
  return [] if @context.included_connections.empty?

  connections = @context.connections
  return [] if connections.empty?

  normalized_includes = self.class.normalize_includes(@context.included_connections)

  normalized_includes.filter_map do |connection_name, nested_includes|
    connection_config = connections[connection_name]
    next unless connection_config

    build_connection_node(connection_config, nested_includes)
  end
end

#build_field_nodesObject

Build field nodes from attribute definitions



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 130

def build_field_nodes
  path_tree = {}
  metafield_aliases = {}
  raw_graphql_nodes = []
  aliased_field_nodes = []

  # Build a tree structure for nested paths
  @context.defined_attributes.each do |attr_name, config|
    if config[:raw_graphql]
      raw_graphql_nodes << build_raw_graphql_node(attr_name, config[:raw_graphql])
    elsif config[:is_metafield]
      store_metafield_config(metafield_aliases, config)
    else
      path = config[:path]
      if path.include?('.')
        # Nested path - use tree structure (shared prefixes)
        build_path_tree(path_tree, path)
      else
        # Simple path - add aliased field node
        aliased_field_nodes << build_aliased_field_node(attr_name, path)
      end
    end
  end

  # Convert tree to Node objects
  nodes_from_tree(path_tree) + aliased_field_nodes + metafield_nodes(metafield_aliases) + raw_graphql_nodes
end

#build_fragmentObject

Build a complete fragment node with all fields and connections

Raises:

  • (NotImplementedError)


112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/active_shopify_graphql/query/query_builder.rb', line 112

def build_fragment
  raise NotImplementedError, "#{@context.loader_class} must define attributes" if @context.defined_attributes.empty?

  fragment_node = Node::Fragment.new(
    name: @context.fragment_name,
    arguments: { on: @context.graphql_type }
  )

  # Add field nodes from attributes
  build_field_nodes.each { |node| fragment_node.add_child(node) }

  # Add connection nodes
  build_connection_nodes.each { |node| fragment_node.add_child(node) }

  fragment_node
end