@spree/docs 0.1.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.
- package/README.md +54 -0
- package/dist/api-reference/platform/authentication.md +38 -0
- package/dist/api-reference/store-api/authentication.md +188 -0
- package/dist/api-reference/store-api/errors.md +277 -0
- package/dist/api-reference/store-api/idempotency.md +129 -0
- package/dist/api-reference/store-api/introduction.md +34 -0
- package/dist/api-reference/store-api/localization.md +279 -0
- package/dist/api-reference/store-api/metadata.md +160 -0
- package/dist/api-reference/store-api/monetary-amounts.md +65 -0
- package/dist/api-reference/store-api/querying.md +399 -0
- package/dist/api-reference/store-api/rate-limitting.md +103 -0
- package/dist/api-reference/store-api/relations.md +185 -0
- package/dist/api-reference/storefront/authentication.md +88 -0
- package/dist/api-reference/tutorials/adyen-integration-guide-for-android.md +165 -0
- package/dist/api-reference/tutorials/adyen-integration-guide-for-ios.md +194 -0
- package/dist/api-reference/tutorials/quick-checkout-with-stripe.md +248 -0
- package/dist/api-reference/v2/fetching-multiple-resources.md +26 -0
- package/dist/api-reference/v2/filtering-and-sorting.md +53 -0
- package/dist/api-reference/v2/introduction.md +22 -0
- package/dist/api-reference/v2/pagination.md +37 -0
- package/dist/api-reference/webhooks-events.md +883 -0
- package/dist/developer/admin/admin.md +205 -0
- package/dist/developer/admin/authentication.md +59 -0
- package/dist/developer/admin/components.md +711 -0
- package/dist/developer/admin/custom-css.md +243 -0
- package/dist/developer/admin/custom-javascript.md +116 -0
- package/dist/developer/admin/extending-ui.md +1964 -0
- package/dist/developer/admin/form-builder.md +444 -0
- package/dist/developer/admin/helper-methods.md +531 -0
- package/dist/developer/admin/navigation.md +805 -0
- package/dist/developer/admin/tables.md +491 -0
- package/dist/developer/advanced/adding_spree_to_rails_app.md +106 -0
- package/dist/developer/cli/quickstart.md +137 -0
- package/dist/developer/contributing/creating-an-extension.md +258 -0
- package/dist/developer/contributing/developing-spree.md +339 -0
- package/dist/developer/contributing/quickstart.md +32 -0
- package/dist/developer/contributing/updating-extensions.md +67 -0
- package/dist/developer/core-concepts/addresses.md +265 -0
- package/dist/developer/core-concepts/adjustments.md +107 -0
- package/dist/developer/core-concepts/architecture.md +177 -0
- package/dist/developer/core-concepts/calculators.md +323 -0
- package/dist/developer/core-concepts/customers.md +230 -0
- package/dist/developer/core-concepts/events.md +624 -0
- package/dist/developer/core-concepts/imports-exports.md +698 -0
- package/dist/developer/core-concepts/inventory.md +191 -0
- package/dist/developer/core-concepts/markets.md +250 -0
- package/dist/developer/core-concepts/media.md +167 -0
- package/dist/developer/core-concepts/metafields.md +187 -0
- package/dist/developer/core-concepts/orders.md +328 -0
- package/dist/developer/core-concepts/payments.md +710 -0
- package/dist/developer/core-concepts/pricing.md +163 -0
- package/dist/developer/core-concepts/products.md +360 -0
- package/dist/developer/core-concepts/promotions.md +322 -0
- package/dist/developer/core-concepts/reports.md +206 -0
- package/dist/developer/core-concepts/search-filtering.md +237 -0
- package/dist/developer/core-concepts/shipments.md +212 -0
- package/dist/developer/core-concepts/slugs.md +111 -0
- package/dist/developer/core-concepts/staff-roles.md +123 -0
- package/dist/developer/core-concepts/store-credits-gift-cards.md +317 -0
- package/dist/developer/core-concepts/stores.md +117 -0
- package/dist/developer/core-concepts/taxes.md +135 -0
- package/dist/developer/core-concepts/translations.md +120 -0
- package/dist/developer/core-concepts/users.md +299 -0
- package/dist/developer/core-concepts/webhooks.md +378 -0
- package/dist/developer/create-spree-app/quickstart.md +158 -0
- package/dist/developer/customization/api.md +93 -0
- package/dist/developer/customization/authentication.md +88 -0
- package/dist/developer/customization/checkout.md +204 -0
- package/dist/developer/customization/configuration.md +55 -0
- package/dist/developer/customization/decorators.md +523 -0
- package/dist/developer/customization/dependencies.md +232 -0
- package/dist/developer/customization/emails.md +21 -0
- package/dist/developer/customization/extensions.md +92 -0
- package/dist/developer/customization/metadata.md +236 -0
- package/dist/developer/customization/model-preferences.md +130 -0
- package/dist/developer/customization/permissions.md +265 -0
- package/dist/developer/customization/quickstart.md +229 -0
- package/dist/developer/customization/routes.md +24 -0
- package/dist/developer/customization/v4/admin-panel.md +78 -0
- package/dist/developer/customization/v4/authentication.md +210 -0
- package/dist/developer/customization/v4/checkout.md +212 -0
- package/dist/developer/customization/v4/deface.md +251 -0
- package/dist/developer/customization/v4/images.md +86 -0
- package/dist/developer/customization/v4/storefront.md +450 -0
- package/dist/developer/deployment/assets.md +87 -0
- package/dist/developer/deployment/aws.md +335 -0
- package/dist/developer/deployment/caching.md +27 -0
- package/dist/developer/deployment/cdn.md +39 -0
- package/dist/developer/deployment/database.md +155 -0
- package/dist/developer/deployment/docker.md +128 -0
- package/dist/developer/deployment/emails.md +77 -0
- package/dist/developer/deployment/environment_variables.md +111 -0
- package/dist/developer/deployment/heroku.md +51 -0
- package/dist/developer/deployment/render.md +95 -0
- package/dist/developer/getting-started/quickstart.md +82 -0
- package/dist/developer/how-to/custom-payment-method.md +374 -0
- package/dist/developer/how-to/custom-promotion.md +373 -0
- package/dist/developer/how-to/custom-report.md +387 -0
- package/dist/developer/how-to/custom-search-provider.md +230 -0
- package/dist/developer/multi-store/quickstart.md +71 -0
- package/dist/developer/multi-store/setup.md +38 -0
- package/dist/developer/multi-tenant/configuration.md +41 -0
- package/dist/developer/multi-tenant/core-concepts.md +75 -0
- package/dist/developer/multi-tenant/installation.md +96 -0
- package/dist/developer/multi-tenant/quickstart.md +20 -0
- package/dist/developer/multi-vendor/installation.md +45 -0
- package/dist/developer/multi-vendor/quickstart.md +17 -0
- package/dist/developer/sdk/admin/quickstart.md +22 -0
- package/dist/developer/sdk/authentication.md +89 -0
- package/dist/developer/sdk/configuration.md +225 -0
- package/dist/developer/sdk/quickstart.md +82 -0
- package/dist/developer/sdk/store/account.md +67 -0
- package/dist/developer/sdk/store/cart-checkout.md +140 -0
- package/dist/developer/sdk/store/markets.md +151 -0
- package/dist/developer/sdk/store/payments.md +96 -0
- package/dist/developer/sdk/store/products.md +149 -0
- package/dist/developer/sdk/store/wishlists.md +52 -0
- package/dist/developer/security/pci_compliance.md +15 -0
- package/dist/developer/security/security_policy.md +68 -0
- package/dist/developer/storefront/blocks.md +285 -0
- package/dist/developer/storefront/custom-css.md +260 -0
- package/dist/developer/storefront/custom-javascript.md +166 -0
- package/dist/developer/storefront/helper-methods.md +1288 -0
- package/dist/developer/storefront/links.md +298 -0
- package/dist/developer/storefront/nextjs/architecture.md +150 -0
- package/dist/developer/storefront/nextjs/customization.md +141 -0
- package/dist/developer/storefront/nextjs/deployment.md +180 -0
- package/dist/developer/storefront/nextjs/quickstart.md +92 -0
- package/dist/developer/storefront/nextjs/spree-next-package.md +314 -0
- package/dist/developer/storefront/pages.md +163 -0
- package/dist/developer/storefront/sections.md +569 -0
- package/dist/developer/storefront/storefront.md +56 -0
- package/dist/developer/storefront/themes.md +161 -0
- package/dist/developer/tutorial/admin.md +134 -0
- package/dist/developer/tutorial/extending-models.md +380 -0
- package/dist/developer/tutorial/file-uploads.md +121 -0
- package/dist/developer/tutorial/introduction.md +33 -0
- package/dist/developer/tutorial/model.md +41 -0
- package/dist/developer/tutorial/page-builder.md +487 -0
- package/dist/developer/tutorial/rich-text.md +73 -0
- package/dist/developer/tutorial/seo.md +332 -0
- package/dist/developer/tutorial/storefront.md +352 -0
- package/dist/developer/tutorial/testing.md +558 -0
- package/dist/developer/upgrades/2.0-to-2.1.md +46 -0
- package/dist/developer/upgrades/2.1-to-2.2.md +59 -0
- package/dist/developer/upgrades/2.2-to-2.3.md +44 -0
- package/dist/developer/upgrades/2.3-to-2.4.md +42 -0
- package/dist/developer/upgrades/3.0-to-3.1.md +47 -0
- package/dist/developer/upgrades/3.1-to-3.2.md +34 -0
- package/dist/developer/upgrades/3.2-to-3.3.md +70 -0
- package/dist/developer/upgrades/3.3-to-3.4.md +36 -0
- package/dist/developer/upgrades/3.4-to-3.5.md +44 -0
- package/dist/developer/upgrades/3.5-to-3.6.md +40 -0
- package/dist/developer/upgrades/3.6-to-3.7.md +62 -0
- package/dist/developer/upgrades/3.7-to-4.0.md +152 -0
- package/dist/developer/upgrades/4.0-to-4.1.md +92 -0
- package/dist/developer/upgrades/4.1-to-4.2.md +109 -0
- package/dist/developer/upgrades/4.10-to-5.0.md +129 -0
- package/dist/developer/upgrades/4.2-to-4.3.md +100 -0
- package/dist/developer/upgrades/4.3-to-4.4.md +125 -0
- package/dist/developer/upgrades/4.4-to-4.5.md +94 -0
- package/dist/developer/upgrades/4.5-to-4.6.md +119 -0
- package/dist/developer/upgrades/4.6-to-4.7.md +39 -0
- package/dist/developer/upgrades/4.8-to-4.9.md +24 -0
- package/dist/developer/upgrades/4.9-to-4.10.md +24 -0
- package/dist/developer/upgrades/4.x-to-4.8.md +52 -0
- package/dist/developer/upgrades/5.0-to-5.1.md +28 -0
- package/dist/developer/upgrades/5.1-to-5.2.md +127 -0
- package/dist/developer/upgrades/5.2-to-5.3.md +338 -0
- package/dist/developer/upgrades/5.3-to-5.4.md +248 -0
- package/dist/developer/upgrades/quickstart.md +36 -0
- package/dist/integrations/analytics/google-analytics.md +64 -0
- package/dist/integrations/analytics/google-tag-manager.md +78 -0
- package/dist/integrations/integrations.md +39 -0
- package/dist/integrations/marketing/klaviyo.md +99 -0
- package/dist/integrations/payments/adyen.md +90 -0
- package/dist/integrations/payments/paypal.md +41 -0
- package/dist/integrations/payments/razorpay.md +45 -0
- package/dist/integrations/payments/stripe.md +109 -0
- package/dist/integrations/search/meilisearch.md +236 -0
- package/dist/integrations/sso-mfa-social-login/admin-dashboard.md +57 -0
- package/dist/integrations/sso-mfa-social-login/storefront.md +56 -0
- package/package.json +27 -0
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Imports & Exports
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Spree provides a comprehensive bulk data import and export system for managing large datasets. The system supports CSV file processing with configurable field mapping, asynchronous processing via background jobs, and real-time progress tracking in the admin interface.
|
|
8
|
+
|
|
9
|
+
### Import/Export System Diagram
|
|
10
|
+
|
|
11
|
+
```mermaid
|
|
12
|
+
erDiagram
|
|
13
|
+
Import {
|
|
14
|
+
string number
|
|
15
|
+
string type
|
|
16
|
+
string status
|
|
17
|
+
bigint owner_id
|
|
18
|
+
string owner_type
|
|
19
|
+
bigint user_id
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
ImportMapping {
|
|
23
|
+
string schema_field
|
|
24
|
+
string file_column
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
ImportRow {
|
|
28
|
+
integer row_number
|
|
29
|
+
text data
|
|
30
|
+
string status
|
|
31
|
+
text validation_errors
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ImportSchema {
|
|
35
|
+
array fields
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
RowProcessor {
|
|
39
|
+
hash attributes
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
Export {
|
|
43
|
+
string number
|
|
44
|
+
string type
|
|
45
|
+
string format
|
|
46
|
+
jsonb search_params
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Store {
|
|
50
|
+
string name
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Import ||--|| Store : "belongs to (owner)"
|
|
54
|
+
Import ||--o{ ImportMapping : "has many"
|
|
55
|
+
Import ||--o{ ImportRow : "has many"
|
|
56
|
+
Import ||--|| ImportSchema : "uses"
|
|
57
|
+
ImportRow ||--|| RowProcessor : "processed by"
|
|
58
|
+
Export ||--|| Store : "belongs to"
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Architecture
|
|
62
|
+
|
|
63
|
+
The import/export system uses several design patterns:
|
|
64
|
+
|
|
65
|
+
1. **Single Table Inheritance (STI)**: Import and Export types inherit from base classes
|
|
66
|
+
2. **State Machine**: Imports progress through states (pending → mapping → processing → completed)
|
|
67
|
+
3. **Schema Definition**: ImportSchema classes define expected fields and validation
|
|
68
|
+
4. **Row Processors**: Transform CSV rows into database records
|
|
69
|
+
5. **Event-Driven Processing**: Background jobs handle heavy lifting asynchronously
|
|
70
|
+
6. **Registry Pattern**: Types registered in `Spree.import_types` and `Spree.export_types`
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Exports
|
|
75
|
+
|
|
76
|
+
Exports generate CSV files from filtered database records.
|
|
77
|
+
|
|
78
|
+
### Built-in Export Types
|
|
79
|
+
|
|
80
|
+
| Type | Description | Multi-line |
|
|
81
|
+
|------|-------------|------------|
|
|
82
|
+
| `Spree::Exports::Products` | Products with all variants | Yes |
|
|
83
|
+
| `Spree::Exports::Orders` | Orders with line items | Yes |
|
|
84
|
+
| `Spree::Exports::Customers` | Customer accounts | No |
|
|
85
|
+
| `Spree::Exports::GiftCards` | Gift cards | No |
|
|
86
|
+
| `Spree::Exports::NewsletterSubscribers` | Newsletter subscribers | No |
|
|
87
|
+
|
|
88
|
+
### Export Model
|
|
89
|
+
|
|
90
|
+
The base `Spree::Export` class provides:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
module Spree
|
|
94
|
+
class Export < Spree.base_class
|
|
95
|
+
# Associations
|
|
96
|
+
belongs_to :store
|
|
97
|
+
belongs_to :user # Admin who created export
|
|
98
|
+
|
|
99
|
+
# Attachments
|
|
100
|
+
has_one_attached :attachment # Generated CSV file
|
|
101
|
+
|
|
102
|
+
# Key methods
|
|
103
|
+
def csv_headers # Define column headers
|
|
104
|
+
def scope # Base query with store/vendor filtering
|
|
105
|
+
def scope_includes # Eager loading associations
|
|
106
|
+
def records_to_export # Apply ransack filters
|
|
107
|
+
def multi_line_csv? # True if records produce multiple rows
|
|
108
|
+
def generate # Create CSV and attach file
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Creating a Custom Exporter
|
|
114
|
+
|
|
115
|
+
**Step 1: Create the Export Class**
|
|
116
|
+
|
|
117
|
+
```ruby app/models/spree/exports/subscriptions.rb
|
|
118
|
+
module Spree
|
|
119
|
+
module Exports
|
|
120
|
+
class Subscriptions < Spree::Export
|
|
121
|
+
# Define CSV column headers
|
|
122
|
+
def csv_headers
|
|
123
|
+
%w[id email plan_name status created_at] + metafields_headers
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Eager load associations to avoid N+1 queries
|
|
127
|
+
def scope_includes
|
|
128
|
+
[:user, :plan, { metafields: :metafield_definition }]
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Override scope if needed (e.g., exclude cancelled)
|
|
132
|
+
def scope
|
|
133
|
+
super.where.not(status: 'cancelled')
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Set to true if each record produces multiple CSV rows
|
|
137
|
+
def multi_line_csv?
|
|
138
|
+
false
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Step 2: Add `to_csv` Method to Your Model**
|
|
146
|
+
|
|
147
|
+
```ruby app/models/spree/subscription.rb
|
|
148
|
+
module Spree
|
|
149
|
+
class Subscription < Spree.base_class
|
|
150
|
+
def to_csv(store)
|
|
151
|
+
[
|
|
152
|
+
id,
|
|
153
|
+
user&.email,
|
|
154
|
+
plan&.name,
|
|
155
|
+
status,
|
|
156
|
+
created_at.iso8601
|
|
157
|
+
] + metafields_csv_values(store)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def metafields_csv_values(store)
|
|
163
|
+
Spree::MetafieldDefinition.for_resource_type(self.class.name).order(:namespace, :key).map do |definition|
|
|
164
|
+
metafields.find { |m| m.metafield_definition_id == definition.id }&.value
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Step 3: Register the Export Type**
|
|
172
|
+
|
|
173
|
+
```ruby config/initializers/spree.rb
|
|
174
|
+
Rails.application.config.after_initialize do
|
|
175
|
+
Spree.export_types << Spree::Exports::Subscriptions
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Step 4: Add Translations**
|
|
180
|
+
|
|
181
|
+
```yaml config/locales/en.yml
|
|
182
|
+
en:
|
|
183
|
+
spree:
|
|
184
|
+
subscriptions: Subscriptions
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Multi-line Exports
|
|
188
|
+
|
|
189
|
+
For exports where each record produces multiple rows (like products with variants):
|
|
190
|
+
|
|
191
|
+
```ruby app/models/spree/exports/orders_with_items.rb
|
|
192
|
+
module Spree
|
|
193
|
+
module Exports
|
|
194
|
+
class OrdersWithItems < Spree::Export
|
|
195
|
+
def multi_line_csv?
|
|
196
|
+
true
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def csv_headers
|
|
200
|
+
%w[order_number line_item_sku quantity price]
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def scope_includes
|
|
204
|
+
[line_items: :variant]
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
```ruby app/models/spree/order.rb
|
|
212
|
+
# In the Order model
|
|
213
|
+
def to_csv(store)
|
|
214
|
+
line_items.map do |item|
|
|
215
|
+
[number, item.variant.sku, item.quantity, item.price]
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Export Filtering
|
|
221
|
+
|
|
222
|
+
Exports support Ransack filtering via `search_params`:
|
|
223
|
+
|
|
224
|
+
```ruby
|
|
225
|
+
# In admin, users can filter before exporting
|
|
226
|
+
export = Spree::Exports::Products.new(
|
|
227
|
+
store: current_store,
|
|
228
|
+
user: current_user,
|
|
229
|
+
search_params: { name_cont: 'shirt', status_eq: 'active' }.to_json,
|
|
230
|
+
record_selection: 'filtered' # or 'all' to ignore filters
|
|
231
|
+
)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Imports
|
|
237
|
+
|
|
238
|
+
Imports process CSV files to create or update database records.
|
|
239
|
+
|
|
240
|
+
### Built-in Import Types
|
|
241
|
+
|
|
242
|
+
| Type | Description |
|
|
243
|
+
|------|-------------|
|
|
244
|
+
| `Spree::Imports::Products` | Products and variants |
|
|
245
|
+
|
|
246
|
+
### Import Workflow
|
|
247
|
+
|
|
248
|
+
```
|
|
249
|
+
1. Upload CSV → pending
|
|
250
|
+
2. Auto-map columns → mapping
|
|
251
|
+
3. User confirms mapping → completed_mapping
|
|
252
|
+
4. Parse rows (CreateRowsJob) → processing
|
|
253
|
+
5. Process rows (ProcessRowsJob) → completed/failed
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Import Components
|
|
257
|
+
|
|
258
|
+
#### Import Model
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
module Spree
|
|
262
|
+
class Import < Spree.base_class
|
|
263
|
+
# Associations
|
|
264
|
+
belongs_to :owner, polymorphic: true # Store or Vendor
|
|
265
|
+
belongs_to :user
|
|
266
|
+
has_many :mappings # Field mappings
|
|
267
|
+
has_many :rows # CSV rows to process
|
|
268
|
+
|
|
269
|
+
# State machine
|
|
270
|
+
state_machine initial: :pending do
|
|
271
|
+
event :start_mapping do
|
|
272
|
+
transition to: :mapping
|
|
273
|
+
end
|
|
274
|
+
event :complete_mapping do
|
|
275
|
+
transition from: :mapping, to: :completed_mapping
|
|
276
|
+
end
|
|
277
|
+
event :start_processing do
|
|
278
|
+
transition from: :completed_mapping, to: :processing
|
|
279
|
+
end
|
|
280
|
+
event :complete do
|
|
281
|
+
transition from: :processing, to: :completed
|
|
282
|
+
end
|
|
283
|
+
event :fail do
|
|
284
|
+
transition to: :failed
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
# Key methods
|
|
289
|
+
def import_schema # Returns schema class instance
|
|
290
|
+
def row_processor_class # Returns processor class
|
|
291
|
+
def schema_fields # Fields from schema + metafields
|
|
292
|
+
def mapping_done? # All required fields mapped?
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Import Schema
|
|
298
|
+
|
|
299
|
+
Defines expected CSV fields:
|
|
300
|
+
|
|
301
|
+
```ruby
|
|
302
|
+
module Spree
|
|
303
|
+
class ImportSchema
|
|
304
|
+
FIELDS = []
|
|
305
|
+
|
|
306
|
+
def fields
|
|
307
|
+
self.class::FIELDS
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def required_fields
|
|
311
|
+
FIELDS.select { |f| f[:required] }.map { |f| f[:name] }
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def optional_fields
|
|
315
|
+
FIELDS.reject { |f| f[:required] }.map { |f| f[:name] }
|
|
316
|
+
end
|
|
317
|
+
end
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
#### Import Mapping
|
|
322
|
+
|
|
323
|
+
Maps CSV columns to schema fields:
|
|
324
|
+
|
|
325
|
+
```ruby
|
|
326
|
+
module Spree
|
|
327
|
+
class ImportMapping < Spree.base_class
|
|
328
|
+
belongs_to :import
|
|
329
|
+
|
|
330
|
+
# Attributes
|
|
331
|
+
# schema_field - target field name from schema
|
|
332
|
+
# file_column - CSV column header
|
|
333
|
+
|
|
334
|
+
def try_to_auto_assign_file_column(csv_headers)
|
|
335
|
+
# Matches by parameterized name comparison
|
|
336
|
+
self.file_column = csv_headers.find do |header|
|
|
337
|
+
header.parameterize.underscore == schema_field.parameterize.underscore
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
#### Import Row
|
|
345
|
+
|
|
346
|
+
Represents a single CSV row:
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
module Spree
|
|
350
|
+
class ImportRow < Spree.base_class
|
|
351
|
+
belongs_to :import, counter_cache: :rows_count
|
|
352
|
+
belongs_to :item, polymorphic: true, optional: true # Created record
|
|
353
|
+
|
|
354
|
+
# Attributes
|
|
355
|
+
# row_number - position in CSV
|
|
356
|
+
# data - JSON-serialized row data
|
|
357
|
+
# status - pending/processing/completed/failed
|
|
358
|
+
# validation_errors - error message if failed
|
|
359
|
+
|
|
360
|
+
def process!
|
|
361
|
+
start_processing!
|
|
362
|
+
self.item = import.row_processor_class.new(self).process!
|
|
363
|
+
complete!
|
|
364
|
+
rescue StandardError => e
|
|
365
|
+
self.validation_errors = e.message
|
|
366
|
+
fail!
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
def to_schema_hash
|
|
370
|
+
# Maps CSV data using import.mappings
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
#### Row Processor
|
|
377
|
+
|
|
378
|
+
Transforms row data into database records:
|
|
379
|
+
|
|
380
|
+
```ruby
|
|
381
|
+
module Spree
|
|
382
|
+
module Imports
|
|
383
|
+
module RowProcessors
|
|
384
|
+
class Base
|
|
385
|
+
def initialize(row)
|
|
386
|
+
@row = row
|
|
387
|
+
@import = row.import
|
|
388
|
+
@attributes = row.to_schema_hash
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
attr_reader :row, :import, :attributes
|
|
392
|
+
|
|
393
|
+
def process!
|
|
394
|
+
raise NotImplementedError
|
|
395
|
+
end
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Creating a Custom Importer
|
|
403
|
+
|
|
404
|
+
**Step 1: Create the Import Class**
|
|
405
|
+
|
|
406
|
+
```ruby app/models/spree/imports/subscriptions.rb
|
|
407
|
+
module Spree
|
|
408
|
+
module Imports
|
|
409
|
+
class Subscriptions < Spree::Import
|
|
410
|
+
def row_processor_class
|
|
411
|
+
Spree::Imports::RowProcessors::Subscription
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
end
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
**Step 2: Define the Schema**
|
|
419
|
+
|
|
420
|
+
```ruby app/models/spree/import_schemas/subscriptions.rb
|
|
421
|
+
module Spree
|
|
422
|
+
module ImportSchemas
|
|
423
|
+
class Subscriptions < Spree::ImportSchema
|
|
424
|
+
FIELDS = [
|
|
425
|
+
{ name: 'email', label: 'Customer Email', required: true },
|
|
426
|
+
{ name: 'plan_name', label: 'Plan Name', required: true },
|
|
427
|
+
{ name: 'status', label: 'Status', required: true },
|
|
428
|
+
{ name: 'start_date', label: 'Start Date' },
|
|
429
|
+
{ name: 'billing_interval', label: 'Billing Interval' },
|
|
430
|
+
{ name: 'amount', label: 'Amount' },
|
|
431
|
+
{ name: 'currency', label: 'Currency' }
|
|
432
|
+
].freeze
|
|
433
|
+
end
|
|
434
|
+
end
|
|
435
|
+
end
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Step 3: Create the Row Processor**
|
|
439
|
+
|
|
440
|
+
```ruby app/services/spree/imports/row_processors/subscription.rb
|
|
441
|
+
module Spree
|
|
442
|
+
module Imports
|
|
443
|
+
module RowProcessors
|
|
444
|
+
class Subscription < Base
|
|
445
|
+
def process!
|
|
446
|
+
user = find_or_create_user
|
|
447
|
+
plan = find_plan
|
|
448
|
+
|
|
449
|
+
subscription = Spree::Subscription.find_or_initialize_by(
|
|
450
|
+
user: user,
|
|
451
|
+
plan: plan
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
subscription.status = attributes['status'] if attributes['status'].present?
|
|
455
|
+
subscription.start_date = parse_date(attributes['start_date']) if attributes['start_date'].present?
|
|
456
|
+
subscription.billing_interval = attributes['billing_interval'] if attributes['billing_interval'].present?
|
|
457
|
+
|
|
458
|
+
if attributes['amount'].present?
|
|
459
|
+
currency = attributes['currency'].presence || import.store.default_currency
|
|
460
|
+
subscription.set_price(currency, attributes['amount'])
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
subscription.save!
|
|
464
|
+
subscription
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
private
|
|
468
|
+
|
|
469
|
+
def find_or_create_user
|
|
470
|
+
email = attributes['email'].strip.downcase
|
|
471
|
+
Spree.user_class.find_or_create_by!(email: email)
|
|
472
|
+
end
|
|
473
|
+
|
|
474
|
+
def find_plan
|
|
475
|
+
Spree::Plan.find_by!(name: attributes['plan_name'].strip)
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def parse_date(date_string)
|
|
479
|
+
Date.parse(date_string)
|
|
480
|
+
rescue ArgumentError
|
|
481
|
+
nil
|
|
482
|
+
end
|
|
483
|
+
end
|
|
484
|
+
end
|
|
485
|
+
end
|
|
486
|
+
end
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
**Step 4: Register the Import Type**
|
|
490
|
+
|
|
491
|
+
```ruby config/initializers/spree.rb
|
|
492
|
+
Rails.application.config.after_initialize do
|
|
493
|
+
Spree.import_types << Spree::Imports::Subscriptions
|
|
494
|
+
end
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Step 5: Add Translations**
|
|
498
|
+
|
|
499
|
+
```yaml config/locales/en.yml
|
|
500
|
+
en:
|
|
501
|
+
spree:
|
|
502
|
+
subscriptions: Subscriptions
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
### Products Import Schema
|
|
506
|
+
|
|
507
|
+
The built-in products import supports these fields:
|
|
508
|
+
|
|
509
|
+
**Required Fields:**
|
|
510
|
+
- `slug` - Product URL slug
|
|
511
|
+
- `sku` - Variant SKU
|
|
512
|
+
- `name` - Product name
|
|
513
|
+
- `price` - Variant price
|
|
514
|
+
|
|
515
|
+
**Optional Fields:**
|
|
516
|
+
- `status` - Product status (active/draft/archived)
|
|
517
|
+
- `description` - Product description
|
|
518
|
+
- `meta_title`, `meta_description`, `meta_keywords` - SEO metadata
|
|
519
|
+
- `tags` - Product tags
|
|
520
|
+
- `compare_at_price` - Original price for sale display
|
|
521
|
+
- `currency` - Price currency
|
|
522
|
+
- `width`, `height`, `depth`, `dimensions_unit` - Dimensions
|
|
523
|
+
- `weight`, `weight_unit` - Weight
|
|
524
|
+
- `available_on`, `discontinue_on` - Availability dates
|
|
525
|
+
- `track_inventory` - Enable inventory tracking
|
|
526
|
+
- `inventory_count`, `inventory_backorderable` - Stock settings
|
|
527
|
+
- `tax_category`, `shipping_category` - Category assignments
|
|
528
|
+
- `image1_src`, `image2_src`, `image3_src` - Image URLs
|
|
529
|
+
- `option1_name`, `option1_value` through `option3_name`, `option3_value` - Variant options
|
|
530
|
+
- `category1`, `category2`, `category3` - Taxon assignments (format: "Taxonomy -> Taxon -> Child Taxon")
|
|
531
|
+
|
|
532
|
+
### Handling Multi-Variant Products
|
|
533
|
+
|
|
534
|
+
The products import handles variants intelligently:
|
|
535
|
+
|
|
536
|
+
1. **Master variant rows** (no option values): Create/update the product and its master variant
|
|
537
|
+
2. **Non-master variant rows** (with option values): Create additional variants for an existing product
|
|
538
|
+
|
|
539
|
+
```csv
|
|
540
|
+
slug,sku,name,price,option1_name,option1_value,option2_name,option2_value
|
|
541
|
+
my-tshirt,TSHIRT-001,My T-Shirt,29.99,,,
|
|
542
|
+
my-tshirt,TSHIRT-S-RED,My T-Shirt,29.99,Size,Small,Color,Red
|
|
543
|
+
my-tshirt,TSHIRT-M-RED,My T-Shirt,29.99,Size,Medium,Color,Red
|
|
544
|
+
my-tshirt,TSHIRT-L-RED,My T-Shirt,29.99,Size,Large,Color,Red
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Metafield Support
|
|
548
|
+
|
|
549
|
+
Both imports and exports support metafields dynamically:
|
|
550
|
+
|
|
551
|
+
**Export:** Metafield definitions are automatically added as CSV columns using the format `metafield.{namespace}.{key}`.
|
|
552
|
+
|
|
553
|
+
**Import:** Map CSV columns to metafield definitions. The system automatically detects columns matching the metafield pattern and updates the corresponding metafield values.
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
557
|
+
## Background Jobs
|
|
558
|
+
|
|
559
|
+
### CreateRowsJob
|
|
560
|
+
|
|
561
|
+
Parses CSV and creates ImportRow records:
|
|
562
|
+
|
|
563
|
+
```ruby
|
|
564
|
+
module Spree
|
|
565
|
+
module Imports
|
|
566
|
+
class CreateRowsJob < Spree::BaseJob
|
|
567
|
+
queue_as Spree.queues.imports
|
|
568
|
+
|
|
569
|
+
def perform(import_id)
|
|
570
|
+
import = Spree::Import.find(import_id)
|
|
571
|
+
# Stream CSV, batch insert rows
|
|
572
|
+
# Then enqueue ProcessRowsJob
|
|
573
|
+
end
|
|
574
|
+
end
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### ProcessRowsJob
|
|
580
|
+
|
|
581
|
+
Processes pending rows through row processors:
|
|
582
|
+
|
|
583
|
+
```ruby
|
|
584
|
+
module Spree
|
|
585
|
+
module Imports
|
|
586
|
+
class ProcessRowsJob < Spree::BaseJob
|
|
587
|
+
queue_as Spree.queues.imports
|
|
588
|
+
|
|
589
|
+
def perform(import_id)
|
|
590
|
+
import = Spree::Import.find(import_id)
|
|
591
|
+
import.rows.pending_and_failed.find_each do |row|
|
|
592
|
+
row.process!
|
|
593
|
+
end
|
|
594
|
+
import.complete!
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
```
|
|
600
|
+
|
|
601
|
+
### GenerateJob (Exports)
|
|
602
|
+
|
|
603
|
+
Generates CSV files for exports:
|
|
604
|
+
|
|
605
|
+
```ruby
|
|
606
|
+
module Spree
|
|
607
|
+
module Exports
|
|
608
|
+
class GenerateJob < Spree::BaseJob
|
|
609
|
+
queue_as Spree.queues.exports
|
|
610
|
+
|
|
611
|
+
def perform(export_id)
|
|
612
|
+
export = Spree::Export.find(export_id)
|
|
613
|
+
export.generate
|
|
614
|
+
end
|
|
615
|
+
end
|
|
616
|
+
end
|
|
617
|
+
end
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## Events
|
|
623
|
+
|
|
624
|
+
### Import Events
|
|
625
|
+
|
|
626
|
+
| Event | Trigger |
|
|
627
|
+
|-------|---------|
|
|
628
|
+
| `import.created` | Import record created |
|
|
629
|
+
| `import.completed` | All rows processed |
|
|
630
|
+
|
|
631
|
+
### Export Events
|
|
632
|
+
|
|
633
|
+
| Event | Trigger |
|
|
634
|
+
|-------|---------|
|
|
635
|
+
| `export.created` | Export record created (triggers generation) |
|
|
636
|
+
|
|
637
|
+
### Import Row Events
|
|
638
|
+
|
|
639
|
+
| Event | Trigger |
|
|
640
|
+
|-------|---------|
|
|
641
|
+
| `import_row.completed` | Row processed successfully |
|
|
642
|
+
| `import_row.failed` | Row processing failed |
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Configuration
|
|
647
|
+
|
|
648
|
+
### Queue Configuration
|
|
649
|
+
|
|
650
|
+
```ruby config/initializers/spree.rb
|
|
651
|
+
# Configure job queues
|
|
652
|
+
Spree.queues.imports = :imports
|
|
653
|
+
Spree.queues.exports = :exports
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
### Preferences
|
|
657
|
+
|
|
658
|
+
Imports support configurable delimiter:
|
|
659
|
+
|
|
660
|
+
```ruby
|
|
661
|
+
import.preferred_delimiter = ';' # Default: ','
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
---
|
|
665
|
+
|
|
666
|
+
## Key Files Reference
|
|
667
|
+
|
|
668
|
+
| File | Purpose |
|
|
669
|
+
|------|---------|
|
|
670
|
+
| `core/app/models/spree/import.rb` | Base import model |
|
|
671
|
+
| `core/app/models/spree/export.rb` | Base export model |
|
|
672
|
+
| `core/app/models/spree/import_schema.rb` | Base schema class |
|
|
673
|
+
| `core/app/models/spree/import_mapping.rb` | Field mapping model |
|
|
674
|
+
| `core/app/models/spree/import_row.rb` | Row model with processing |
|
|
675
|
+
| `core/app/models/spree/imports/products.rb` | Products import type |
|
|
676
|
+
| `core/app/models/spree/import_schemas/products.rb` | Products schema |
|
|
677
|
+
| `core/app/services/spree/imports/row_processors/base.rb` | Base processor |
|
|
678
|
+
| `core/app/services/spree/imports/row_processors/product_variant.rb` | Products processor |
|
|
679
|
+
| `core/app/models/spree/exports/products.rb` | Products export |
|
|
680
|
+
| `core/app/models/spree/exports/orders.rb` | Orders export |
|
|
681
|
+
| `core/app/models/spree/exports/customers.rb` | Customers export |
|
|
682
|
+
| `core/app/jobs/spree/imports/create_rows_job.rb` | Row creation job |
|
|
683
|
+
| `core/app/jobs/spree/imports/process_rows_job.rb` | Row processing job |
|
|
684
|
+
| `core/app/jobs/spree/exports/generate_job.rb` | Export generation job |
|
|
685
|
+
| `admin/app/controllers/spree/admin/imports_controller.rb` | Admin imports controller |
|
|
686
|
+
| `admin/app/controllers/spree/admin/exports_controller.rb` | Admin exports controller |
|
|
687
|
+
|
|
688
|
+
## Permissions
|
|
689
|
+
|
|
690
|
+
Access is controlled via CanCanCan:
|
|
691
|
+
|
|
692
|
+
```ruby
|
|
693
|
+
# Allow admin to manage imports/exports
|
|
694
|
+
can :manage, Spree::Import
|
|
695
|
+
can :manage, Spree::Export
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
Records are filtered by `current_ability` ensuring users only export data they have access to.
|