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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/README.md +187 -56
  4. data/lib/active_shopify_graphql/configuration.rb +2 -15
  5. data/lib/active_shopify_graphql/connections/connection_loader.rb +141 -0
  6. data/lib/active_shopify_graphql/gid_helper.rb +2 -0
  7. data/lib/active_shopify_graphql/graphql_associations.rb +245 -0
  8. data/lib/active_shopify_graphql/loader.rb +147 -126
  9. data/lib/active_shopify_graphql/loader_context.rb +2 -47
  10. data/lib/active_shopify_graphql/loader_proxy.rb +67 -0
  11. data/lib/active_shopify_graphql/loaders/admin_api_loader.rb +1 -17
  12. data/lib/active_shopify_graphql/loaders/customer_account_api_loader.rb +10 -26
  13. data/lib/active_shopify_graphql/model/associations.rb +94 -0
  14. data/lib/active_shopify_graphql/model/attributes.rb +48 -0
  15. data/lib/active_shopify_graphql/model/connections.rb +174 -0
  16. data/lib/active_shopify_graphql/model/finder_methods.rb +155 -0
  17. data/lib/active_shopify_graphql/model/graphql_type_resolver.rb +89 -0
  18. data/lib/active_shopify_graphql/model/loader_switchable.rb +79 -0
  19. data/lib/active_shopify_graphql/model/metafield_attributes.rb +59 -0
  20. data/lib/active_shopify_graphql/{base.rb → model.rb} +30 -16
  21. data/lib/active_shopify_graphql/model_builder.rb +53 -0
  22. data/lib/active_shopify_graphql/query/node/collection.rb +78 -0
  23. data/lib/active_shopify_graphql/query/node/connection.rb +16 -0
  24. data/lib/active_shopify_graphql/query/node/current_customer.rb +37 -0
  25. data/lib/active_shopify_graphql/query/node/field.rb +23 -0
  26. data/lib/active_shopify_graphql/query/node/fragment.rb +17 -0
  27. data/lib/active_shopify_graphql/query/node/nested_connection.rb +79 -0
  28. data/lib/active_shopify_graphql/query/node/raw.rb +15 -0
  29. data/lib/active_shopify_graphql/query/node/root_connection.rb +76 -0
  30. data/lib/active_shopify_graphql/query/node/single_record.rb +36 -0
  31. data/lib/active_shopify_graphql/query/node/singular.rb +16 -0
  32. data/lib/active_shopify_graphql/query/node.rb +95 -0
  33. data/lib/active_shopify_graphql/query/query_builder.rb +290 -0
  34. data/lib/active_shopify_graphql/query/relation.rb +424 -0
  35. data/lib/active_shopify_graphql/query/scope.rb +219 -0
  36. data/lib/active_shopify_graphql/response/page_info.rb +40 -0
  37. data/lib/active_shopify_graphql/response/paginated_result.rb +114 -0
  38. data/lib/active_shopify_graphql/response/response_mapper.rb +242 -0
  39. data/lib/active_shopify_graphql/search_query/hash_condition_formatter.rb +107 -0
  40. data/lib/active_shopify_graphql/search_query/parameter_binder.rb +68 -0
  41. data/lib/active_shopify_graphql/search_query/value_sanitizer.rb +24 -0
  42. data/lib/active_shopify_graphql/search_query.rb +34 -84
  43. data/lib/active_shopify_graphql/version.rb +1 -1
  44. data/lib/active_shopify_graphql.rb +30 -28
  45. metadata +47 -15
  46. data/lib/active_shopify_graphql/associations.rb +0 -90
  47. data/lib/active_shopify_graphql/attributes.rb +0 -50
  48. data/lib/active_shopify_graphql/connection_loader.rb +0 -96
  49. data/lib/active_shopify_graphql/connections.rb +0 -198
  50. data/lib/active_shopify_graphql/finder_methods.rb +0 -159
  51. data/lib/active_shopify_graphql/fragment_builder.rb +0 -228
  52. data/lib/active_shopify_graphql/graphql_type_resolver.rb +0 -91
  53. data/lib/active_shopify_graphql/includes_scope.rb +0 -48
  54. data/lib/active_shopify_graphql/loader_switchable.rb +0 -180
  55. data/lib/active_shopify_graphql/metafield_attributes.rb +0 -61
  56. data/lib/active_shopify_graphql/query_node.rb +0 -173
  57. data/lib/active_shopify_graphql/query_tree.rb +0 -225
  58. data/lib/active_shopify_graphql/response_mapper.rb +0 -249
@@ -1,180 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveShopifyGraphQL
4
- # Provides capability to switch between different loaders within the same model
5
- module LoaderSwitchable
6
- extend ActiveSupport::Concern
7
-
8
- # Generic method to execute with a specific loader
9
- # @param loader_class [Class] The loader class to use
10
- # @yield [Object] Block to execute with the loader
11
- # @return [Object] Result of the block
12
- def with_loader(loader_class, &_block)
13
- old_loader = Thread.current[:active_shopify_graphql_loader]
14
- Thread.current[:active_shopify_graphql_loader] = loader_class.new(self.class)
15
-
16
- if block_given?
17
- yield(self)
18
- else
19
- self
20
- end
21
- ensure
22
- Thread.current[:active_shopify_graphql_loader] = old_loader
23
- end
24
-
25
- # Executes with the admin API loader
26
- # @return [self]
27
- def with_admin_api(&block)
28
- with_loader(ActiveShopifyGraphQL::Loaders::AdminApiLoader, &block)
29
- end
30
-
31
- # Executes with the customer account API loader
32
- # @return [self]
33
- def with_customer_account_api(&block)
34
- with_loader(ActiveShopifyGraphQL::Loaders::CustomerAccountApiLoader, &block)
35
- end
36
-
37
- class_methods do
38
- # @!method use_loader(loader_class)
39
- # Sets the default loader class for this model.
40
- #
41
- # @param loader_class [Class] The loader class to use as default
42
- # @example
43
- # class Customer < ActiveRecord::Base
44
- # use_loader ActiveShopifyGraphQL::Loaders::CustomerAccountApiLoader
45
- # end
46
- def use_loader(loader_class)
47
- @default_loader_class = loader_class
48
- end
49
-
50
- # Define loader-specific attribute and graphql_type overrides
51
- # @param loader_class [Class] The loader class to override attributes for
52
- def for_loader(loader_class, &block)
53
- @current_loader_context = loader_class
54
- @loader_contexts ||= {}
55
- @loader_contexts[loader_class] ||= {}
56
- instance_eval(&block) if block_given?
57
- @current_loader_context = nil
58
- end
59
-
60
- # Class-level method to execute with admin API loader
61
- # @return [LoaderProxy] Proxy object with find method
62
- def with_admin_api
63
- LoaderProxy.new(self, ActiveShopifyGraphQL::Loaders::AdminApiLoader.new(self))
64
- end
65
-
66
- # Class-level method to execute with customer account API loader
67
- # @return [LoaderProxy] Proxy object with find method
68
- def with_customer_account_api(token = nil)
69
- LoaderProxy.new(self, ActiveShopifyGraphQL::Loaders::CustomerAccountApiLoader.new(self, token))
70
- end
71
-
72
- private
73
-
74
- # Returns the default loader class (either set via DSL or inferred)
75
- # @return [Class] The default loader class
76
- def default_loader_class
77
- @default_loader_class ||= ActiveShopifyGraphQL::Loaders::AdminApiLoader
78
- end
79
- end
80
-
81
- # Simple proxy class to handle loader delegation
82
- class LoaderProxy
83
- def initialize(model_class, loader, included_connections: [], selected_attributes: nil)
84
- @model_class = model_class
85
- @loader = loader
86
- @included_connections = included_connections
87
- @selected_attributes = selected_attributes
88
- end
89
-
90
- def includes(*connection_names)
91
- # Validate connections exist
92
- @model_class.send(:validate_includes_connections!, connection_names) if @model_class.respond_to?(:validate_includes_connections!, true)
93
-
94
- # Collect connections with eager_load: true
95
- auto_included_connections = @model_class.connections.select { |_name, config| config[:eager_load] }.keys
96
-
97
- # Merge manual and automatic connections
98
- all_included_connections = (@included_connections + connection_names + auto_included_connections).uniq
99
-
100
- # Create a new loader with the included connections
101
- new_loader = @loader.class.new(
102
- @model_class,
103
- *loader_extra_args,
104
- selected_attributes: @selected_attributes,
105
- included_connections: all_included_connections
106
- )
107
-
108
- LoaderProxy.new(
109
- @model_class,
110
- new_loader,
111
- included_connections: all_included_connections,
112
- selected_attributes: @selected_attributes
113
- )
114
- end
115
-
116
- def select(*attribute_names)
117
- new_selected = attribute_names.map(&:to_sym)
118
-
119
- # Create a new loader with the selected attributes
120
- new_loader = @loader.class.new(
121
- @model_class,
122
- *loader_extra_args,
123
- selected_attributes: new_selected,
124
- included_connections: @included_connections
125
- )
126
-
127
- LoaderProxy.new(
128
- @model_class,
129
- new_loader,
130
- included_connections: @included_connections,
131
- selected_attributes: new_selected
132
- )
133
- end
134
-
135
- def find(id = nil)
136
- # For Customer Account API, if no ID is provided, load the current customer
137
- if id.nil? && @loader.is_a?(ActiveShopifyGraphQL::Loaders::CustomerAccountApiLoader)
138
- attributes = @loader.load_attributes
139
- return nil if attributes.nil?
140
-
141
- return @model_class.new(attributes)
142
- end
143
-
144
- # For other cases, require ID and use standard flow
145
- return nil if id.nil?
146
-
147
- gid = GidHelper.normalize_gid(id, @model_class.model_name.name.demodulize)
148
-
149
- attributes = @loader.load_attributes(gid)
150
- return nil if attributes.nil?
151
-
152
- @model_class.new(attributes)
153
- end
154
-
155
- # Delegate where to the model class with the specific loader
156
- def where(*args, **options)
157
- @model_class.where(*args, **options.merge(loader: @loader))
158
- end
159
-
160
- attr_reader :loader
161
-
162
- def inspect
163
- "#{@model_class.name}(with_#{@loader.class.name.demodulize})"
164
- end
165
- alias to_s inspect
166
-
167
- private
168
-
169
- # Returns extra arguments needed when creating a new loader of the same type
170
- # For CustomerAccountApiLoader, this includes the token
171
- def loader_extra_args
172
- if @loader.is_a?(ActiveShopifyGraphQL::Loaders::CustomerAccountApiLoader)
173
- [@loader.instance_variable_get(:@token)]
174
- else
175
- []
176
- end
177
- end
178
- end
179
- end
180
- end
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveShopifyGraphQL
4
- module MetafieldAttributes
5
- extend ActiveSupport::Concern
6
-
7
- class_methods do
8
- # Define a metafield attribute for this model.
9
- #
10
- # @param name [Symbol] The Ruby attribute name
11
- # @param namespace [String] The metafield namespace
12
- # @param key [String] The metafield key
13
- # @param type [Symbol] The type for coercion (:string, :integer, :float, :boolean, :datetime, :json)
14
- # @param null [Boolean] Whether the attribute can be null (default: true)
15
- # @param default [Object] Default value when GraphQL response is nil
16
- # @param transform [Proc] Custom transform block for the value
17
- def metafield_attribute(name, namespace:, key:, type: :string, null: true, default: nil, transform: nil)
18
- @metafields ||= {}
19
- @metafields[name] = { namespace: namespace, key: key, type: type }
20
-
21
- # Build metafield config
22
- alias_name = "#{infer_path(name)}Metafield"
23
- value_field = type == :json ? 'jsonValue' : 'value'
24
- path = "#{alias_name}.#{value_field}"
25
-
26
- config = {
27
- path: path,
28
- type: type,
29
- null: null,
30
- default: default,
31
- transform: transform,
32
- is_metafield: true,
33
- metafield_alias: alias_name,
34
- metafield_namespace: namespace,
35
- metafield_key: key
36
- }
37
-
38
- if @current_loader_context
39
- @loader_contexts[@current_loader_context][name] = config
40
- else
41
- @base_attributes ||= {}
42
- @base_attributes[name] = config
43
- end
44
-
45
- attr_accessor name unless method_defined?(name) || method_defined?("#{name}=")
46
- end
47
-
48
- # Get metafields defined for this model
49
- def metafields
50
- @metafields || {}
51
- end
52
-
53
- private
54
-
55
- # Infer GraphQL path from Ruby attribute name (delegates to Attributes if available)
56
- def infer_path(name)
57
- name.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
58
- end
59
- end
60
- end
61
- end
@@ -1,173 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Represents a node in the GraphQL query tree
4
- # This allows building the complete query structure before converting to a string
5
- class QueryNode
6
- attr_reader :name, :arguments, :children, :node_type, :alias_name
7
-
8
- # @param name [String] The field name (e.g., 'id', 'displayName', 'orders')
9
- # @param alias_name [String] Optional field alias (e.g., 'myAlias' for 'myAlias: fieldName')
10
- # @param arguments [Hash] Field arguments (e.g., { first: 10, sortKey: 'CREATED_AT' })
11
- # @param node_type [Symbol] Type of node: :field, :connection, :singular, :fragment
12
- # @param children [Array<QueryNode>] Child nodes for nested structures
13
- def initialize(name:, alias_name: nil, arguments: {}, node_type: :field, children: [])
14
- @name = name
15
- @alias_name = alias_name
16
- @arguments = arguments
17
- @node_type = node_type
18
- @children = children
19
- end
20
-
21
- # Add a child node
22
- def add_child(node)
23
- @children << node
24
- node
25
- end
26
-
27
- # Check if node has children
28
- def has_children?
29
- @children.any?
30
- end
31
-
32
- # Convert node to GraphQL string
33
- def to_s(indent_level: 0)
34
- case @node_type
35
- when :field
36
- render_field(indent_level: indent_level)
37
- when :connection
38
- render_connection(indent_level: indent_level)
39
- when :singular
40
- render_singular(indent_level: indent_level)
41
- when :fragment
42
- render_fragment
43
- when :raw
44
- render_raw
45
- else
46
- raise ArgumentError, "Unknown node type: #{@node_type}"
47
- end
48
- end
49
-
50
- private
51
-
52
- def compact?
53
- ActiveShopifyGraphQL.configuration.compact_queries
54
- end
55
-
56
- def render_field(indent_level:)
57
- # Simple field with no children
58
- field_name = @alias_name ? "#{@alias_name}: #{@name}" : @name
59
- args_string = format_arguments
60
- full_name = "#{field_name}#{args_string}"
61
-
62
- return full_name unless has_children?
63
-
64
- # Field with nested structure (e.g., defaultAddress { city })
65
- indent = compact? ? "" : " " * indent_level
66
- nested_indent = compact? ? "" : " " * (indent_level + 1)
67
-
68
- nested_fields = @children.map { |child| child.to_s(indent_level: indent_level + 1) }
69
-
70
- if compact?
71
- "#{full_name} { #{nested_fields.join(' ')} }"
72
- else
73
- separator = "\n"
74
- "#{full_name} {#{separator}#{nested_indent}#{nested_fields.join("\n#{nested_indent}")}#{separator}#{indent}}"
75
- end
76
- end
77
-
78
- def render_connection(indent_level:)
79
- args_string = format_arguments
80
-
81
- indent = compact? ? "" : " " * indent_level
82
- nested_indent = compact? ? "" : " " * (indent_level + 1)
83
-
84
- # Build nested fields from children
85
- nested_fields = @children.map { |child| child.to_s(indent_level: indent_level + 2) }
86
- fields_string = nested_fields.join(compact? ? " " : "\n#{nested_indent} ")
87
-
88
- # Include alias if present
89
- field_name = @alias_name ? "#{@alias_name}: #{@name}" : @name
90
-
91
- if compact?
92
- "#{field_name}#{args_string} { edges { node { #{fields_string} } } }"
93
- else
94
- <<~GRAPHQL.strip
95
- #{field_name}#{args_string} {
96
- #{nested_indent}edges {
97
- #{nested_indent} node {
98
- #{nested_indent} #{fields_string}
99
- #{nested_indent} }
100
- #{nested_indent}}
101
- #{indent}}
102
- GRAPHQL
103
- end
104
- end
105
-
106
- def render_singular(indent_level:)
107
- args_string = format_arguments
108
-
109
- indent = compact? ? "" : " " * indent_level
110
- nested_indent = compact? ? "" : " " * (indent_level + 1)
111
-
112
- # Build nested fields from children
113
- nested_fields = @children.map { |child| child.to_s(indent_level: indent_level + 1) }
114
- fields_string = nested_fields.join(compact? ? " " : "\n#{nested_indent}")
115
-
116
- # Include alias if present
117
- field_name = @alias_name ? "#{@alias_name}: #{@name}" : @name
118
-
119
- if compact?
120
- "#{field_name}#{args_string} { #{fields_string} }"
121
- else
122
- "#{field_name}#{args_string} {\n#{nested_indent}#{fields_string}\n#{indent}}"
123
- end
124
- end
125
-
126
- def render_fragment
127
- # Fragment fields are the children
128
- fields = @children.map { |child| child.to_s(indent_level: 0) }
129
- all_fields = fields.join(compact? ? " " : "\n")
130
-
131
- if compact?
132
- "fragment #{@name} on #{@arguments[:on]} { #{all_fields} }"
133
- else
134
- "fragment #{@name} on #{@arguments[:on]} {\n#{all_fields}\n}"
135
- end
136
- end
137
-
138
- def render_raw
139
- # Raw GraphQL string stored in arguments[:raw_graphql]
140
- @arguments[:raw_graphql]
141
- end
142
-
143
- def format_arguments
144
- return "" if @arguments.empty?
145
-
146
- args = @arguments.map do |key, value|
147
- # Convert Ruby snake_case to GraphQL camelCase
148
- graphql_key = key.to_s.camelize(:lower)
149
-
150
- # Format value based on type
151
- formatted_value = case value
152
- when String
153
- # Check if it needs quotes (query parameter vs enum values)
154
- # For metafields and most strings, add quotes
155
- if %i[namespace key].include?(key.to_sym)
156
- "\"#{value}\""
157
- elsif key.to_sym == :query
158
- "\"#{value}\""
159
- else
160
- value
161
- end
162
- when Symbol
163
- value.to_s
164
- else
165
- value
166
- end
167
-
168
- "#{graphql_key}: #{formatted_value}"
169
- end
170
-
171
- "(#{args.join(', ')})"
172
- end
173
- end
@@ -1,225 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveShopifyGraphQL
4
- # Builds complete GraphQL queries from a LoaderContext.
5
- # Refactored for Single Responsibility - only handles query string generation.
6
- # Fragment building is delegated to FragmentBuilder.
7
- class QueryTree
8
- attr_reader :context
9
-
10
- def initialize(context)
11
- @context = context
12
- @fragments = []
13
- @query_config = {}
14
- end
15
-
16
- # Class-level factory methods for building complete queries
17
-
18
- def self.build_single_record_query(context)
19
- new(context).tap do |tree|
20
- tree.add_fragment(FragmentBuilder.new(context).build)
21
- tree.set_query_config(
22
- type: :single_record,
23
- model_type: context.graphql_type,
24
- query_name: context.query_name,
25
- fragment_name: context.fragment_name
26
- )
27
- end.to_s
28
- end
29
-
30
- # Build a query that doesn't require an ID parameter (e.g., Customer Account API's current customer)
31
- def self.build_current_customer_query(context, query_name: nil)
32
- new(context).tap do |tree|
33
- tree.add_fragment(FragmentBuilder.new(context).build)
34
- tree.set_query_config(
35
- type: :current_customer,
36
- model_type: context.graphql_type,
37
- query_name: query_name || context.query_name,
38
- fragment_name: context.fragment_name
39
- )
40
- end.to_s
41
- end
42
-
43
- def self.build_collection_query(context, query_name:, variables:, connection_type: :nodes_only)
44
- new(context).tap do |tree|
45
- tree.add_fragment(FragmentBuilder.new(context).build)
46
- tree.set_query_config(
47
- type: :collection,
48
- model_type: context.graphql_type,
49
- query_name: query_name,
50
- fragment_name: context.fragment_name,
51
- variables: variables,
52
- connection_type: connection_type
53
- )
54
- end.to_s
55
- end
56
-
57
- def self.build_connection_query(context, query_name:, variables:, parent_query: nil, connection_type: :connection)
58
- new(context).tap do |tree|
59
- tree.add_fragment(FragmentBuilder.new(context).build)
60
- tree.set_query_config(
61
- type: :connection,
62
- query_name: query_name,
63
- fragment_name: context.fragment_name,
64
- variables: variables,
65
- parent_query: parent_query,
66
- connection_type: connection_type
67
- )
68
- end.to_s
69
- end
70
-
71
- # Delegate normalize_includes to FragmentBuilder
72
- def self.normalize_includes(includes)
73
- FragmentBuilder.normalize_includes(includes)
74
- end
75
-
76
- def self.fragment_name(graphql_type)
77
- "#{graphql_type}Fragment"
78
- end
79
-
80
- def add_fragment(fragment_node)
81
- @fragments << fragment_node
82
- end
83
-
84
- def set_query_config(config)
85
- @query_config = config
86
- end
87
-
88
- def to_s
89
- case @query_config[:type]
90
- when :single_record then render_single_record_query
91
- when :current_customer then render_current_customer_query
92
- when :collection then render_collection_query
93
- when :connection then render_connection_query
94
- else ""
95
- end
96
- end
97
-
98
- private
99
-
100
- def compact?
101
- ActiveShopifyGraphQL.configuration.compact_queries
102
- end
103
-
104
- def fragments_string
105
- @fragments.map(&:to_s).join(compact? ? " " : "\n\n")
106
- end
107
-
108
- def field_signature(variables)
109
- params = build_field_parameters(variables.compact)
110
- params.empty? ? "" : "(#{params.join(', ')})"
111
- end
112
-
113
- def render_single_record_query
114
- type = @query_config[:model_type]
115
- query_name = @query_config[:query_name]
116
- fragment_name = @query_config[:fragment_name]
117
-
118
- if compact?
119
- "#{fragments_string} query get#{type}($id: ID!) { #{query_name}(id: $id) { ...#{fragment_name} } }"
120
- else
121
- "#{fragments_string}\n\nquery get#{type}($id: ID!) {\n #{query_name}(id: $id) {\n ...#{fragment_name}\n }\n}\n"
122
- end
123
- end
124
-
125
- def render_current_customer_query
126
- type = @query_config[:model_type]
127
- query_name = @query_config[:query_name]
128
- fragment_name = @query_config[:fragment_name]
129
-
130
- if compact?
131
- "#{fragments_string} query getCurrent#{type} { #{query_name} { ...#{fragment_name} } }"
132
- else
133
- "#{fragments_string}\n\nquery getCurrent#{type} {\n #{query_name} {\n ...#{fragment_name}\n }\n}\n"
134
- end
135
- end
136
-
137
- def render_collection_query
138
- type = @query_config[:model_type]
139
- query_name = @query_config[:query_name]
140
- fragment_name = @query_config[:fragment_name]
141
- variables = @query_config[:variables] || {}
142
- connection_type = @query_config[:connection_type] || :nodes_only
143
-
144
- field_sig = field_signature(variables)
145
-
146
- if compact?
147
- body = wrap_connection_body_compact(fragment_name, connection_type)
148
- "#{fragments_string} query get#{type.pluralize} { #{query_name}#{field_sig} { #{body} } }"
149
- else
150
- body = wrap_connection_body_formatted(fragment_name, connection_type, 2)
151
- "#{fragments_string}\nquery get#{type.pluralize} {\n #{query_name}#{field_sig} {\n#{body}\n }\n}\n"
152
- end
153
- end
154
-
155
- def render_connection_query
156
- query_name = @query_config[:query_name]
157
- fragment_name = @query_config[:fragment_name]
158
- variables = @query_config[:variables] || {}
159
- parent_query = @query_config[:parent_query]
160
- connection_type = @query_config[:connection_type] || :connection
161
-
162
- field_sig = field_signature(variables)
163
-
164
- if parent_query
165
- render_nested_connection_query(query_name, fragment_name, field_sig, parent_query, connection_type)
166
- else
167
- render_root_connection_query(query_name, fragment_name, field_sig, connection_type)
168
- end
169
- end
170
-
171
- def render_nested_connection_query(query_name, fragment_name, field_sig, parent_query, connection_type)
172
- if compact?
173
- body = wrap_connection_body_compact(fragment_name, connection_type)
174
- "#{fragments_string} query($id: ID!) { #{parent_query} { #{query_name}#{field_sig} { #{body} } } }"
175
- else
176
- body = wrap_connection_body_formatted(fragment_name, connection_type, 3)
177
- "#{fragments_string}\nquery($id: ID!) {\n #{parent_query} {\n #{query_name}#{field_sig} {\n#{body}\n }\n }\n}\n"
178
- end
179
- end
180
-
181
- def render_root_connection_query(query_name, fragment_name, field_sig, connection_type)
182
- if compact?
183
- body = wrap_connection_body_compact(fragment_name, connection_type)
184
- "#{fragments_string} query { #{query_name}#{field_sig} { #{body} } }"
185
- else
186
- body = wrap_connection_body_formatted(fragment_name, connection_type, 2)
187
- "#{fragments_string}\nquery {\n #{query_name}#{field_sig} {\n#{body}\n }\n}\n"
188
- end
189
- end
190
-
191
- def wrap_connection_body_compact(fragment_name, connection_type)
192
- case connection_type
193
- when :singular then "...#{fragment_name}"
194
- when :nodes_only then "nodes { ...#{fragment_name} }"
195
- else "edges { node { ...#{fragment_name} } }"
196
- end
197
- end
198
-
199
- def wrap_connection_body_formatted(fragment_name, connection_type, indent_level)
200
- indent = " " * indent_level
201
- case connection_type
202
- when :singular
203
- "#{indent}...#{fragment_name}"
204
- when :nodes_only
205
- "#{indent}nodes {\n#{indent} ...#{fragment_name}\n#{indent}}"
206
- else
207
- "#{indent}edges {\n#{indent} node {\n#{indent} ...#{fragment_name}\n#{indent} }\n#{indent}}"
208
- end
209
- end
210
-
211
- def build_field_parameters(variables)
212
- variables.map do |key, value|
213
- "#{key.to_s.camelize(:lower)}: #{format_inline_value(key, value)}"
214
- end
215
- end
216
-
217
- def format_inline_value(key, value)
218
- case value
219
- when Integer, TrueClass, FalseClass then value.to_s
220
- when String then key.to_sym == :query ? "\"#{value}\"" : value
221
- else value.to_s
222
- end
223
- end
224
- end
225
- end