From a98a93584b268a5e2f7ba3c30c1a94432ad49677 Mon Sep 17 00:00:00 2001 From: Matteo Latini Date: Tue, 17 Jun 2025 17:03:01 +0200 Subject: [PATCH 1/2] Add delete functionality for Metaobject Definitions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the delete_metaobject_definition method to complete the CRUD operations for metaobject definitions management API. The method follows the same patterns as create and update methods, including proper error handling and logging. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- lib/shopify_toolkit/metaobject_statements.rb | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/shopify_toolkit/metaobject_statements.rb b/lib/shopify_toolkit/metaobject_statements.rb index 6c2dcb8..e0c70db 100644 --- a/lib/shopify_toolkit/metaobject_statements.rb +++ b/lib/shopify_toolkit/metaobject_statements.rb @@ -107,6 +107,36 @@ def update_metaobject_definition(type, **options) .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectDefinitionUpdate.userErrors") } end + log_time \ + def delete_metaobject_definition(type) + existing_gid = get_metaobject_definition_gid(type) + + unless existing_gid + say "Metaobject #{type} does not exist, skipping deletion" + return + end + + # https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metaobjectDefinitionDelete + query = + "# GraphQL + mutation DeleteMetaobjectDefinition($id: ID!) { + metaobjectDefinitionDelete(id: $id) { + deletedId + userErrors { + field + message + code + } + } + } + " + variables = { id: existing_gid } + + shopify_admin_client + .query(query:, variables:) + .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectDefinitionDelete.userErrors") } + end + def self.define(&block) context = Object.new context.extend(self) From 925dc530d331c7aca8b66d0244debbe2e18d6a67 Mon Sep 17 00:00:00 2001 From: Matteo Latini Date: Tue, 17 Jun 2025 17:33:01 +0200 Subject: [PATCH 2/2] Add metaobject instances management API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implements create_metaobject for creating metaobject instances - Adds find_metaobject to retrieve single metaobjects by handle - Adds find_metaobjects to query multiple metaobjects with filtering - Implements update_metaobject for modifying existing instances - Implements delete_metaobject for removing metaobject instances - Supports both handle and GID-based operations - Includes proper error handling and user feedback - Follows existing code patterns and conventions - Updates README roadmap to mark completion 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 4 +- lib/shopify_toolkit/metaobject_statements.rb | 203 +++++++++++++++++++ 2 files changed, 205 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fdee1d5..abbbcda 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ A toolkit for working with Custom Shopify Apps built on Rails. - [x] Create - [x] Update - [x] Find - - [ ] Delete -- [ ] Metaobject Instances management API + - [x] Delete +- [x] Metaobject Instances management API - [ ] GraphQL Admin API code generation (syntax checking, etc) - [ ] GraphQL Admin API client with built-in rate limiting - [ ] GraphQL Admin API client with built-in caching diff --git a/lib/shopify_toolkit/metaobject_statements.rb b/lib/shopify_toolkit/metaobject_statements.rb index e0c70db..071561a 100644 --- a/lib/shopify_toolkit/metaobject_statements.rb +++ b/lib/shopify_toolkit/metaobject_statements.rb @@ -137,6 +137,209 @@ def delete_metaobject_definition(type) .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectDefinitionDelete.userErrors") } end + log_time \ + def create_metaobject(type, handle: nil, fields: [], **options) + # Check if metaobject definition exists + unless get_metaobject_definition_gid(type) + raise "Metaobject definition #{type} does not exist. Create it first." + end + + # Skip creation if metaobject with handle already exists + if handle && find_metaobject(type, handle) + say "Metaobject #{type} with handle '#{handle}' already exists, skipping creation" + return find_metaobject(type, handle) + end + + # https://shopify.dev/docs/api/admin-graphql/latest/mutations/metaobjectCreate + query = + "# GraphQL + mutation CreateMetaobject($metaobject: MetaobjectCreateInput!) { + metaobjectCreate(metaobject: $metaobject) { + metaobject { + id + handle + type + displayName + fields { + key + value + } + } + userErrors { + field + message + code + } + } + } + " + + metaobject_input = { type: type.to_s, **options } + metaobject_input[:handle] = handle if handle + metaobject_input[:fields] = fields.map { |field| { key: field[:key].to_s, value: field[:value].to_s } } if fields.any? + + variables = { metaobject: metaobject_input } + + shopify_admin_client + .query(query:, variables:) + .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectCreate.userErrors") } + end + + def find_metaobject(type, handle) + # https://shopify.dev/docs/api/admin-graphql/latest/queries/metaobject + query = + "# GraphQL + query FindMetaobject($type: String!, $handle: String!) { + metaobject(type: $type, handle: $handle) { + id + handle + type + displayName + fields { + key + value + } + } + } + " + variables = { type: type.to_s, handle: handle.to_s } + + result = shopify_admin_client + .query(query:, variables:) + .tap { handle_shopify_admin_client_errors(_1) } + .body + + result.dig("data", "metaobject") + end + + def find_metaobjects(type, first: 50, query: nil, sort_key: "id", reverse: false) + # https://shopify.dev/docs/api/admin-graphql/latest/queries/metaobjects + graphql_query = + "# GraphQL + query FindMetaobjects($type: String!, $first: Int, $query: String, $sortKey: String, $reverse: Boolean) { + metaobjects(type: $type, first: $first, query: $query, sortKey: $sortKey, reverse: $reverse) { + nodes { + id + handle + type + displayName + fields { + key + value + } + } + pageInfo { + hasNextPage + hasPreviousPage + startCursor + endCursor + } + } + } + " + variables = { + type: type.to_s, + first: first, + sortKey: sort_key, + reverse: reverse + } + variables[:query] = query if query + + result = shopify_admin_client + .query(query: graphql_query, variables:) + .tap { handle_shopify_admin_client_errors(_1) } + .body + + result.dig("data", "metaobjects") + end + + log_time \ + def update_metaobject(type, handle_or_id, fields: [], **options) + # Find the metaobject to get its ID + metaobject = if handle_or_id.start_with?("gid://") + # Already a GID + { "id" => handle_or_id } + else + # It's a handle, find by handle + find_metaobject(type, handle_or_id) + end + + unless metaobject + say "Metaobject #{type} with identifier '#{handle_or_id}' not found, skipping update" + return + end + + # https://shopify.dev/docs/api/admin-graphql/latest/mutations/metaobjectUpdate + query = + "# GraphQL + mutation UpdateMetaobject($id: ID!, $metaobject: MetaobjectUpdateInput!) { + metaobjectUpdate(id: $id, metaobject: $metaobject) { + metaobject { + id + handle + type + displayName + fields { + key + value + } + } + userErrors { + field + message + code + } + } + } + " + + metaobject_input = options.dup + metaobject_input[:fields] = fields.map { |field| { key: field[:key].to_s, value: field[:value].to_s } } if fields.any? + + variables = { id: metaobject["id"], metaobject: metaobject_input } + + shopify_admin_client + .query(query:, variables:) + .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectUpdate.userErrors") } + end + + log_time \ + def delete_metaobject(type, handle_or_id) + # Find the metaobject to get its ID + metaobject = if handle_or_id.start_with?("gid://") + # Already a GID + { "id" => handle_or_id } + else + # It's a handle, find by handle + find_metaobject(type, handle_or_id) + end + + unless metaobject + say "Metaobject #{type} with identifier '#{handle_or_id}' not found, skipping deletion" + return + end + + # https://shopify.dev/docs/api/admin-graphql/latest/mutations/metaobjectDelete + query = + "# GraphQL + mutation DeleteMetaobject($id: ID!) { + metaobjectDelete(id: $id) { + deletedId + userErrors { + field + message + code + } + } + } + " + variables = { id: metaobject["id"] } + + shopify_admin_client + .query(query:, variables:) + .tap { handle_shopify_admin_client_errors(_1, "data.metaobjectDelete.userErrors") } + end + def self.define(&block) context = Object.new context.extend(self)