@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,523 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Decorators
|
|
3
|
+
description: Decorators allow you to add or modify behavior of Spree classes in your application.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
> **WARNING:** **Decorators should be a last resort.** They tightly couple your code to Spree internals and can break during upgrades. Before using decorators, consider these modern alternatives that are safer and easier to maintain:
|
|
7
|
+
>
|
|
8
|
+
> - **[Events & Subscribers](/developer/core-concepts/events)** - For reacting to model changes (after save, create, update, delete)
|
|
9
|
+
> - **[Webhooks](/developer/core-concepts/webhooks)** - For notifying external services when events occur
|
|
10
|
+
> - **[Dependencies](/developer/customization/dependencies)** - For swapping out services, serializers, and abilities
|
|
11
|
+
> - **[Admin Navigation](/developer/admin/navigation)** - For adding menu items without controller decorators
|
|
12
|
+
> - **[Admin Partials](/developer/admin/extending-ui)** - For extending admin UI without view decorators
|
|
13
|
+
> - **[Admin Tables](/developer/admin/tables)** - For customizing admin list views
|
|
14
|
+
> - **[Ransack Configuration](/developer/core-concepts/search-filtering#extending-ransackable-configuration)** - For adding searchable/sortable fields without model decorators
|
|
15
|
+
|
|
16
|
+
## When to Use Decorators vs Modern Alternatives
|
|
17
|
+
|
|
18
|
+
Before reaching for a decorator, check if your use case is better served by a modern alternative:
|
|
19
|
+
|
|
20
|
+
| Use Case | Instead of Decorator | Use This |
|
|
21
|
+
|----------|---------------------|----------|
|
|
22
|
+
| After-save hooks (sync to external service) | Model decorator with `after_save` | [Events subscriber](/developer/core-concepts/events) |
|
|
23
|
+
| Notify external service on changes | Model decorator with callbacks | [Webhooks](/developer/core-concepts/webhooks) |
|
|
24
|
+
| Custom add-to-cart logic | Service decorator | [Dependencies injection](/developer/customization/dependencies) |
|
|
25
|
+
| Custom API responses | Serializer decorator | [Dependencies injection](/developer/customization/dependencies) |
|
|
26
|
+
| Add admin menu item | Controller decorator | [Admin Navigation API](/developer/admin/navigation) |
|
|
27
|
+
| Add section to admin form | View decorator/override | [Admin Partials injection](/developer/admin/extending-ui) |
|
|
28
|
+
| Add searchable/filterable field | Model decorator with `ransackable_attributes` | [Ransack configuration](/developer/core-concepts/search-filtering#extending-ransackable-configuration) |
|
|
29
|
+
| Add association to core model | - | Decorator (still appropriate) |
|
|
30
|
+
| Add validation to core model | - | Decorator (still appropriate) |
|
|
31
|
+
| Add new method to core model | - | Decorator (still appropriate) |
|
|
32
|
+
|
|
33
|
+
> **INFO:** Decorators are still appropriate for **structural changes** like adding associations, validations, scopes, and new methods to models. Use modern alternatives for **behavioral changes** like callbacks, hooks, and side effects.
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
|
|
37
|
+
All of Spree's models, controllers, helpers, etc can easily be extended or overridden to meet your exact requirements using standard Ruby idioms.
|
|
38
|
+
|
|
39
|
+
Standard practice for including such changes in your application or extension is to create a file within the relevant **app/models/spree** or **app/controllers/spree** directory with the original class name with **_decorator** appended.
|
|
40
|
+
|
|
41
|
+
## Why Use Decorators?
|
|
42
|
+
|
|
43
|
+
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:
|
|
44
|
+
|
|
45
|
+
1. **Upgrades** - Your changes would be lost when updating Spree
|
|
46
|
+
2. **Maintainability** - It's hard to track what you've customized
|
|
47
|
+
3. **Conflicts** - Direct modifications can conflict with Spree's code
|
|
48
|
+
|
|
49
|
+
Instead, we use **decorators** - a Ruby pattern that lets you add or modify behavior of existing classes without changing their original source code.
|
|
50
|
+
|
|
51
|
+
## How Decorators Work
|
|
52
|
+
|
|
53
|
+
In Ruby, classes are "open" - you can add methods to them at any time. Decorators leverage this by:
|
|
54
|
+
|
|
55
|
+
1. Creating a module with your new methods
|
|
56
|
+
2. Using `Module#prepend` to inject your module into the class's inheritance chain
|
|
57
|
+
3. Your methods run first, and can call `super` to invoke the original method
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# This is the basic pattern
|
|
61
|
+
module Spree
|
|
62
|
+
module ProductDecorator
|
|
63
|
+
# Add a new method
|
|
64
|
+
def my_new_method
|
|
65
|
+
"Hello from decorator!"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Override an existing method
|
|
69
|
+
def existing_method
|
|
70
|
+
# Do something before
|
|
71
|
+
result = super # Call the original method
|
|
72
|
+
# Do something after
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
Product.prepend(ProductDecorator)
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
## Generating Decorators
|
|
84
|
+
|
|
85
|
+
Spree provides generators to create decorator files with the correct structure:
|
|
86
|
+
|
|
87
|
+
### Model Decorator Generator
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
bin/rails g spree:model_decorator Spree::Product
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This creates `app/models/spree/product_decorator.rb`:
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
module Spree
|
|
97
|
+
module ProductDecorator
|
|
98
|
+
def self.prepended(base)
|
|
99
|
+
# Class-level configurations go here
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
Product.prepend(ProductDecorator)
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Controller Decorator Generator
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
bin/rails g spree:controller_decorator Spree::Admin::ProductsController
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This creates `app/controllers/spree/admin/products_controller_decorator.rb`:
|
|
114
|
+
|
|
115
|
+
```ruby
|
|
116
|
+
module Spree
|
|
117
|
+
module Admin
|
|
118
|
+
module ProductsControllerDecorator
|
|
119
|
+
def self.prepended(base)
|
|
120
|
+
# Class-level configurations go here
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
ProductsController.prepend(ProductsControllerDecorator)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Decorating Models
|
|
130
|
+
|
|
131
|
+
### Changing Behavior of Existing Methods
|
|
132
|
+
|
|
133
|
+
The most common use case is changing the behavior of existing methods. When overriding a method, you can call `super` to invoke the original implementation:
|
|
134
|
+
|
|
135
|
+
```ruby app/models/spree/product_decorator.rb
|
|
136
|
+
module Spree
|
|
137
|
+
module ProductDecorator
|
|
138
|
+
def available?
|
|
139
|
+
# Add custom logic before
|
|
140
|
+
return false if discontinued?
|
|
141
|
+
|
|
142
|
+
# Call the original method
|
|
143
|
+
super
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
Product.prepend(ProductDecorator)
|
|
148
|
+
end
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
> **WARNING:** Always consider whether you need to call `super` when overriding methods. Omitting it completely replaces the original behavior, which may break functionality.
|
|
152
|
+
|
|
153
|
+
### Adding New Methods
|
|
154
|
+
|
|
155
|
+
Add new instance methods directly in the decorator module:
|
|
156
|
+
|
|
157
|
+
```ruby app/models/spree/product_decorator.rb
|
|
158
|
+
module Spree
|
|
159
|
+
module ProductDecorator
|
|
160
|
+
def featured?
|
|
161
|
+
metadata[:featured] == true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def days_until_available
|
|
165
|
+
return 0 if available_on.nil? || available_on <= Time.current
|
|
166
|
+
(available_on.to_date - Date.current).to_i
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
Product.prepend(ProductDecorator)
|
|
171
|
+
end
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Adding Associations
|
|
175
|
+
|
|
176
|
+
Use the `self.prepended(base)` callback to add associations:
|
|
177
|
+
|
|
178
|
+
```ruby app/models/spree/product_decorator.rb
|
|
179
|
+
module Spree
|
|
180
|
+
module ProductDecorator
|
|
181
|
+
def self.prepended(base)
|
|
182
|
+
base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
|
|
183
|
+
base.has_many :videos, class_name: 'Spree::Video', dependent: :destroy
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
Product.prepend(ProductDecorator)
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Adding Validations
|
|
192
|
+
|
|
193
|
+
```ruby app/models/spree/product_decorator.rb
|
|
194
|
+
module Spree
|
|
195
|
+
module ProductDecorator
|
|
196
|
+
def self.prepended(base)
|
|
197
|
+
base.validates :external_id, presence: true, uniqueness: true
|
|
198
|
+
base.validates :weight, numericality: { greater_than: 0 }, allow_nil: true
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
Product.prepend(ProductDecorator)
|
|
203
|
+
end
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Adding Scopes
|
|
207
|
+
|
|
208
|
+
```ruby app/models/spree/product_decorator.rb
|
|
209
|
+
module Spree
|
|
210
|
+
module ProductDecorator
|
|
211
|
+
def self.prepended(base)
|
|
212
|
+
base.scope :featured, -> { where("metadata->>'featured' = ?", 'true') }
|
|
213
|
+
base.scope :recently_added, -> { where('created_at > ?', 30.days.ago) }
|
|
214
|
+
base.scope :on_sale, -> { joins(:variants).where('spree_prices.compare_at_amount > spree_prices.amount') }
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
Product.prepend(ProductDecorator)
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Adding Class Methods
|
|
223
|
+
|
|
224
|
+
Use `extend` within the `prepended` callback to add class methods:
|
|
225
|
+
|
|
226
|
+
```ruby app/models/spree/product_decorator.rb
|
|
227
|
+
module Spree
|
|
228
|
+
module ProductDecorator
|
|
229
|
+
def self.prepended(base)
|
|
230
|
+
base.extend ClassMethods
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
module ClassMethods
|
|
234
|
+
def search_by_name(query)
|
|
235
|
+
where('LOWER(name) LIKE ?', "%#{query.downcase}%")
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
Product.prepend(ProductDecorator)
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Usage:
|
|
245
|
+
|
|
246
|
+
```ruby
|
|
247
|
+
Spree::Product.search_by_name('shirt')
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Decorating Controllers
|
|
251
|
+
|
|
252
|
+
> **WARNING:** **Consider creating a new controller instead of decorating.** Creating your own controller that inherits from Spree's base controllers is more maintainable and less likely to break during upgrades. Use controller decorators only when you must modify existing Spree actions.
|
|
253
|
+
|
|
254
|
+
### Recommended: Create a New Controller
|
|
255
|
+
|
|
256
|
+
Instead of decorating `Spree::ProductsController` to add a new action, create your own controller:
|
|
257
|
+
|
|
258
|
+
```ruby app/controllers/spree/product_quick_views_controller.rb
|
|
259
|
+
module Spree
|
|
260
|
+
class ProductQuickViewsController < StoreController
|
|
261
|
+
def show
|
|
262
|
+
@product = current_store.products.friendly.find(params[:product_id])
|
|
263
|
+
render partial: 'spree/products/quick_view', locals: { product: @product }
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
```ruby config/routes.rb
|
|
270
|
+
Spree::Core::Engine.add_routes do
|
|
271
|
+
get 'products/:product_id/quick_view', to: 'product_quick_views#show', as: :product_quick_view
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This approach:
|
|
276
|
+
- Won't break when Spree updates `ProductsController`
|
|
277
|
+
- Is easier to test in isolation
|
|
278
|
+
- Makes your customizations clearly visible in your codebase
|
|
279
|
+
|
|
280
|
+
### Adding a New Action via Decorator
|
|
281
|
+
|
|
282
|
+
If you must add an action to an existing controller:
|
|
283
|
+
|
|
284
|
+
```ruby app/controllers/spree/products_controller_decorator.rb
|
|
285
|
+
module Spree
|
|
286
|
+
module ProductsControllerDecorator
|
|
287
|
+
def self.prepended(base)
|
|
288
|
+
base.before_action :load_product, only: [:quick_view]
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def quick_view
|
|
292
|
+
respond_to do |format|
|
|
293
|
+
format.html { render partial: 'quick_view', locals: { product: @product } }
|
|
294
|
+
format.json { render json: @product }
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
private
|
|
299
|
+
|
|
300
|
+
def load_product
|
|
301
|
+
@product = current_store.products.friendly.find(params[:id])
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
ProductsController.prepend(ProductsControllerDecorator)
|
|
306
|
+
end
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
Don't forget to add the route:
|
|
310
|
+
|
|
311
|
+
```ruby config/routes.rb
|
|
312
|
+
Spree::Core::Engine.add_routes do
|
|
313
|
+
get 'products/:id/quick_view', to: 'products#quick_view', as: :product_quick_view
|
|
314
|
+
end
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Modifying Existing Actions
|
|
318
|
+
|
|
319
|
+
> **WARNING:** **High risk of breaking during upgrades.** When you override an existing Spree action, your code depends on Spree's internal implementation details. If Spree changes the action's behavior, instance variables, or method signatures in a future version, your decorator may silently break or cause unexpected bugs. Use [Events](/developer/core-concepts/events) for post-action side effects instead.
|
|
320
|
+
|
|
321
|
+
```ruby app/controllers/spree/admin/products_controller_decorator.rb
|
|
322
|
+
module Spree
|
|
323
|
+
module Admin
|
|
324
|
+
module ProductsControllerDecorator
|
|
325
|
+
def create
|
|
326
|
+
# Add custom logic before
|
|
327
|
+
log_product_creation_attempt
|
|
328
|
+
|
|
329
|
+
# Call original method
|
|
330
|
+
super
|
|
331
|
+
|
|
332
|
+
# Add custom logic after
|
|
333
|
+
notify_team_of_new_product if @product.persisted?
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
private
|
|
337
|
+
|
|
338
|
+
def log_product_creation_attempt
|
|
339
|
+
Rails.logger.info "Product creation attempted by #{current_spree_user.email}"
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def notify_team_of_new_product
|
|
343
|
+
ProductNotificationJob.perform_later(@product)
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
ProductsController.prepend(ProductsControllerDecorator)
|
|
348
|
+
end
|
|
349
|
+
end
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
> **INFO:** **Better alternative:** For post-action side effects like notifications, use [Events subscribers](/developer/core-concepts/events) instead. Subscribe to `product.created` to be notified when products are created, without coupling to controller internals.
|
|
353
|
+
|
|
354
|
+
### Adding Before Actions
|
|
355
|
+
|
|
356
|
+
```ruby app/controllers/spree/checkout_controller_decorator.rb
|
|
357
|
+
module Spree
|
|
358
|
+
module CheckoutControllerDecorator
|
|
359
|
+
def self.prepended(base)
|
|
360
|
+
base.before_action :check_minimum_order, only: [:update]
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
private
|
|
364
|
+
|
|
365
|
+
def check_minimum_order
|
|
366
|
+
if @order.total < 25.0 && params[:state] == 'payment'
|
|
367
|
+
flash[:error] = 'Minimum order amount is $25'
|
|
368
|
+
redirect_to checkout_state_path(@order.state)
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
CheckoutController.prepend(CheckoutControllerDecorator)
|
|
374
|
+
end
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
## Best Practices
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
- **Use the prepended callback** — Always use `self.prepended(base)` for class-level additions like associations, validations, scopes, and callbacks.
|
|
381
|
+
|
|
382
|
+
- **Keep decorators focused** — Each decorator should have a single responsibility. Create multiple decorators for different concerns if needed.
|
|
383
|
+
|
|
384
|
+
- **Call super when overriding** — When overriding methods, call `super` to preserve original behavior unless you intentionally want to replace it entirely.
|
|
385
|
+
|
|
386
|
+
- **Test decorated behavior** — Write tests specifically for your decorated functionality to catch regressions during upgrades.
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
### Organizing Multiple Decorators
|
|
390
|
+
|
|
391
|
+
If you have many customizations for a single class, consider splitting them into focused decorators:
|
|
392
|
+
|
|
393
|
+
```
|
|
394
|
+
app/models/spree/
|
|
395
|
+
├── product_decorator.rb # Main decorator (loads others)
|
|
396
|
+
├── product/
|
|
397
|
+
│ ├── brand_decorator.rb # Brand association
|
|
398
|
+
│ ├── inventory_decorator.rb # Inventory customizations
|
|
399
|
+
│ └── seo_decorator.rb # SEO-related methods
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
```ruby app/models/spree/product_decorator.rb
|
|
403
|
+
# Load focused decorators
|
|
404
|
+
require_dependency 'spree/product/brand_decorator'
|
|
405
|
+
require_dependency 'spree/product/inventory_decorator'
|
|
406
|
+
require_dependency 'spree/product/seo_decorator'
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
## Common Pitfalls
|
|
410
|
+
|
|
411
|
+
### Forgetting to Call Super
|
|
412
|
+
|
|
413
|
+
```ruby
|
|
414
|
+
# ❌ Bad - completely replaces original behavior
|
|
415
|
+
def available?
|
|
416
|
+
in_stock? && active?
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# ✅ Good - extends original behavior
|
|
420
|
+
def available?
|
|
421
|
+
super && custom_availability_check
|
|
422
|
+
end
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Using Instance Variables in prepended
|
|
426
|
+
|
|
427
|
+
```ruby
|
|
428
|
+
# ❌ Bad - instance variables don't work in prepended
|
|
429
|
+
def self.prepended(base)
|
|
430
|
+
@custom_setting = true # This won't work as expected
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
# ✅ Good - use class attributes or methods
|
|
434
|
+
def self.prepended(base)
|
|
435
|
+
base.class_attribute :custom_setting, default: true
|
|
436
|
+
end
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### Circular Dependencies
|
|
440
|
+
|
|
441
|
+
Be careful when decorators depend on each other:
|
|
442
|
+
|
|
443
|
+
```ruby
|
|
444
|
+
# ❌ Bad - can cause loading issues
|
|
445
|
+
# product_decorator.rb
|
|
446
|
+
def self.prepended(base)
|
|
447
|
+
base.has_many :variants # Variant decorator might not be loaded yet
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
# ✅ Good - use strings for class names
|
|
451
|
+
def self.prepended(base)
|
|
452
|
+
base.has_many :variants, class_name: 'Spree::Variant'
|
|
453
|
+
end
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
## Migrating from Decorators to Modern Patterns
|
|
457
|
+
|
|
458
|
+
If you have existing decorators that use callbacks for side effects, consider migrating them to Events subscribers for better maintainability.
|
|
459
|
+
|
|
460
|
+
### Example: Migrating an After-Save Callback
|
|
461
|
+
|
|
462
|
+
**Before (Decorator with callback):**
|
|
463
|
+
|
|
464
|
+
```ruby app/models/spree/product_decorator.rb
|
|
465
|
+
module Spree
|
|
466
|
+
module ProductDecorator
|
|
467
|
+
def self.prepended(base)
|
|
468
|
+
base.after_save :sync_to_external_service
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
private
|
|
472
|
+
|
|
473
|
+
def sync_to_external_service
|
|
474
|
+
ExternalSyncJob.perform_later(self) if saved_change_to_name?
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
Product.prepend(ProductDecorator)
|
|
479
|
+
end
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**After (Events subscriber):**
|
|
483
|
+
|
|
484
|
+
```ruby app/subscribers/my_app/product_sync_subscriber.rb
|
|
485
|
+
module MyApp
|
|
486
|
+
class ProductSyncSubscriber < Spree::Subscriber
|
|
487
|
+
subscribes_to 'product.updated'
|
|
488
|
+
|
|
489
|
+
def handle(event)
|
|
490
|
+
product = Spree::Product.find_by(id: event.payload['id'])
|
|
491
|
+
return unless product
|
|
492
|
+
|
|
493
|
+
# The payload includes changes, check if name changed
|
|
494
|
+
if event.payload['previous_changes']&.key?('name')
|
|
495
|
+
ExternalSyncJob.perform_later(product)
|
|
496
|
+
end
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### Benefits of Migration
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
- **Loose coupling** — Your code doesn't depend on Spree internals. Events provide a stable interface.
|
|
506
|
+
|
|
507
|
+
- **Easier upgrades** — Events-based code is less likely to break when Spree is updated.
|
|
508
|
+
|
|
509
|
+
- **Better testability** — Subscribers can be tested in isolation without loading the full model.
|
|
510
|
+
|
|
511
|
+
- **Async by default** — Subscribers run via ActiveJob, keeping your requests fast.
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
## Related Documentation
|
|
515
|
+
|
|
516
|
+
- [Events](/developer/core-concepts/events) - Learn about Spree's event system
|
|
517
|
+
- [Webhooks](/developer/core-concepts/webhooks) - HTTP callbacks for external integrations
|
|
518
|
+
- [Dependencies](/developer/customization/dependencies) - Swap core services with your own
|
|
519
|
+
- [Admin Navigation](/developer/admin/navigation) - Extend admin menu without decorators
|
|
520
|
+
- [Admin Partials](/developer/admin/extending-ui) - Extend admin UI without view decorators
|
|
521
|
+
- [Extending Core Models Tutorial](/developer/tutorial/extending-models) - Step-by-step guide to connecting custom models with Spree core
|
|
522
|
+
- [Customization Overview](/developer/customization/quickstart) - General customization patterns
|
|
523
|
+
- [Logic Customization](/developer/customization/logic) - Customizing business logic
|