@spree/docs 0.1.176 → 0.1.178
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/dist/api-reference/store.yaml +48 -48
- package/dist/developer/cli/quickstart.md +1 -1
- package/dist/developer/contributing/creating-an-extension.md +2 -2
- package/dist/developer/core-concepts/addresses.md +3 -3
- package/dist/developer/core-concepts/architecture.md +4 -4
- package/dist/developer/core-concepts/calculators.md +2 -2
- package/dist/developer/core-concepts/carts.md +207 -0
- package/dist/developer/core-concepts/channels.md +3 -3
- package/dist/developer/core-concepts/customers.md +1 -1
- package/dist/developer/core-concepts/events.md +1 -1
- package/dist/developer/core-concepts/fulfillments.md +278 -0
- package/dist/developer/core-concepts/imports-exports.md +6 -18
- package/dist/developer/core-concepts/inventory.md +3 -3
- package/dist/developer/core-concepts/media.md +1 -1
- package/dist/developer/core-concepts/metafields.md +1 -1
- package/dist/developer/core-concepts/orders.md +92 -339
- package/dist/developer/core-concepts/payments.md +1 -1
- package/dist/developer/core-concepts/promotions.md +68 -195
- package/dist/developer/core-concepts/returns-exchanges-claims.md +173 -0
- package/dist/developer/core-concepts/store-credits-gift-cards.md +16 -18
- package/dist/developer/core-concepts/stores.md +1 -1
- package/dist/developer/core-concepts/taxes-discounts-fees.md +199 -0
- package/dist/developer/core-concepts/taxes.md +3 -3
- package/dist/developer/create-spree-app/quickstart.md +1 -1
- package/dist/developer/customization/api.md +36 -11
- package/dist/developer/customization/configuration.md +207 -37
- package/dist/developer/customization/decorators.md +8 -8
- package/dist/developer/customization/permissions.md +34 -229
- package/dist/developer/customization/quickstart.md +159 -108
- package/dist/developer/customization/validations.md +1 -1
- package/dist/developer/dashboard/overview.md +1 -1
- package/dist/developer/how-to/build-a-b2b-store.md +21 -0
- package/dist/developer/how-to/build-a-marketplace.md +25 -0
- package/dist/developer/how-to/custom-api-authentication.md +1 -1
- package/dist/developer/how-to/custom-delivery-rate-provider.md +242 -0
- package/dist/developer/how-to/custom-document-numbers.md +154 -0
- package/dist/developer/how-to/custom-order-routing.md +1 -1
- package/dist/developer/how-to/custom-payment-method.md +1 -1
- package/dist/developer/how-to/custom-promotion.md +76 -115
- package/dist/developer/how-to/custom-report.md +2 -2
- package/dist/developer/how-to/custom-stock-splitter.md +5 -5
- package/dist/developer/how-to/sell-digital-products.md +20 -0
- package/dist/developer/multi-tenant/quickstart.md +1 -1
- package/dist/developer/providers/dam.md +14 -0
- package/dist/developer/providers/erp.md +31 -0
- package/dist/developer/providers/fulfillment.md +25 -0
- package/dist/developer/{deployment/telemetry.md → providers/observability.md} +1 -1
- package/dist/developer/providers/overview.md +44 -0
- package/dist/developer/providers/pim.md +25 -0
- package/dist/developer/providers/sso.md +20 -0
- package/dist/developer/sdk/admin/extending.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +1 -1
- package/dist/developer/sdk/admin/resources.md +1 -1
- package/dist/developer/sdk/extending.md +1 -1
- package/dist/developer/tutorial/admin-api.md +13 -0
- package/dist/developer/tutorial/dashboard-plugin.md +14 -0
- package/dist/developer/tutorial/events.md +7 -161
- package/dist/developer/tutorial/introduction.md +15 -35
- package/dist/developer/tutorial/model.md +7 -98
- package/dist/developer/tutorial/store-api.md +13 -0
- package/dist/developer/tutorial/storefront.md +12 -0
- package/dist/developer/tutorial/testing.md +7 -711
- package/dist/developer/upgrades/5.6-to-6.0.md +2 -2
- package/package.json +1 -1
- package/dist/developer/admin/admin.md +0 -214
- package/dist/developer/admin/authentication.md +0 -59
- package/dist/developer/admin/components.md +0 -711
- package/dist/developer/admin/custom-css.md +0 -256
- package/dist/developer/admin/custom-javascript.md +0 -116
- package/dist/developer/admin/extending-ui.md +0 -1839
- package/dist/developer/admin/form-builder.md +0 -444
- package/dist/developer/admin/helper-methods.md +0 -531
- package/dist/developer/admin/navigation.md +0 -805
- package/dist/developer/admin/tables.md +0 -490
- package/dist/developer/advanced/adding_spree_to_rails_app.md +0 -92
- package/dist/developer/core-concepts/adjustments.md +0 -113
- package/dist/developer/core-concepts/reports.md +0 -208
- package/dist/developer/core-concepts/shipments.md +0 -307
- package/dist/developer/core-concepts/users.md +0 -303
- package/dist/developer/customization/authentication.md +0 -100
- package/dist/developer/customization/checkout.md +0 -202
- package/dist/developer/customization/emails.md +0 -18
- package/dist/developer/customization/routes.md +0 -24
- package/dist/developer/multi-vendor/installation.md +0 -61
- package/dist/developer/multi-vendor/quickstart.md +0 -17
- package/dist/developer/tutorial/admin.md +0 -206
- package/dist/developer/tutorial/api.md +0 -606
- package/dist/developer/tutorial/extending-models.md +0 -393
- package/dist/developer/tutorial/sdk.md +0 -170
- package/dist/developer/upgrades/2.0-to-2.1.md +0 -46
- package/dist/developer/upgrades/2.1-to-2.2.md +0 -59
- package/dist/developer/upgrades/2.2-to-2.3.md +0 -44
- package/dist/developer/upgrades/2.3-to-2.4.md +0 -42
- package/dist/developer/upgrades/3.0-to-3.1.md +0 -47
- package/dist/developer/upgrades/3.1-to-3.2.md +0 -34
- package/dist/developer/upgrades/3.2-to-3.3.md +0 -70
- package/dist/developer/upgrades/3.3-to-3.4.md +0 -36
- package/dist/developer/upgrades/3.4-to-3.5.md +0 -44
- package/dist/developer/upgrades/3.5-to-3.6.md +0 -40
- package/dist/developer/upgrades/3.6-to-3.7.md +0 -62
- package/dist/developer/upgrades/3.7-to-4.0.md +0 -152
- package/dist/developer/upgrades/4.0-to-4.1.md +0 -92
- package/dist/developer/upgrades/4.1-to-4.2.md +0 -109
- package/dist/developer/upgrades/4.10-to-5.0.md +0 -131
- package/dist/developer/upgrades/4.2-to-4.3.md +0 -100
- package/dist/developer/upgrades/4.3-to-4.4.md +0 -125
- package/dist/developer/upgrades/4.4-to-4.5.md +0 -94
- package/dist/developer/upgrades/4.5-to-4.6.md +0 -119
- package/dist/developer/upgrades/4.6-to-4.7.md +0 -39
- package/dist/developer/upgrades/4.8-to-4.9.md +0 -24
- package/dist/developer/upgrades/4.9-to-4.10.md +0 -24
- package/dist/developer/upgrades/4.x-to-4.8.md +0 -52
- package/dist/developer/upgrades/5.0-to-5.1.md +0 -28
- package/dist/developer/upgrades/5.1-to-5.2.md +0 -131
- package/dist/developer/upgrades/5.2-to-5.3.md +0 -338
- package/dist/developer/upgrades/5.3-to-5.4.md +0 -277
- package/dist/developer/upgrades/5.4-to-5.5.md +0 -301
- package/dist/developer/upgrades/5.5-to-5.6.md +0 -207
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Extending Core Models
|
|
3
|
-
description: Tutorial — extend Spree's core models with a custom Brand resource, generate a migration, and associate it with Products via a decorator.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
> **NOTE:** This is a backend customization. You need to perform it in the `backend` directory if you used `create-spree-app` to set up your Spree application.
|
|
7
|
-
|
|
8
|
-
In this tutorial, we'll connect our custom Brand model with Spree's core Product model. This is a common pattern when building features that need to integrate with existing Spree functionality.
|
|
9
|
-
|
|
10
|
-
> **INFO:** This guide assumes you've completed the [Model](model.md) and [Admin](admin.md) tutorials.
|
|
11
|
-
|
|
12
|
-
## What We're Building
|
|
13
|
-
|
|
14
|
-
By the end of this tutorial, you'll have:
|
|
15
|
-
|
|
16
|
-
- Products associated with Brands
|
|
17
|
-
- A brand selector in the Product admin form
|
|
18
|
-
- Understanding of how to safely extend Spree core models
|
|
19
|
-
|
|
20
|
-
## Choosing the Right Approach
|
|
21
|
-
|
|
22
|
-
Before extending Spree models, consider which approach fits your needs best:
|
|
23
|
-
|
|
24
|
-
| What you need | Recommended approach |
|
|
25
|
-
|---------------|---------------------|
|
|
26
|
-
| Add association (belongs_to, has_many) | **Decorator** (this tutorial) |
|
|
27
|
-
| Add validation or scope | **Decorator** |
|
|
28
|
-
| Add new instance/class method | **Decorator** |
|
|
29
|
-
| React to model changes (after save, etc.) | [Events subscriber](../core-concepts/events.md) |
|
|
30
|
-
| Sync with external service on changes | [Events](../core-concepts/events.md) or [Webhooks](../core-concepts/webhooks.md) |
|
|
31
|
-
| Add searchable/filterable field | [Ransack configuration](../core-concepts/search-filtering.md#extending-ransackable-configuration) |
|
|
32
|
-
| Add admin UI elements | [Admin Partials](../admin/extending-ui.md) |
|
|
33
|
-
|
|
34
|
-
> **INFO:** This tutorial uses **decorators** because we're adding a structural association between models. For behavioral changes like callbacks, prefer [Events](../core-concepts/events.md) instead - they're easier to test and maintain.
|
|
35
|
-
|
|
36
|
-
## Understanding Decorators
|
|
37
|
-
|
|
38
|
-
When working with Spree, you'll often need to add functionality to existing models like `Spree::Product` or `Spree::Order`. However, you shouldn't modify these files directly because:
|
|
39
|
-
|
|
40
|
-
1. **Upgrades** - Your changes would be lost when updating Spree
|
|
41
|
-
2. **Maintainability** - It's hard to track what you've customized
|
|
42
|
-
3. **Conflicts** - Direct modifications can conflict with Spree's code
|
|
43
|
-
|
|
44
|
-
Instead, we use **decorators** - a Ruby pattern that lets you add or modify behavior of existing classes without changing their original source code.
|
|
45
|
-
|
|
46
|
-
### How Decorators Work
|
|
47
|
-
|
|
48
|
-
In Ruby, classes are "open" - you can add methods to them at any time. Decorators leverage this by:
|
|
49
|
-
|
|
50
|
-
1. Creating a module with your new methods
|
|
51
|
-
2. Using `Module#prepend` to inject your module into the class's inheritance chain
|
|
52
|
-
3. Your methods run first, and can call `super` to invoke the original method
|
|
53
|
-
|
|
54
|
-
```ruby
|
|
55
|
-
# This is the basic pattern
|
|
56
|
-
module Spree
|
|
57
|
-
module ProductDecorator
|
|
58
|
-
# Add a new method
|
|
59
|
-
def my_new_method
|
|
60
|
-
"Hello from decorator!"
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Override an existing method
|
|
64
|
-
def existing_method
|
|
65
|
-
# Do something before
|
|
66
|
-
result = super # Call the original method
|
|
67
|
-
# Do something after
|
|
68
|
-
result
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
Product.prepend(ProductDecorator)
|
|
73
|
-
end
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
The key line is `Product.prepend(ProductDecorator)` - this inserts your module at the beginning of the method lookup chain, so your methods are found first.
|
|
77
|
-
|
|
78
|
-
## Step 1: Create the Migration
|
|
79
|
-
|
|
80
|
-
First, add a `brand_id` column to the products table:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
```bash Spree CLI (Docker)
|
|
84
|
-
spree generate migration AddBrandIdToSpreeProducts brand_id:integer:index
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
```bash Without Spree CLI
|
|
88
|
-
bin/rails g migration AddBrandIdToSpreeProducts brand_id:integer:index
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
Edit the migration to add an index (but no foreign key constraint, keeping it optional):
|
|
93
|
-
|
|
94
|
-
```ruby db/migrate/XXXXXXXXXXXXXX_add_brand_id_to_spree_products.rb
|
|
95
|
-
class AddBrandIdToSpreeProducts < ActiveRecord::Migration[8.0]
|
|
96
|
-
def change
|
|
97
|
-
add_column :spree_products, :brand_id, :integer
|
|
98
|
-
add_index :spree_products, :brand_id
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
Run the migration:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```bash Spree CLI (Docker)
|
|
107
|
-
spree migrate
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
```bash Without Spree CLI
|
|
111
|
-
bin/rails db:migrate
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
> **INFO:** We intentionally don't add a foreign key constraint. This keeps the association optional and avoids issues if brands are deleted. Spree follows this pattern for flexibility.
|
|
116
|
-
|
|
117
|
-
## Step 2: Generate the Product Decorator
|
|
118
|
-
|
|
119
|
-
Spree provides a generator to create decorator files with the correct structure:
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
```bash Spree CLI (Docker)
|
|
123
|
-
spree generate model_decorator Spree::Product
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
```bash Without Spree CLI
|
|
127
|
-
bin/rails g spree:model_decorator Spree::Product
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
This creates `app/models/spree/product_decorator.rb`:
|
|
132
|
-
|
|
133
|
-
```ruby app/models/spree/product_decorator.rb
|
|
134
|
-
module Spree
|
|
135
|
-
module ProductDecorator
|
|
136
|
-
def self.prepended(base)
|
|
137
|
-
# Class-level configurations go here
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
Product.prepend(ProductDecorator)
|
|
142
|
-
end
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
## Step 3: Add the Brand Association to Product
|
|
146
|
-
|
|
147
|
-
Update the decorator to add the `belongs_to` association:
|
|
148
|
-
|
|
149
|
-
```ruby app/models/spree/product_decorator.rb {4}
|
|
150
|
-
module Spree
|
|
151
|
-
module ProductDecorator
|
|
152
|
-
def self.prepended(base)
|
|
153
|
-
base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
|
|
154
|
-
end
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
Product.prepend(ProductDecorator)
|
|
158
|
-
end
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Understanding the Code
|
|
162
|
-
|
|
163
|
-
- `self.prepended(base)` - This callback runs when the module is prepended to a class. The `base` parameter is the class being decorated (`Spree::Product`)
|
|
164
|
-
- `base.belongs_to` - We call class methods on `base` to add associations, validations, scopes, etc.
|
|
165
|
-
- `optional: true` - Products don't require a brand (the `brand_id` can be `NULL`)
|
|
166
|
-
|
|
167
|
-
## Step 4: Add Products Association to Brand
|
|
168
|
-
|
|
169
|
-
Now update your Brand model to define the reverse association:
|
|
170
|
-
|
|
171
|
-
```ruby app/models/spree/brand.rb {5}
|
|
172
|
-
module Spree
|
|
173
|
-
class Brand < Spree::Base
|
|
174
|
-
# ... existing code ...
|
|
175
|
-
|
|
176
|
-
has_many :products, class_name: 'Spree::Product', dependent: :nullify
|
|
177
|
-
|
|
178
|
-
# ... rest of your model ...
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
> **INFO:** We use `dependent: :nullify` instead of `dependent: :destroy`. When a brand is deleted, products will have their `brand_id` set to `NULL` rather than being deleted. This is safer for e-commerce data.
|
|
184
|
-
|
|
185
|
-
## Step 5: Permit the Brand Parameter
|
|
186
|
-
|
|
187
|
-
For the admin form to save the brand association, we need to permit the `brand_id` parameter.
|
|
188
|
-
|
|
189
|
-
Add to your Spree initializer:
|
|
190
|
-
|
|
191
|
-
```ruby config/initializers/spree.rb {3}
|
|
192
|
-
# .. other code ..
|
|
193
|
-
|
|
194
|
-
Spree::PermittedAttributes.product_attributes << :brand_id
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Step 6: Add Brand Selector to Product Admin Form
|
|
198
|
-
|
|
199
|
-
Create a partial to inject the brand selector into the product form. Spree's admin product form has injection points for customization.
|
|
200
|
-
|
|
201
|
-
Create the partial:
|
|
202
|
-
|
|
203
|
-
```erb app/views/spree/admin/products/_brand_field.html.erb
|
|
204
|
-
<div class="card mb-6">
|
|
205
|
-
<div class="card-header">
|
|
206
|
-
<h5 class="card-title"><%= Spree.t(:brand) %></h5>
|
|
207
|
-
</div>
|
|
208
|
-
<div class="card-body">
|
|
209
|
-
<%= f.spree_select :brand_id,
|
|
210
|
-
Spree::Brand.order(:name).pluck(:name, :id),
|
|
211
|
-
{ include_blank: true, label: Spree.t(:brand) } %>
|
|
212
|
-
</div>
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Register this partial to appear in the product form. Add to your initializer:
|
|
216
|
-
|
|
217
|
-
**Spree 5.2+:**
|
|
218
|
-
|
|
219
|
-
```ruby config/initializers/spree.rb
|
|
220
|
-
Rails.application.config.after_initialize do
|
|
221
|
-
Spree.admin.partials.product_form_sidebar << 'spree/admin/products/brand_field'
|
|
222
|
-
end
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
**Spree 5.1 and below:**
|
|
226
|
-
|
|
227
|
-
```ruby config/initializers/spree.rb
|
|
228
|
-
Rails.application.config.spree_admin.product_form_sidebar_partials << 'spree/admin/products/brand_field'
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
## Step 7: Add Translation
|
|
233
|
-
|
|
234
|
-
Add the translation for the brand label:
|
|
235
|
-
|
|
236
|
-
```yaml config/locales/en.yml
|
|
237
|
-
en:
|
|
238
|
-
spree:
|
|
239
|
-
brand: Brand
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
## Testing the Association
|
|
243
|
-
|
|
244
|
-
Verify everything works in the Rails console:
|
|
245
|
-
|
|
246
|
-
```ruby
|
|
247
|
-
# Create a brand and product
|
|
248
|
-
brand = Spree::Brand.create!(name: 'Nike')
|
|
249
|
-
product = Spree::Product.first
|
|
250
|
-
product.update!(brand: brand)
|
|
251
|
-
|
|
252
|
-
# Test associations
|
|
253
|
-
product.brand # => #<Spree::Brand id: 1, name: "Nike"...>
|
|
254
|
-
brand.products # => [#<Spree::Product...>]
|
|
255
|
-
brand.products.count # => 1
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
## Decorator Best Practices
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
- **Use prepended callback** — Always use `self.prepended(base)` for class-level additions like associations, validations, and scopes.
|
|
262
|
-
|
|
263
|
-
- **Keep decorators focused** — Each decorator should have a single responsibility. Create multiple decorators if needed.
|
|
264
|
-
|
|
265
|
-
- **Call super when overriding** — When overriding methods, call `super` to preserve original behavior unless you intentionally want to replace it.
|
|
266
|
-
|
|
267
|
-
- **Test decorated behavior** — Write tests specifically for your decorated functionality to catch regressions.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
### Common Decorator Patterns
|
|
271
|
-
|
|
272
|
-
#### Adding Validations
|
|
273
|
-
|
|
274
|
-
```ruby
|
|
275
|
-
def self.prepended(base)
|
|
276
|
-
base.validates :custom_field, presence: true
|
|
277
|
-
end
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
#### Adding Scopes
|
|
281
|
-
|
|
282
|
-
```ruby
|
|
283
|
-
def self.prepended(base)
|
|
284
|
-
base.scope :featured, -> { where(featured: true) }
|
|
285
|
-
end
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
#### Adding Callbacks
|
|
289
|
-
|
|
290
|
-
> **WARNING:** For callbacks that trigger side effects (syncing to external services, sending notifications, etc.), use [Events subscribers](../core-concepts/events.md) instead of decorator callbacks. Events are easier to test and won't break during Spree upgrades.
|
|
291
|
-
|
|
292
|
-
**Decorator approach** (use only for simple, internal logic):
|
|
293
|
-
|
|
294
|
-
```ruby
|
|
295
|
-
def self.prepended(base)
|
|
296
|
-
base.before_save :normalize_name
|
|
297
|
-
end
|
|
298
|
-
|
|
299
|
-
def normalize_name
|
|
300
|
-
self.name = name.strip.titleize if name.present?
|
|
301
|
-
end
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
**Events approach** (recommended for side effects): subscribe to `product.updated` and react in a `Spree::Subscriber` — no decorator, no callback coupling. The [Events & Webhooks tutorial](events.md) builds this out fully, including external-system sync (OMS, warehouse) and outbound webhooks.
|
|
305
|
-
|
|
306
|
-
#### Adding Class Methods
|
|
307
|
-
|
|
308
|
-
```ruby
|
|
309
|
-
def self.prepended(base)
|
|
310
|
-
base.extend ClassMethods
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
module ClassMethods
|
|
314
|
-
def my_class_method
|
|
315
|
-
# Class method logic
|
|
316
|
-
end
|
|
317
|
-
end
|
|
318
|
-
```
|
|
319
|
-
|
|
320
|
-
## Complete Files
|
|
321
|
-
|
|
322
|
-
### Product Decorator
|
|
323
|
-
|
|
324
|
-
```ruby app/models/spree/product_decorator.rb
|
|
325
|
-
module Spree
|
|
326
|
-
module ProductDecorator
|
|
327
|
-
def self.prepended(base)
|
|
328
|
-
base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
|
|
329
|
-
end
|
|
330
|
-
end
|
|
331
|
-
|
|
332
|
-
Product.prepend(ProductDecorator)
|
|
333
|
-
end
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
### Brand Model (Updated)
|
|
337
|
-
|
|
338
|
-
```ruby app/models/spree/brand.rb
|
|
339
|
-
module Spree
|
|
340
|
-
class Brand < Spree::Base
|
|
341
|
-
has_many :products, class_name: 'Spree::Product', dependent: :nullify
|
|
342
|
-
|
|
343
|
-
has_one_attached :logo
|
|
344
|
-
has_rich_text :description
|
|
345
|
-
|
|
346
|
-
validates :name, presence: true
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
> **INFO:** SEO features like slugs, meta titles, and FriendlyId are covered in the [Slugs](../core-concepts/slugs.md) documentation.
|
|
352
|
-
|
|
353
|
-
### Spree Initializer Additions
|
|
354
|
-
|
|
355
|
-
```ruby config/initializers/spree.rb
|
|
356
|
-
# Permit brand_id in product params
|
|
357
|
-
Spree::PermittedAttributes.product_attributes << :brand_id
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
**Spree 5.2+:**
|
|
361
|
-
|
|
362
|
-
```ruby config/initializers/spree.rb
|
|
363
|
-
Rails.application.config.after_initialize do
|
|
364
|
-
# Add brand field to product form
|
|
365
|
-
Spree.admin.partials.product_form << 'spree/admin/products/brand_field'
|
|
366
|
-
end
|
|
367
|
-
```
|
|
368
|
-
|
|
369
|
-
**Spree 5.1 and below:**
|
|
370
|
-
|
|
371
|
-
```ruby config/initializers/spree.rb
|
|
372
|
-
# Add brand field to product form
|
|
373
|
-
Rails.application.config.spree_admin.product_form_partials << 'spree/admin/products/brand_field'
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
## Next Step
|
|
378
|
-
|
|
379
|
-
Now that Brands are connected to Products, let's expose them through the Store API:
|
|
380
|
-
|
|
381
|
-
- [6. API](api.md) — Create API endpoints for brands and extend the Product API response
|
|
382
|
-
|
|
383
|
-
## Related Documentation
|
|
384
|
-
|
|
385
|
-
- [Model Tutorial](model.md) - Creating the Brand model
|
|
386
|
-
- [Admin Tutorial](admin.md) - Building the admin interface
|
|
387
|
-
- [API Tutorial](api.md) - Exposing brands through the API
|
|
388
|
-
- [Events & Webhooks tutorial](events.md) - Subscribers, custom events, and webhooks in the Brand storyline
|
|
389
|
-
- [Events](../core-concepts/events.md) - Subscribe to model changes without decorators
|
|
390
|
-
- [Webhooks](../core-concepts/webhooks.md) - HTTP callbacks for external integrations
|
|
391
|
-
- [Decorators](../customization/decorators.md) - Full decorator reference
|
|
392
|
-
- [Customization Overview](../customization/quickstart.md) - More customization patterns
|
|
393
|
-
- [Products](../core-concepts/products.md) - Product model documentation
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: SDK
|
|
3
|
-
description: Use the Spree TypeScript SDK to consume your custom Brand endpoints, fetch extended Product data, and surface the new resource in your headless storefront.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
In this tutorial, we'll use the `@spree/sdk` TypeScript SDK to consume the Brand API endpoints we created in the [API tutorial](api.md), and work with the extended Product data that now includes brand information.
|
|
7
|
-
|
|
8
|
-
> **INFO:** This guide assumes you've completed the [API](api.md) tutorial and have the Brand endpoints running.
|
|
9
|
-
|
|
10
|
-
## What We're Building
|
|
11
|
-
|
|
12
|
-
By the end of this tutorial, you'll have:
|
|
13
|
-
|
|
14
|
-
- Typed calls to your custom Brand endpoints using `client.request`
|
|
15
|
-
- Extended Product types that include brand data
|
|
16
|
-
- A working brand page example that ties it all together
|
|
17
|
-
|
|
18
|
-
## How the SDK Works
|
|
19
|
-
|
|
20
|
-
The `@spree/sdk` package provides a typed client for the Store API:
|
|
21
|
-
|
|
22
|
-
```typescript
|
|
23
|
-
import { createClient } from '@spree/sdk'
|
|
24
|
-
|
|
25
|
-
const client = createClient({
|
|
26
|
-
baseUrl: 'https://api.mystore.com',
|
|
27
|
-
publishableKey: 'pk_YOUR_KEY',
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
// Built-in resources
|
|
31
|
-
const products = await client.products.list()
|
|
32
|
-
const product = await client.products.get('prod_86Rf07xd4z')
|
|
33
|
-
const cart = await client.carts.create()
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Under the hood, `createClient()` creates a request function that handles auth headers (`x-spree-api-key`), retries with exponential backoff, and URL building. All requests go through the base path `/api/v3/store`, so `client.products.list()` calls `GET /api/v3/store/products`.
|
|
37
|
-
|
|
38
|
-
## Calling Custom Endpoints
|
|
39
|
-
|
|
40
|
-
The client exposes a `request` method — the same function that powers all built-in resources. Use it to call any Store API endpoint, including custom ones:
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
import { createClient } from '@spree/sdk'
|
|
44
|
-
import type { PaginatedResponse } from '@spree/sdk'
|
|
45
|
-
|
|
46
|
-
const client = createClient({
|
|
47
|
-
baseUrl: 'https://api.mystore.com',
|
|
48
|
-
publishableKey: 'pk_YOUR_KEY',
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// Define your Brand type
|
|
52
|
-
interface Brand {
|
|
53
|
-
id: string
|
|
54
|
-
name: string
|
|
55
|
-
slug: string | null
|
|
56
|
-
description: string | null
|
|
57
|
-
logo_url: string | null
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Call custom endpoints — paths are relative to /api/v3/store
|
|
61
|
-
const brands = await client.request<PaginatedResponse<Brand>>('GET', '/brands')
|
|
62
|
-
const nike = await client.request<Brand>('GET', '/brands/nike')
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
`client.request` has the same auth headers, retry logic, and locale/currency defaults as all built-in resources. The type parameter (`<Brand>`, `<PaginatedResponse<Brand>>`) gives you full type safety on the response.
|
|
66
|
-
|
|
67
|
-
## Step 1: Define Brand Types
|
|
68
|
-
|
|
69
|
-
Create a types file for your custom Brand resource:
|
|
70
|
-
|
|
71
|
-
```typescript types/brand.ts
|
|
72
|
-
import type { Product, PaginatedResponse } from '@spree/sdk'
|
|
73
|
-
|
|
74
|
-
export interface Brand {
|
|
75
|
-
id: string
|
|
76
|
-
name: string
|
|
77
|
-
slug: string | null
|
|
78
|
-
description: string | null
|
|
79
|
-
logo_url: string | null
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface ProductWithBrand extends Product {
|
|
83
|
-
brand_id: string | null
|
|
84
|
-
brand?: Brand
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
## Step 2: Work with Extended Product Responses
|
|
89
|
-
|
|
90
|
-
The Product serializer now includes `brand_id` and an expandable `brand` association.
|
|
91
|
-
|
|
92
|
-
### Fetching Products with Brand Data
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
import type { ProductWithBrand } from './types/brand'
|
|
96
|
-
|
|
97
|
-
// Without expand — brand_id is included, brand object is not
|
|
98
|
-
const product = await client.products.get('prod_86Rf07xd4z') as ProductWithBrand
|
|
99
|
-
console.log(product.brand_id) // "brand_k5nR8xLq"
|
|
100
|
-
console.log(product.brand) // undefined
|
|
101
|
-
|
|
102
|
-
// With expand — full brand object included
|
|
103
|
-
const productWithBrand = await client.products.get(
|
|
104
|
-
'prod_86Rf07xd4z',
|
|
105
|
-
{ expand: ['brand'] }
|
|
106
|
-
) as ProductWithBrand
|
|
107
|
-
console.log(productWithBrand.brand?.name) // "Nike"
|
|
108
|
-
|
|
109
|
-
// Multiple expands
|
|
110
|
-
const full = await client.products.get(
|
|
111
|
-
'prod_86Rf07xd4z',
|
|
112
|
-
{ expand: ['brand', 'variants', 'categories'] }
|
|
113
|
-
) as ProductWithBrand
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Filtering Products by Brand
|
|
117
|
-
|
|
118
|
-
Ransack predicates work on whitelisted attributes and associations. The [API tutorial](api.md) shows how to register `brand_id` and `brand` via `Spree.ransack` — once that's done, you can filter:
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
// Products from a specific brand
|
|
122
|
-
const nikeProducts = await client.products.list({
|
|
123
|
-
brand_id_eq: 'brand_k5nR8xLq',
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
// Products matching brand name (requires Spree.ransack.add_association)
|
|
127
|
-
const nikeProducts2 = await client.products.list({
|
|
128
|
-
brand_name_cont: 'nike',
|
|
129
|
-
})
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
> **INFO:** Ransack predicates like `_eq`, `_cont`, `_gt`, `_lt` work on whitelisted attributes and associations. See [Search & Filtering](../core-concepts/search-filtering.md) for the full list.
|
|
133
|
-
|
|
134
|
-
## Complete Example: Brand Page
|
|
135
|
-
|
|
136
|
-
A real-world example combining everything — fetch a brand by slug and list its products:
|
|
137
|
-
|
|
138
|
-
```typescript
|
|
139
|
-
import { createClient } from '@spree/sdk'
|
|
140
|
-
import type { PaginatedResponse } from '@spree/sdk'
|
|
141
|
-
import type { Brand, ProductWithBrand } from './types/brand'
|
|
142
|
-
|
|
143
|
-
const client = createClient({
|
|
144
|
-
baseUrl: 'https://api.mystore.com',
|
|
145
|
-
publishableKey: 'pk_YOUR_KEY',
|
|
146
|
-
})
|
|
147
|
-
|
|
148
|
-
// Fetch brand by slug
|
|
149
|
-
const brand = await client.request<Brand>('GET', '/brands/nike')
|
|
150
|
-
|
|
151
|
-
// Fetch products for this brand
|
|
152
|
-
const products = await client.products.list({
|
|
153
|
-
brand_id_eq: brand.id,
|
|
154
|
-
sort: '-available_on',
|
|
155
|
-
}) as PaginatedResponse<ProductWithBrand>
|
|
156
|
-
|
|
157
|
-
// Render
|
|
158
|
-
console.log(`${brand.name} — ${brand.description}`)
|
|
159
|
-
console.log(`${products.meta.count} products`)
|
|
160
|
-
products.data.forEach(p => {
|
|
161
|
-
console.log(` ${p.name} — ${p.price.display}`)
|
|
162
|
-
})
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
## Related Documentation
|
|
166
|
-
|
|
167
|
-
- [API Tutorial](api.md) - Creating the Brand API endpoints
|
|
168
|
-
- [SDK Overview](/sdk) - Full SDK documentation
|
|
169
|
-
- [Search & Filtering](../core-concepts/search-filtering.md) - Ransack predicates and sorting
|
|
170
|
-
- [Agentic Development](../agentic/overview.md) - Agent skills and the docs MCP server cover the SDK patterns used here
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 2.0 to 2.1
|
|
3
|
-
description: 'This guide covers upgrading a 2.0 Spree store, to a 2.1 store.'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
This guide has been written from the perspective of a blank Spree 2.0 store with no extensions.
|
|
9
|
-
|
|
10
|
-
If you have extensions that your store depends on, you will need to manually verify that each of those extensions works within your 2.1 store once this upgrade is complete. Typically, extensions that are compatible with this version of Spree will have a 2-1-stable branch.
|
|
11
|
-
|
|
12
|
-
This is the first Spree release that supports Rails 4 exclusively. Spree releases after this point will continue to support Rails 4 only.
|
|
13
|
-
|
|
14
|
-
## Upgrade Rails
|
|
15
|
-
|
|
16
|
-
For this Spree release, you will need to upgrade your Rails version to at least 4.0.0.
|
|
17
|
-
|
|
18
|
-
It is recommended to read through the [Upgrading Ruby on Rails guide](http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-%20from-rails-3-2-to-rails-4-0) to learn what needs to be done for your application to migrate to Rails 4.
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
gem 'rails', '~> 4.0'
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
## Upgrade Spree
|
|
25
|
-
|
|
26
|
-
For best results, use the 2-1-stable branch from GitHub:
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
gem 'spree', '~> 2.1'
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
Run `bundle update spree`.
|
|
33
|
-
|
|
34
|
-
## Copy and run migrations
|
|
35
|
-
|
|
36
|
-
Copy over the migrations from Spree (and any other engine) and run them using
|
|
37
|
-
these commands:
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
rake railties:install:migrations
|
|
41
|
-
rake db:migrate
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Read the release notes
|
|
45
|
-
|
|
46
|
-
For information about changes contained with this release, please read the [2.1.0 Release Notes](https://github.com/spree/spree/releases/tag/v2.1.0).
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: 2.1 to 2.2
|
|
3
|
-
section: upgrades
|
|
4
|
-
description: 'This guide covers upgrading a 2.1 Spree store, to a 2.2 store.'
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Overview
|
|
8
|
-
|
|
9
|
-
This guide has been written from the perspective of a blank Spree 2.1 store with no extensions.
|
|
10
|
-
|
|
11
|
-
If you have extensions that your store depends on, you will need to manually verify that each of those extensions works within your 2.2 store once this upgrade is complete. Typically, extensions that are compatible with this version of Spree will have a 2-2-stable branch.
|
|
12
|
-
|
|
13
|
-
## Upgrade Rails
|
|
14
|
-
|
|
15
|
-
For this Spree release, you will need to upgrade your Rails version to at least 4.0.6.
|
|
16
|
-
|
|
17
|
-
```ruby
|
|
18
|
-
gem 'rails', '~> 4.0.6'
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Upgrade Spree
|
|
22
|
-
|
|
23
|
-
For best results, use the 2-2-stable branch from GitHub:
|
|
24
|
-
|
|
25
|
-
```ruby
|
|
26
|
-
gem 'spree', '~> 2.2'
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Run `bundle update spree`.
|
|
30
|
-
|
|
31
|
-
## Copy and run migrations
|
|
32
|
-
|
|
33
|
-
Copy over the migrations from Spree (and any other engine) and run them using
|
|
34
|
-
these commands:
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
rake railties:install:migrations
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
rake db:migrate
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Read the release notes
|
|
45
|
-
|
|
46
|
-
For information about changes contained with this release, please read the [2.2.0 Release Notes](http://guides.spreecommerce.org/release_notes/spree_2_2_0.html).
|
|
47
|
-
|
|
48
|
-
### Rename assets
|
|
49
|
-
|
|
50
|
-
As mentioned in the release notes, asset paths have changed. Change the references on the left, to the ones on the right:
|
|
51
|
-
|
|
52
|
-
* `admin/spree_backend` => `spree/backend`
|
|
53
|
-
* `store/spree_frontend` => `spree/frontend`
|
|
54
|
-
|
|
55
|
-
This applies across the board on Spree, and may need to be done in your store's extensions.
|
|
56
|
-
|
|
57
|
-
### Paperclip settings have been removed from master
|
|
58
|
-
|
|
59
|
-
Please consult [this section](http://guides.spreecommerce.org/release_notes/spree_2_2_0.html#paperclip-settings-have-been-removed) of the release notes if you were using custom Paperclip settings. This will direct you what to do in that particular case.
|