active_shopify_graphql 0.5.3 → 1.0.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.
- checksums.yaml +4 -4
- data/README.md +42 -12
- data/lib/active_shopify_graphql/configuration.rb +3 -3
- data/lib/active_shopify_graphql/connections/connection_loader.rb +2 -2
- data/lib/active_shopify_graphql/loader.rb +17 -13
- data/lib/active_shopify_graphql/loaders/admin_api_loader.rb +3 -5
- data/lib/active_shopify_graphql/loaders/customer_account_api_loader.rb +4 -9
- data/lib/active_shopify_graphql/model/connections.rb +1 -1
- data/lib/active_shopify_graphql/model/finder_methods.rb +12 -0
- data/lib/active_shopify_graphql/query/relation.rb +31 -3
- data/lib/active_shopify_graphql/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5fbed890ba698aa5134ff32bdd471ffa8944e56eaf5d6a8a6d28e8060983e519
|
|
4
|
+
data.tar.gz: 34d83bf513460e154f33369fe54e953c68a466e37d16effccb7cb92014f16824
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4a1dad259585ae87c1afa68c18eb03f74b48b13068b452a41a7d42bfb576c244df4de5eab321559dc5c9a5f11e1f2964fb0cdef142058ae345a75cb29e9e5700
|
|
7
|
+
data.tar.gz: f847bdf0f63e10b67f866abd6fae9c0d24195977d078c6d7125e7eff083f2f6ba99e1b0c30ada1374f5ceb4f36f0e8a1d52506a2d7354c7e01020d7d1ab4bcf7
|
data/README.md
CHANGED
|
@@ -25,15 +25,21 @@ gem install active_shopify_graphql
|
|
|
25
25
|
```ruby
|
|
26
26
|
# Configure in pure Ruby
|
|
27
27
|
ActiveShopifyGraphQL.configure do |config|
|
|
28
|
-
config.
|
|
29
|
-
|
|
28
|
+
config.admin_api_executor = lambda do |query, **variables|
|
|
29
|
+
client = ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
|
|
30
|
+
response = client.query(query:, variables:)
|
|
31
|
+
response.body if response
|
|
32
|
+
end
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
# Or define a Rails initializer
|
|
33
36
|
Rails.configuration.to_prepare do
|
|
34
37
|
ActiveShopifyGraphQL.configure do |config|
|
|
35
|
-
config.
|
|
36
|
-
|
|
38
|
+
config.admin_api_executor = lambda do |query, **variables|
|
|
39
|
+
client = ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
|
|
40
|
+
response = client.query(query:, variables:)
|
|
41
|
+
response.body if response
|
|
42
|
+
end
|
|
37
43
|
end
|
|
38
44
|
end
|
|
39
45
|
|
|
@@ -90,7 +96,7 @@ orders = customer["orders"]["nodes"].map { |o| parse_order(o) }
|
|
|
90
96
|
customer = Customer.includes(:orders).find(123456789)
|
|
91
97
|
customer.email # => "john@example.com"
|
|
92
98
|
customer.created_at # => #<DateTime>
|
|
93
|
-
customer.orders
|
|
99
|
+
customer.orders # Eagerly loaded as a single query
|
|
94
100
|
```
|
|
95
101
|
|
|
96
102
|
**Benefits:**
|
|
@@ -138,11 +144,19 @@ Configure your Shopify GraphQL clients:
|
|
|
138
144
|
# config/initializers/active_shopify_graphql.rb
|
|
139
145
|
Rails.configuration.to_prepare do
|
|
140
146
|
ActiveShopifyGraphQL.configure do |config|
|
|
141
|
-
# Admin API (
|
|
142
|
-
config.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
147
|
+
# Admin API (receives query, **variables as arguments)
|
|
148
|
+
config.admin_api_executor = lambda do |query, **variables|
|
|
149
|
+
client = ShopifyAPI::Clients::Graphql::Admin.new(session: ShopifyAPI::Context.active_session)
|
|
150
|
+
response = client.query(query:, variables:)
|
|
151
|
+
response.body if response
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Optional Customer Account API (receives query, customer_token, **variables as arguments)
|
|
155
|
+
config.customer_account_api_executor = lambda do |query, customer_token, **variables|
|
|
156
|
+
# This is a custom client example
|
|
157
|
+
client = Shopify::Account::Client.from_config(customer_token)
|
|
158
|
+
response = client.query(query, **variables)
|
|
159
|
+
end
|
|
146
160
|
end
|
|
147
161
|
end
|
|
148
162
|
```
|
|
@@ -309,7 +323,8 @@ Customer.where(email: "john@example.com")
|
|
|
309
323
|
Customer.where(created_at: { gte: "2024-01-01", lt: "2024-02-01" })
|
|
310
324
|
Customer.where(orders_count: { gte: 5 })
|
|
311
325
|
|
|
312
|
-
# Wildcards
|
|
326
|
+
# Wildcards are supported in string queries only
|
|
327
|
+
# Use with caution as interpolation can lead to security issues
|
|
313
328
|
Customer.where("email:*@example.com")
|
|
314
329
|
|
|
315
330
|
# Parameter binding (safe)
|
|
@@ -319,6 +334,21 @@ Customer.where("email::email", email: "john@example.com")
|
|
|
319
334
|
Customer.where(email: "@gmail.com").limit(100)
|
|
320
335
|
```
|
|
321
336
|
|
|
337
|
+
#### Sorting
|
|
338
|
+
|
|
339
|
+
```ruby
|
|
340
|
+
# Sort by created_at ascending (default)
|
|
341
|
+
Customer.where(email: "@example.com").order(sort_key: "CREATED_AT")
|
|
342
|
+
|
|
343
|
+
# Sort by updated_at descending
|
|
344
|
+
Customer.order(sort_key: "UPDATED_AT", reverse: true)
|
|
345
|
+
|
|
346
|
+
# Combine with other chainable methods
|
|
347
|
+
Customer.where(country: "Canada").order(sort_key: "CREATED_AT").limit(25)
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**Note:** Sort keys are Shopify GraphQL enum values (e.g., `"CREATED_AT"`, `"UPDATED_AT"`, `"RELEVANCE"`). Available values depend on the specific Shopify API endpoint.
|
|
351
|
+
|
|
322
352
|
#### Query Optimization
|
|
323
353
|
|
|
324
354
|
```ruby
|
|
@@ -349,7 +379,7 @@ ProductVariant.where("sku:FRZ*").in_pages(of: 10) do |page|
|
|
|
349
379
|
end
|
|
350
380
|
|
|
351
381
|
# Lazy enumeration
|
|
352
|
-
scope = Customer.where(email: "
|
|
382
|
+
scope = Customer.where(email: "@example.com")
|
|
353
383
|
scope.each { |c| puts c.name } # Executes query
|
|
354
384
|
scope.first # Fetches just first
|
|
355
385
|
```
|
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
module ActiveShopifyGraphQL
|
|
4
4
|
# Configuration class for setting up external dependencies
|
|
5
5
|
class Configuration
|
|
6
|
-
attr_accessor :
|
|
6
|
+
attr_accessor :admin_api_executor, :customer_account_api_executor, :logger, :log_queries, :max_objects_per_paginated_query
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
|
-
@
|
|
10
|
-
@
|
|
9
|
+
@admin_api_executor = nil
|
|
10
|
+
@customer_account_api_executor = nil
|
|
11
11
|
@logger = nil
|
|
12
12
|
@log_queries = false
|
|
13
13
|
@max_objects_per_paginated_query = 250
|
|
@@ -44,7 +44,7 @@ module ActiveShopifyGraphQL
|
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
parent_id = extract_gid(parent)
|
|
47
|
-
response_data = @loader_instance.
|
|
47
|
+
response_data = @loader_instance.execute_query(query, id: parent_id)
|
|
48
48
|
|
|
49
49
|
return [] if response_data.nil?
|
|
50
50
|
|
|
@@ -76,7 +76,7 @@ module ActiveShopifyGraphQL
|
|
|
76
76
|
singular: singular
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
-
response_data = @loader_instance.
|
|
79
|
+
response_data = @loader_instance.execute_query(query)
|
|
80
80
|
|
|
81
81
|
return [] if response_data.nil?
|
|
82
82
|
|
|
@@ -58,7 +58,7 @@ module ActiveShopifyGraphQL
|
|
|
58
58
|
|
|
59
59
|
# Map the GraphQL response to model attributes
|
|
60
60
|
def map_response_to_attributes(response_data, parent_instance: nil)
|
|
61
|
-
mapper =
|
|
61
|
+
mapper = Response::ResponseMapper.new(context)
|
|
62
62
|
attributes = mapper.map_response(response_data)
|
|
63
63
|
cache_connections(mapper, response_data, target: attributes, parent_instance: parent_instance)
|
|
64
64
|
attributes
|
|
@@ -74,7 +74,7 @@ module ActiveShopifyGraphQL
|
|
|
74
74
|
# @return [Hash, nil] Attribute hash with connection cache, or nil if not found
|
|
75
75
|
def load_attributes(id)
|
|
76
76
|
query = Query::QueryBuilder.build_single_record_query(context)
|
|
77
|
-
response_data =
|
|
77
|
+
response_data = perform_graphql_query(query, id: id)
|
|
78
78
|
return nil if response_data.nil?
|
|
79
79
|
|
|
80
80
|
map_response_to_attributes(response_data)
|
|
@@ -86,15 +86,19 @@ module ActiveShopifyGraphQL
|
|
|
86
86
|
# @param per_page [Integer] Number of records per page
|
|
87
87
|
# @param after [String, nil] Cursor to fetch records after
|
|
88
88
|
# @param before [String, nil] Cursor to fetch records before
|
|
89
|
+
# @param sort_key [String, nil] The Shopify sort key (e.g., "CREATED_AT")
|
|
90
|
+
# @param reverse [Boolean, nil] Whether to reverse the sort order
|
|
89
91
|
# @param query_scope [Query::Scope] The query scope for navigation
|
|
90
92
|
# @return [PaginatedResult] A paginated result with attribute hashes and page info
|
|
91
|
-
def load_paginated_collection(conditions:, per_page:, query_scope:, after: nil, before: nil)
|
|
93
|
+
def load_paginated_collection(conditions:, per_page:, query_scope:, after: nil, before: nil, sort_key: nil, reverse: nil)
|
|
92
94
|
collection_query_name = context.query_name.pluralize
|
|
93
95
|
variables = build_collection_variables(
|
|
94
96
|
conditions,
|
|
95
97
|
per_page: per_page,
|
|
96
98
|
after: after,
|
|
97
|
-
before: before
|
|
99
|
+
before: before,
|
|
100
|
+
sort_key: sort_key,
|
|
101
|
+
reverse: reverse
|
|
98
102
|
)
|
|
99
103
|
|
|
100
104
|
query = Query::QueryBuilder.build_paginated_collection_query(
|
|
@@ -113,6 +117,11 @@ module ActiveShopifyGraphQL
|
|
|
113
117
|
connection_loader.load_records(query_name, variables, parent, connection_config)
|
|
114
118
|
end
|
|
115
119
|
|
|
120
|
+
def execute_query(query, **variables)
|
|
121
|
+
log_query(self.class.name, query, variables)
|
|
122
|
+
perform_graphql_query(query, **variables)
|
|
123
|
+
end
|
|
124
|
+
|
|
116
125
|
# Abstract method for executing GraphQL queries
|
|
117
126
|
def perform_graphql_query(query, **variables)
|
|
118
127
|
raise NotImplementedError, "#{self.class} must implement perform_graphql_query"
|
|
@@ -120,10 +129,6 @@ module ActiveShopifyGraphQL
|
|
|
120
129
|
|
|
121
130
|
private
|
|
122
131
|
|
|
123
|
-
def create_response_mapper
|
|
124
|
-
Response::ResponseMapper.new(context)
|
|
125
|
-
end
|
|
126
|
-
|
|
127
132
|
def should_log?
|
|
128
133
|
ActiveShopifyGraphQL.configuration.log_queries && ActiveShopifyGraphQL.configuration.logger
|
|
129
134
|
end
|
|
@@ -175,17 +180,13 @@ module ActiveShopifyGraphQL
|
|
|
175
180
|
raise ArgumentError, "Shopify query validation failed: #{messages.join(', ')}"
|
|
176
181
|
end
|
|
177
182
|
|
|
178
|
-
def execute_query(query, **variables)
|
|
179
|
-
perform_graphql_query(query, **variables)
|
|
180
|
-
end
|
|
181
|
-
|
|
182
183
|
def execute_query_and_validate_search_response(query, **variables)
|
|
183
184
|
response = execute_query(query, **variables)
|
|
184
185
|
validate_search_query_response(response)
|
|
185
186
|
response
|
|
186
187
|
end
|
|
187
188
|
|
|
188
|
-
def build_collection_variables(conditions, per_page:, after: nil, before: nil)
|
|
189
|
+
def build_collection_variables(conditions, per_page:, after: nil, before: nil, sort_key: nil, reverse: nil)
|
|
189
190
|
search_query = SearchQuery.new(conditions)
|
|
190
191
|
variables = { query: search_query.to_s }
|
|
191
192
|
|
|
@@ -197,6 +198,9 @@ module ActiveShopifyGraphQL
|
|
|
197
198
|
variables[:after] = after if after
|
|
198
199
|
end
|
|
199
200
|
|
|
201
|
+
variables[:sort_key] = sort_key if sort_key
|
|
202
|
+
variables[:reverse] = reverse unless reverse.nil?
|
|
203
|
+
|
|
200
204
|
variables.compact
|
|
201
205
|
end
|
|
202
206
|
|
|
@@ -4,12 +4,10 @@ module ActiveShopifyGraphQL
|
|
|
4
4
|
module Loaders
|
|
5
5
|
class AdminApiLoader < Loader
|
|
6
6
|
def perform_graphql_query(query, **variables)
|
|
7
|
-
|
|
7
|
+
executor = ActiveShopifyGraphQL.configuration.admin_api_executor
|
|
8
|
+
raise Error, "Admin API executor not configured. Please configure it using ActiveShopifyGraphQL.configure" unless executor
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
raise Error, "Admin API client not configured. Please configure it using ActiveShopifyGraphQL.configure" unless client
|
|
11
|
-
|
|
12
|
-
client.execute(query, **variables)
|
|
10
|
+
executor.call(query, **variables)
|
|
13
11
|
end
|
|
14
12
|
end
|
|
15
13
|
end
|
|
@@ -23,16 +23,11 @@ module ActiveShopifyGraphQL
|
|
|
23
23
|
map_response_to_attributes(response_data)
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
-
def client
|
|
27
|
-
client_class = ActiveShopifyGraphQL.configuration.customer_account_client_class
|
|
28
|
-
raise Error, "Customer Account API client class not configured" unless client_class
|
|
29
|
-
|
|
30
|
-
@client ||= client_class.from_config(@token)
|
|
31
|
-
end
|
|
32
|
-
|
|
33
26
|
def perform_graphql_query(query, **variables)
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
executor = ActiveShopifyGraphQL.configuration.customer_account_api_executor
|
|
28
|
+
raise Error, "Customer Account API executor not configured. Please configure it using ActiveShopifyGraphQL.configure" unless executor
|
|
29
|
+
|
|
30
|
+
executor.call(query, @token, **variables)
|
|
36
31
|
end
|
|
37
32
|
|
|
38
33
|
def initialization_args
|
|
@@ -102,7 +102,7 @@ module ActiveShopifyGraphQL::Model::Connections
|
|
|
102
102
|
)
|
|
103
103
|
else
|
|
104
104
|
# Create a new proxy for custom options (don't cache these)
|
|
105
|
-
Connections::ConnectionProxy.new(
|
|
105
|
+
ActiveShopifyGraphQL::Connections::ConnectionProxy.new(
|
|
106
106
|
parent: self,
|
|
107
107
|
connection_name: name,
|
|
108
108
|
connection_config: self.class.connections[name],
|
|
@@ -120,6 +120,18 @@ module ActiveShopifyGraphQL::Model::FinderMethods
|
|
|
120
120
|
all.includes(*connection_names)
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
+
# Set the sort order for the query
|
|
124
|
+
# @param sort_key [String] The Shopify sort key (e.g., "CREATED_AT", "UPDATED_AT")
|
|
125
|
+
# @param reverse [Boolean] Whether to reverse the sort order (optional)
|
|
126
|
+
# @return [Relation] A relation with order applied
|
|
127
|
+
#
|
|
128
|
+
# @example
|
|
129
|
+
# Customer.order(sort_key: "CREATED_AT").where(country: "Canada")
|
|
130
|
+
# Customer.where(status: "active").order(sort_key: "UPDATED_AT", reverse: true)
|
|
131
|
+
def order(sort_key:, reverse: nil)
|
|
132
|
+
all.order(sort_key: sort_key, reverse: reverse)
|
|
133
|
+
end
|
|
134
|
+
|
|
123
135
|
private
|
|
124
136
|
|
|
125
137
|
# Validates that selected attributes exist in the model
|
|
@@ -23,7 +23,7 @@ module ActiveShopifyGraphQL
|
|
|
23
23
|
|
|
24
24
|
DEFAULT_PER_PAGE = 250
|
|
25
25
|
|
|
26
|
-
attr_reader :model_class, :included_connections, :conditions, :total_limit, :per_page
|
|
26
|
+
attr_reader :model_class, :included_connections, :conditions, :total_limit, :per_page, :sort_key, :reverse
|
|
27
27
|
|
|
28
28
|
def initialize(
|
|
29
29
|
model_class,
|
|
@@ -33,7 +33,9 @@ module ActiveShopifyGraphQL
|
|
|
33
33
|
total_limit: nil,
|
|
34
34
|
per_page: DEFAULT_PER_PAGE,
|
|
35
35
|
loader_class: nil,
|
|
36
|
-
loader_extra_args: []
|
|
36
|
+
loader_extra_args: [],
|
|
37
|
+
sort_key: nil,
|
|
38
|
+
reverse: nil
|
|
37
39
|
)
|
|
38
40
|
@model_class = model_class
|
|
39
41
|
@conditions = conditions
|
|
@@ -43,6 +45,8 @@ module ActiveShopifyGraphQL
|
|
|
43
45
|
@per_page = [per_page, ActiveShopifyGraphQL.configuration.max_objects_per_paginated_query].min
|
|
44
46
|
@loader_class = loader_class
|
|
45
47
|
@loader_extra_args = loader_extra_args
|
|
48
|
+
@sort_key = sort_key
|
|
49
|
+
@reverse = reverse
|
|
46
50
|
@loaded = false
|
|
47
51
|
@records = nil
|
|
48
52
|
end
|
|
@@ -66,6 +70,22 @@ module ActiveShopifyGraphQL
|
|
|
66
70
|
spawn(conditions: new_conditions)
|
|
67
71
|
end
|
|
68
72
|
|
|
73
|
+
# Set the sort order for the query
|
|
74
|
+
# @param sort_key [String] The Shopify sort key (e.g., "CREATED_AT", "UPDATED_AT")
|
|
75
|
+
# @param reverse [Boolean] Whether to reverse the sort order (optional)
|
|
76
|
+
# @return [Relation] A new relation with order applied
|
|
77
|
+
# @raise [ArgumentError] If order is called on a relation that already has ordering
|
|
78
|
+
# @example
|
|
79
|
+
# Customer.where(email: "*@example.com").order(sort_key: "CREATED_AT", reverse: true)
|
|
80
|
+
def order(sort_key:, reverse: nil)
|
|
81
|
+
if has_ordering?
|
|
82
|
+
raise ArgumentError, "Chaining multiple order clauses is not supported. " \
|
|
83
|
+
"Combine ordering in a single order call instead."
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
spawn(sort_key: sort_key, reverse: reverse)
|
|
87
|
+
end
|
|
88
|
+
|
|
69
89
|
# Find a single record by conditions
|
|
70
90
|
# @param conditions [Hash] The conditions to match
|
|
71
91
|
# @return [Object, nil] The first matching record or nil
|
|
@@ -302,6 +322,8 @@ module ActiveShopifyGraphQL
|
|
|
302
322
|
per_page: effective_per_page,
|
|
303
323
|
after: after,
|
|
304
324
|
before: before,
|
|
325
|
+
sort_key: @sort_key,
|
|
326
|
+
reverse: @reverse,
|
|
305
327
|
query_scope: build_query_scope_for_pagination
|
|
306
328
|
)
|
|
307
329
|
end
|
|
@@ -324,7 +346,9 @@ module ActiveShopifyGraphQL
|
|
|
324
346
|
total_limit: changes.fetch(:total_limit, @total_limit),
|
|
325
347
|
per_page: changes.fetch(:per_page, @per_page),
|
|
326
348
|
loader_class: changes.fetch(:loader_class, @loader_class),
|
|
327
|
-
loader_extra_args: changes.fetch(:loader_extra_args, @loader_extra_args)
|
|
349
|
+
loader_extra_args: changes.fetch(:loader_extra_args, @loader_extra_args),
|
|
350
|
+
sort_key: changes.fetch(:sort_key, @sort_key),
|
|
351
|
+
reverse: changes.fetch(:reverse, @reverse)
|
|
328
352
|
)
|
|
329
353
|
end
|
|
330
354
|
|
|
@@ -341,6 +365,10 @@ module ActiveShopifyGraphQL
|
|
|
341
365
|
end
|
|
342
366
|
end
|
|
343
367
|
|
|
368
|
+
def has_ordering?
|
|
369
|
+
!@sort_key.nil?
|
|
370
|
+
end
|
|
371
|
+
|
|
344
372
|
def build_loader
|
|
345
373
|
klass = @loader_class || @model_class.send(:default_loader_class)
|
|
346
374
|
|