Skip to content

Commit 84f2d9b

Browse files
committed
Defer field validation for metaobjects
Updates schema execution to defer field validation for cross-referencing metaobjects, preventing creation failure when a reference field points to a definition not yet created. The metaobject is created, and the validation update is deferred. Also, access management is updated to skip Admin access settings, as they are not settable via the GraphQL API, preserving only storefront access.
1 parent d1de45d commit 84f2d9b

File tree

2 files changed

+130
-17
lines changed

2 files changed

+130
-17
lines changed

lib/shopify_toolkit/metaobject_statements.rb

Lines changed: 129 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,38 @@ module ShopifyToolkit::MetaobjectStatements
88
include ShopifyToolkit::AdminClient
99
include ShopifyToolkit::MetaobjectUtilities
1010

11+
@@pending_field_validations = []
12+
1113
def self.log_time(method_name)
1214
current_method = instance_method(method_name)
1315
define_method(method_name) do |*args, **kwargs, &block|
1416
say_with_time("#{method_name}(#{args.map(&:inspect).join(', ')})") { current_method.bind(self).call(*args, **kwargs, &block) }
1517
end
1618
end
1719

18-
# create_metafield :products, :my_metafield, :single_line_text_field, name: "Prova"
19-
# @param namespace: if nil the metafield will be app-specific (default: :custom)
20+
def apply_pending_field_validations
21+
return if @@pending_field_validations.empty?
22+
23+
say "Applying #{@@pending_field_validations.size} pending field validations"
24+
25+
@@pending_field_validations.reject! do |item|
26+
metaobject_type = item[:metaobject_type]
27+
field_key = item[:field_key]
28+
validations = item[:validations]
29+
30+
begin
31+
success = add_field_validations_to_metaobject(metaobject_type, field_key, validations, item)
32+
unless success
33+
say "-- Deferring field '#{field_key}' in '#{metaobject_type}' (missing dependencies)"
34+
end
35+
success
36+
rescue StandardError => e
37+
say "-- Failed to process field '#{field_key}' in '#{metaobject_type}': #{e.message}"
38+
true # Remove from array (don't retry errors)
39+
end
40+
end
41+
end
42+
2043
log_time \
2144
def create_metaobject_definition(type, **options)
2245
# Skip creation if metaobject already exists
@@ -29,27 +52,116 @@ def create_metaobject_definition(type, **options)
2952
# Transform options for GraphQL API
3053
definition = options.merge(type: type.to_s)
3154

32-
# Convert field_definitions to fieldDefinitions and transform field structure
33-
if options[:field_definitions]
34-
definition[:fieldDefinitions] = options[:field_definitions].map do |field|
35-
field_def = {
36-
key: field[:key].to_s,
37-
name: field[:name],
38-
type: field[:type].to_s
39-
}
40-
field_def[:description] = field[:description] if field[:description]
41-
field_def[:required] = field[:required] if field[:required]
55+
begin
56+
# Convert field_definitions to fieldDefinitions and transform field structure
57+
if options[:field_definitions]
58+
definition[:fieldDefinitions] = options[:field_definitions].filter_map do |field|
59+
field_def = build_field_definition(field)
60+
field_needs_validations = is_metaobject_reference_type?(field[:type])
61+
62+
if field[:validations]
63+
begin
64+
converted_validations = convert_validations_types_to_gids(field[:validations])
65+
field_def[:validations] = converted_validations if converted_validations&.any?
66+
rescue RuntimeError => e
67+
if e.message.include?("not found")
68+
@@pending_field_validations << {
69+
metaobject_type: type,
70+
field_key: field[:key],
71+
field_definition: field,
72+
validations: field[:validations]
73+
}
74+
say "Deferring field '#{field[:key]}' in '#{type}' (missing dependency)"
75+
76+
if field_needs_validations
77+
next # Skip this field entirely
78+
end
79+
end
80+
end
81+
elsif field_needs_validations
82+
next # Skip fields that need validations but don't have any
83+
end
84+
85+
field_def
86+
end
87+
88+
definition.delete(:field_definitions)
89+
end
90+
91+
# Remove admin access to avoid API restrictions
92+
if definition[:access]&.is_a?(Hash)
93+
definition[:access] = definition[:access].dup
94+
definition[:access].delete("admin") if definition[:access]["admin"]
95+
definition.delete(:access) if definition[:access].empty?
96+
end
4297

43-
# Convert validations from types to GIDs if present
44-
if field[:validations]
45-
converted_validations = convert_validations_types_to_gids(field[:validations])
46-
field_def[:validations] = converted_validations if converted_validations&.any?
98+
# Clean up empty validations arrays that cause API errors
99+
if definition[:fieldDefinitions]
100+
definition[:fieldDefinitions].each do |field_def|
101+
field_def.delete(:validations) if field_def[:validations]&.empty?
47102
end
103+
end
104+
105+
result = create_metaobject_definition_immediate(definition)
106+
result
107+
end
108+
end
48109

49-
field_def
110+
def add_field_validations_to_metaobject(metaobject_type, field_key, validations, item = nil)
111+
# Get the existing metaobject definition
112+
existing_gid = get_metaobject_definition_gid(metaobject_type)
113+
unless existing_gid
114+
say "Error: Cannot add validations to '#{metaobject_type}' - metaobject not found"
115+
return false
116+
end
117+
118+
begin
119+
converted_validations = convert_validations_types_to_gids(validations)
120+
121+
if converted_validations&.any?
122+
# Use the passed item, or try to find it (for backward compatibility)
123+
if item.nil?
124+
item = @@pending_field_validations.find { |pending_item|
125+
pending_item[:metaobject_type] == metaobject_type && pending_item[:field_key] == field_key
126+
}
127+
end
128+
129+
if item && item[:field_definition]
130+
field_def = item[:field_definition]
131+
new_field = build_field_definition(field_def, converted_validations)
132+
133+
field_operation = { create: new_field }
134+
update_metaobject_definition(metaobject_type, fieldDefinitions: [field_operation])
135+
136+
say "Added field '#{field_key}' to '#{metaobject_type}'"
137+
return true
138+
else
139+
return false
140+
end
141+
else
142+
return false
50143
end
144+
145+
rescue RuntimeError
146+
return false # Keep trying later or don't retry errors
51147
end
148+
end
149+
150+
private
151+
152+
def build_field_definition(field, validations = nil)
153+
field_def = {
154+
key: field[:key].to_s,
155+
name: field[:name],
156+
type: field[:type].to_s
157+
}
158+
field_def[:description] = field[:description] if field[:description]
159+
field_def[:required] = field[:required] if field[:required]
160+
field_def[:validations] = validations if validations&.any?
161+
field_def
162+
end
52163

164+
def create_metaobject_definition_immediate(definition)
53165
# https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metaobjectDefinitionCreate
54166
query =
55167
"# GraphQL

lib/shopify_toolkit/schema.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def load!
5959
say_with_time "Executing metaobject definitions" do
6060
# Execute only metaobject definitions first
6161
execute_metaobject_definitions(schema_content)
62+
apply_pending_field_validations
6263
end
6364

6465
say_with_time "Executing metafield definitions" do

0 commit comments

Comments
 (0)