Skip to content

Commit 353244f

Browse files
committed
Update the docs with latest features
1 parent de52ab2 commit 353244f

File tree

1 file changed

+116
-83
lines changed

1 file changed

+116
-83
lines changed

README.md

Lines changed: 116 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,20 @@ end
5252

5353
### Basic Model Setup
5454

55-
Create a model that includes `ActiveShopifyGraphQL::Base`:
55+
Create a model that includes `ActiveShopifyGraphQL::Base` and define attributes directly:
5656

5757
```ruby
5858
class Customer
5959
include ActiveShopifyGraphQL::Base
6060

61-
attr_accessor :id, :name, :email, :created_at
61+
# Define the GraphQL type
62+
graphql_type "Customer"
63+
64+
# Define attributes with automatic GraphQL path inference and type coercion
65+
attribute :id, type: :string
66+
attribute :name, path: "displayName", type: :string
67+
attribute :email, path: "defaultEmailAddress.emailAddress", type: :string
68+
attribute :created_at, type: :datetime
6269

6370
validates :id, presence: true
6471

@@ -68,65 +75,26 @@ class Customer
6875
end
6976
```
7077

71-
### Creating Loaders
72-
73-
Create loader classes to define how to fetch and map data from Shopify's GraphQL APIs. There are two approaches: the new **attribute-based approach** (recommended) and the legacy **manual fragment approach**.
78+
### Defining Attributes
7479

75-
#### Attribute-Based Approach (Recommended)
80+
Attributes are now defined directly in the model class using the `attribute` method. The GraphQL fragments and response mapping are automatically generated!
7681

77-
Use the new `attribute` method to define GraphQL fields with automatic type coercion and fragment generation:
82+
#### Basic Attribute Definition
7883

7984
```ruby
80-
# For Admin API
81-
module ActiveShopifyGraphQL::Loaders::AdminApi
82-
class CustomerLoader < ActiveShopifyGraphQL::AdminApiLoader
83-
graphql_type "Customer"
84-
85-
# Define attributes with automatic GraphQL path inference and type coercion
86-
attribute :id, type: :string
87-
attribute :name, path: "displayName", type: :string
88-
attribute :email, path: "defaultEmailAddress.emailAddress", type: :string
89-
attribute :created_at, type: :datetime
90-
91-
# Custom transform example
92-
attribute :tags, type: :string, transform: ->(tags_array) { tags_array.join(", ") }
93-
94-
# The fragment and map_response_to_attributes are automatically generated!
95-
end
96-
end
97-
```
85+
class Customer
86+
include ActiveShopifyGraphQL::Base
9887

99-
#### Manual Fragment Approach (Legacy)
88+
graphql_type "Customer"
10089

101-
For more complex scenarios or when you need full control over the GraphQL fragment:
90+
# Define attributes with automatic GraphQL path inference and type coercion
91+
attribute :id, type: :string
92+
attribute :name, path: "displayName", type: :string
93+
attribute :email, path: "defaultEmailAddress.emailAddress", type: :string
94+
attribute :created_at, type: :datetime
10295

103-
```ruby
104-
# For Admin API
105-
module ActiveShopifyGraphQL::Loaders::AdminApi
106-
class CustomerLoader < ActiveShopifyGraphQL::AdminApiLoader
107-
graphql_type "Customer"
108-
109-
fragment <<~GRAPHQL
110-
id
111-
displayName
112-
defaultEmailAddress {
113-
emailAddress
114-
}
115-
createdAt
116-
GRAPHQL
117-
118-
def map_response_to_attributes(response_data)
119-
customer_data = response_data.dig("data", "customer")
120-
return nil unless customer_data
121-
122-
{
123-
id: customer_data["id"],
124-
name: customer_data["displayName"],
125-
email: customer_data.dig("defaultEmailAddress", "emailAddress"),
126-
created_at: customer_data["createdAt"]
127-
}
128-
end
129-
end
96+
# Custom transform example
97+
attribute :tags, type: :string, transform: ->(tags_array) { tags_array.join(", ") }
13098
end
13199
```
132100

@@ -136,10 +104,10 @@ The `attribute` method supports several options for flexibility:
136104

137105
```ruby
138106
attribute :name,
139-
path: "displayName", # Custom GraphQL path (auto-inferred if omitted)
140-
type: :string, # Type coercion (:string, :integer, :float, :boolean, :datetime)
141-
null: false, # Whether the attribute can be null (default: true)
142-
transform: ->(value) { value.upcase } # Custom transformation block
107+
path: "displayName", # Custom GraphQL path (auto-inferred if omitted)
108+
type: :string, # Type coercion (:string, :integer, :float, :boolean, :datetime)
109+
null: false, # Whether the attribute can be null (default: true)
110+
transform: ->(value) { value.upcase } # Custom transformation block
143111
```
144112

145113
**Auto-inference:** When `path` is omitted, it's automatically inferred by converting snake_case to camelCase (e.g., `display_name``displayName`).
@@ -148,23 +116,54 @@ attribute :name,
148116

149117
**Type coercion:** Automatic conversion using ActiveModel types ensures type safety.
150118

151-
#### Customer Account API Example
119+
**Array handling:** Arrays are automatically preserved regardless of the specified type.
120+
121+
#### Metafield Attributes
152122

153-
The Customer Account API has different field names and doesn't require an ID for customer queries:
123+
Shopify metafields can be easily accessed using the `metafield_attribute` method:
154124

155125
```ruby
156-
# For Customer Account API
157-
module ActiveShopifyGraphQL::Loaders::CustomerAccountApi
158-
class CustomerLoader < ActiveShopifyGraphQL::CustomerAccountApiLoader
159-
graphql_type "Customer"
126+
class Product
127+
include ActiveShopifyGraphQL::Base
128+
129+
graphql_type "Product"
130+
131+
# Regular attributes
132+
attribute :id, type: :string
133+
attribute :title, type: :string
134+
135+
# Metafield attributes
136+
metafield_attribute :boxes_available, namespace: 'custom', key: 'available_boxes', type: :integer
137+
metafield_attribute :seo_description, namespace: 'seo', key: 'meta_description', type: :string
138+
metafield_attribute :product_data, namespace: 'custom', key: 'data', type: :json
139+
metafield_attribute :is_featured, namespace: 'custom', key: 'featured', type: :boolean, null: false
140+
end
141+
```
142+
143+
The metafield attributes automatically generate the correct GraphQL syntax and handle value extraction from either `value` or `jsonValue` fields based on the type.
144+
145+
#### API-Specific Attributes
146+
147+
For models that need different attributes depending on the API being used, you can define loader-specific overrides:
148+
149+
```ruby
150+
class Customer
151+
include ActiveShopifyGraphQL::Base
152+
153+
graphql_type "Customer"
160154

161-
# Customer Account API uses different field names
162-
attribute :id, type: :string
155+
# Default attributes (used by Admin API)
156+
attribute :id, type: :string
157+
attribute :name, path: "displayName", type: :string
158+
attribute :email, path: "defaultEmailAddress.emailAddress", type: :string
159+
attribute :created_at, type: :datetime
160+
161+
# Customer Account API uses different field names
162+
for_loader ActiveShopifyGraphQL::CustomerAccountApiLoader do
163163
attribute :name, path: "firstName", type: :string
164164
attribute :last_name, path: "lastName", type: :string
165165
attribute :email, path: "emailAddress.emailAddress", type: :string
166166
attribute :phone, path: "phoneNumber.phoneNumber", type: :string, null: true
167-
attribute :created_at, path: "createdAt", type: :datetime
168167
end
169168
end
170169
```
@@ -174,11 +173,10 @@ end
174173
Use the `find` method to retrieve records by ID:
175174

176175
```ruby
177-
# Using default loader (Admin API)
176+
# Using Admin API (default)
178177
customer = Customer.find("gid://shopify/Customer/123456789")
179-
180-
# Using specific loader
181-
customer = Customer.find("gid://shopify/Customer/123456789", loader: custom_loader)
178+
# You can also use just the ID number
179+
customer = Customer.find(123456789)
182180

183181
# Using Customer Account API
184182
customer = Customer.with_customer_account_api(token).find
@@ -206,7 +204,6 @@ Use the `where` method to query multiple records using Shopify's search syntax:
206204
```ruby
207205
# Simple conditions
208206
customers = Customer.where(email: "john@example.com")
209-
customers = Customer.where(first_name: "John", country: "Canada")
210207

211208
# Range queries
212209
customers = Customer.where(created_at: { gte: "2024-01-01", lt: "2024-02-01" })
@@ -215,12 +212,48 @@ customers = Customer.where(orders_count: { gte: 5 })
215212
# Multi-word values are automatically quoted
216213
customers = Customer.where(first_name: "John Doe")
217214

218-
# With custom loader and limits
219-
customers = Customer.where({ email: "john@example.com" }, loader: custom_loader, limit: 100)
215+
# With limits
216+
customers = Customer.where({ email: "john@example.com" }, limit: 100)
220217
```
221218

222219
The `where` method automatically converts Ruby conditions into Shopify's GraphQL query syntax and validates that the query fields are supported by Shopify.
223220

221+
### Optimizing Queries with Select
222+
223+
Use the `select` method to only fetch specific attributes, reducing GraphQL query size and improving performance:
224+
225+
```ruby
226+
# Only fetch id, name, and email
227+
customer = Customer.select(:id, :name, :email).find(123)
228+
229+
# Works with where queries too
230+
customers = Customer.select(:id, :name).where(country: "Canada")
231+
232+
# Always includes id even if not specified
233+
customer = Customer.select(:name).find(123)
234+
# This will still include :id in the GraphQL query
235+
```
236+
237+
The `select` method validates that the specified attributes exist and automatically includes the `id` field for proper object identification.
238+
239+
### Optimizing Queries with Select
240+
241+
Use the `select` method to only fetch specific attributes, reducing GraphQL query size and improving performance:
242+
243+
```ruby
244+
# Only fetch id, name, and email
245+
customer = Customer.select(:id, :name, :email).find(123)
246+
247+
# Works with where queries too
248+
customers = Customer.select(:id, :name).where(country: "Canada")
249+
250+
# Always includes id even if not specified
251+
customer = Customer.select(:name).find(123)
252+
# This will still include :id in the GraphQL query
253+
```
254+
255+
The `select` method validates that the specified attributes exist and automatically includes the `id` field for proper object identification.
256+
224257
## Associations
225258

226259
ActiveShopifyGraphQL provides ActiveRecord-like associations to define relationships between the Shopify native models and your own custom ones.
@@ -233,22 +266,19 @@ Use `has_many` to define one-to-many relationships:
233266
class Customer
234267
include ActiveShopifyGraphQL::Base
235268

236-
attr_accessor :id, :display_name, :email, :created_at
269+
graphql_type "Customer"
270+
271+
attribute :id, type: :string
272+
attribute :display_name, type: :string
273+
attribute :email, path: "defaultEmailAddress.emailAddress", type: :string
274+
attribute :created_at, type: :datetime
237275

238276
# Define an association to one of your own ActiveRecord models
239277
# foreign_key maps the id of the GraphQL powered model to the rewards.shopify_customer_id table
240278
has_many :rewards, foreign_key: :shopify_customer_id
241279

242280
validates :id, presence: true
243281
end
244-
245-
class Order
246-
include ActiveShopifyGraphQL::Base
247-
248-
attr_accessor :id, :name, :shopify_customer_id, :created_at
249-
250-
validates :id, presence: true
251-
end
252282
```
253283

254284
#### Using the Association
@@ -279,6 +309,9 @@ The associations automatically handle Shopify GID format conversion, extracting
279309
## Next steps
280310

281311
- [x] Support `Model.where(param: value)` proxying params to the GraphQL query attribute
312+
- [x] Attribute-based model definition with automatic GraphQL fragment generation
313+
- [x] Metafield attributes for easy access to Shopify metafields
314+
- [x] Query optimization with `select` method
282315
- [ ] Eager loading of GraphQL connections via `Customer.includes(:orders).find(id)` in a single GraphQL query
283316
- [ ] Better error handling and retry mechanisms for GraphQL API calls
284317
- [ ] Caching layer for frequently accessed data

0 commit comments

Comments
 (0)