Skip to content

Commit e138b35

Browse files
committed
Add ".where" functionality to graphql models
1 parent 0a75e6b commit e138b35

File tree

10 files changed

+783
-32
lines changed

10 files changed

+783
-32
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# frozen_string_literal: true
2+
3+
# Example usage of the new graphql_type class method and simplified fragment API
4+
# This demonstrates the desired API with improved fragment definition
5+
6+
module ActiveShopifyGraphQL
7+
module Loaders
8+
module AdminApi
9+
class CustomerLoader < AdminApiLoader
10+
graphql_type "Customer" # <- this informs the rest of the class
11+
12+
# New class-level fragment definition - just define the fields!
13+
# No need to specify fragment name or "on Customer" - it's derived automatically
14+
fragment <<~GRAPHQL
15+
id
16+
displayName
17+
email
18+
phone
19+
createdAt
20+
updatedAt
21+
defaultEmailAddress {
22+
emailAddress
23+
}
24+
tags
25+
GRAPHQL
26+
27+
def map_response_to_attributes(response_data)
28+
customer_data = response_data.dig("data", "customer")
29+
return {} unless customer_data
30+
31+
{
32+
id: customer_data["id"],
33+
display_name: customer_data["displayName"],
34+
email: customer_data["email"],
35+
phone: customer_data["phone"],
36+
created_at: customer_data["createdAt"],
37+
updated_at: customer_data["updatedAt"]
38+
}
39+
end
40+
end
41+
end
42+
end
43+
end
44+
45+
# Usage examples:
46+
# loader = ActiveShopifyGraphQL::Loaders::AdminApi::CustomerLoader.new
47+
# customer_attrs = loader.load_attributes("gid://shopify/Customer/123")
48+
# customers = loader.load_collection({ email: "test@example.com" })

lib/active_shopify_graphql.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# frozen_string_literal: true
22

3+
require 'active_support'
4+
require 'active_support/inflector'
5+
require 'active_support/concern'
6+
require 'active_support/core_ext/object/blank'
7+
require 'active_model'
8+
require 'globalid'
9+
310
require_relative "active_shopify_graphql/version"
411
require_relative "active_shopify_graphql/configuration"
512
require_relative "active_shopify_graphql/base"

lib/active_shopify_graphql/connections.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def metafield(attribute_name, graphql_field: "metafield", target_class: "Shopify
4949
end
5050
end
5151

52-
def connection(name, target_class: nil, arguments: {}, &block)
52+
def connection(name, target_class: nil, arguments: {})
5353
target_class_name = target_class&.to_s || name.to_s.singularize.classify
5454

5555
# Store connection metadata

lib/active_shopify_graphql/customer_account_api_loader.rb

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,38 @@ def initialize(token)
77
end
88

99
# Override to handle Customer queries that don't need an ID
10-
def graphql_query(model_type = 'Customer')
11-
if model_type == 'Customer'
10+
def graphql_query(model_type = nil)
11+
type = model_type || self.class.graphql_type
12+
if type == 'Customer'
1213
# Customer Account API doesn't need ID for customer queries - token identifies the customer
13-
customer_only_query(model_type)
14+
customer_only_query(type)
1415
else
1516
# For other types, use the standard query with ID
16-
super(model_type)
17+
super(type)
1718
end
1819
end
1920

2021
# Override load_attributes to handle the Customer case
21-
def load_attributes(id = nil, model_type = 'Customer')
22-
query = graphql_query(model_type)
22+
def load_attributes(model_type_or_id = nil, id = nil)
23+
# Handle both old and new signatures like the parent class
24+
if id.nil? && model_type_or_id.is_a?(String) && model_type_or_id != self.class.graphql_type
25+
# Old signature: load_attributes(model_type)
26+
type = model_type_or_id
27+
actual_id = nil
28+
elsif id.nil?
29+
# New signature: load_attributes() or load_attributes(id) - but for Customer, we don't need ID
30+
type = self.class.graphql_type
31+
actual_id = model_type_or_id
32+
else
33+
# Old signature: load_attributes(model_type, id)
34+
type = model_type_or_id
35+
actual_id = id
36+
end
37+
38+
query = graphql_query(type)
2339

2440
# For Customer queries, we don't need variables; for others, we need the ID
25-
variables = model_type == 'Customer' ? {} : { id: id }
41+
variables = type == 'Customer' ? {} : { id: actual_id }
2642

2743
response_data = execute_graphql_query(query, **variables)
2844

@@ -46,9 +62,10 @@ def execute_graphql_query(query, **variables)
4662
end
4763

4864
# Builds a customer-only query (no ID parameter needed)
49-
def customer_only_query(model_type)
50-
query_name_value = query_name(model_type)
51-
fragment_name_value = fragment_name(model_type)
65+
def customer_only_query(model_type = nil)
66+
type = model_type || self.class.graphql_type
67+
query_name_value = query_name(type)
68+
fragment_name_value = fragment_name(type)
5269

5370
<<~GRAPHQL
5471
#{fragment}

lib/active_shopify_graphql/finder_methods.rb

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ module FinderMethods
1212
def find(id, loader: default_loader)
1313
gid = URI::GID.build(app: "shopify", model_name: model_name.name.demodulize, model_id: id)
1414
model_type = name.demodulize
15-
attributes = loader.load_attributes(gid, model_type)
15+
attributes = loader.load_attributes(model_type, gid)
1616

1717
return nil if attributes.nil?
1818

@@ -35,6 +35,48 @@ def default_loader=(loader)
3535
@default_loader = loader
3636
end
3737

38+
# Query for multiple records using attribute conditions
39+
# @param conditions [Hash] The conditions to query (e.g., { email: "example@test.com", first_name: "John" })
40+
# @param options [Hash] Options hash containing loader and limit (when first arg is a Hash)
41+
# @option options [ActiveShopifyGraphQL::Loader] :loader The loader to use for fetching data
42+
# @option options [Integer] :limit The maximum number of records to return (default: 250, max: 250)
43+
# @return [Array<Object>] Array of model instances
44+
# @raise [ArgumentError] If any attribute is not valid for querying
45+
#
46+
# @example
47+
# # Keyword argument style (recommended)
48+
# Customer.where(email: "john@example.com")
49+
# Customer.where(first_name: "John", country: "Canada")
50+
# Customer.where(orders_count: { gte: 5 })
51+
# Customer.where(created_at: { gte: "2024-01-01", lt: "2024-02-01" })
52+
#
53+
# # Hash style with options
54+
# Customer.where({ email: "john@example.com" }, loader: custom_loader, limit: 100)
55+
def where(conditions_or_first_condition = {}, *args, **options)
56+
# Handle both syntaxes:
57+
# where(email: "john@example.com") - keyword args become options
58+
# where({ email: "john@example.com" }, loader: custom_loader) - explicit hash + options
59+
if conditions_or_first_condition.is_a?(Hash) && !conditions_or_first_condition.empty?
60+
# Explicit hash provided as first argument
61+
conditions = conditions_or_first_condition
62+
# Any additional options passed as keyword args or second hash argument
63+
final_options = args.first.is_a?(Hash) ? options.merge(args.first) : options
64+
else
65+
# Keyword arguments style - conditions come from options, excluding known option keys
66+
known_option_keys = %i[loader limit]
67+
conditions = options.except(*known_option_keys)
68+
final_options = options.slice(*known_option_keys)
69+
end
70+
71+
loader = final_options[:loader] || default_loader
72+
limit = final_options[:limit] || 250
73+
74+
model_type = name.demodulize
75+
attributes_array = loader.load_collection(model_type, conditions, limit: limit)
76+
77+
attributes_array.map { |attributes| new(attributes) }
78+
end
79+
3880
private
3981

4082
# Infers the loader class name from the model name

0 commit comments

Comments
 (0)