@spree/docs 0.1.240 → 0.1.241

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.
@@ -8,10 +8,9 @@ description: Use Spree decorators to add or modify behavior on core models, cont
8
8
  > - **[Events & Subscribers](../core-concepts/events.md)** - For reacting to model changes (after save, create, update, delete)
9
9
  > - **[Webhooks](../core-concepts/webhooks.md)** - For notifying external services when events occur
10
10
  > - **[Dependencies](dependencies.md)** - For swapping out services, serializers, and abilities
11
- > - **[Admin Navigation](../dashboard/customization/navigation.md)** - For adding menu items without controller decorators
12
- > - **[Admin Partials](../dashboard/customization/quickstart.md)** - For extending admin UI without view decorators
13
- > - **[Admin Tables](../dashboard/customization/tables.md)** - For customizing admin list views
14
- > - **[Ransack Configuration](../core-concepts/search-filtering.md#extending-ransackable-configuration)** - For adding searchable/sortable fields without model decorators
11
+ > - **[Dashboard Navigation](../dashboard/customization/navigation.md)** - For adding menu items
12
+ > - **[Dashboard Slots](../dashboard/customization/slots.md)** - For adding your own UI to an existing dashboard screen
13
+ > - **[Dashboard Tables](../dashboard/customization/tables.md)** - For customizing list views
15
14
 
16
15
  ## When to Use Decorators vs Modern Alternatives
17
16
 
@@ -23,20 +22,28 @@ Before reaching for a decorator, check if your use case is better served by a mo
23
22
  | Notify external service on changes | Model decorator with callbacks | [Webhooks](../core-concepts/webhooks.md) |
24
23
  | Custom add-to-cart logic | Service decorator | [Dependencies injection](dependencies.md) |
25
24
  | Custom API responses | Serializer decorator | [Dependencies injection](dependencies.md) |
26
- | Add admin menu item | Controller decorator | [Admin Navigation API](../dashboard/customization/navigation.md) |
27
- | Add section to admin form | View decorator/override | [Admin Partials injection](../dashboard/customization/quickstart.md) |
28
- | Add searchable/filterable field | Model decorator with `ransackable_attributes` | [Ransack configuration](../core-concepts/search-filtering.md#extending-ransackable-configuration) |
25
+ | Add a dashboard menu item | Controller decorator | [Navigation registry](../dashboard/customization/navigation.md) |
26
+ | Add a section to a dashboard form | View decorator/override | [Slots](../dashboard/customization/slots.md) |
27
+ | Narrow what an endpoint returns | Overriding an action | Controller decorator overriding `scope` |
28
+ | Accept a new attribute on write | Controller decorator | `Model.additional_permitted_attributes +=` |
29
29
  | Add association to core model | - | Decorator (still appropriate) |
30
30
  | Add validation to core model | - | Decorator (still appropriate) |
31
31
  | Add new method to core model | - | Decorator (still appropriate) |
32
+ | Make a new column [filterable](../core-concepts/search-filtering.md) | - | Decorator (still appropriate) |
32
33
 
33
34
  > **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
 
35
36
  ## Overview
36
37
 
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
+ Spree's models, API controllers and helpers can be extended or overridden to meet your requirements using standard Ruby idioms.
38
39
 
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
+ The convention is a file under `server/app/models/spree` or `server/app/controllers/spree`, named after the original class with `_decorator` appended. The generators below place it for you.
41
+
42
+ > **NOTE:** There is nothing else to decorate. The dashboard is a React application that
43
+ > talks to the Admin API, and the storefront is your own — so the only
44
+ > controllers Spree ships are the API ones, and the only views are the emails.
45
+ > Customize the dashboard through
46
+ > [its own extension points](../dashboard/customization/quickstart.md).
40
47
 
41
48
  ## Why Use Decorators?
42
49
 
@@ -88,7 +95,7 @@ Spree provides generators to create decorator files with the correct structure:
88
95
 
89
96
 
90
97
  ```bash Spree CLI (Docker)
91
- spree generate spree:model_decorator Spree::Product
98
+ spree generate model_decorator Spree::Product
92
99
  ```
93
100
 
94
101
  ```bash Without Spree CLI
@@ -96,7 +103,7 @@ bin/rails g spree:model_decorator Spree::Product
96
103
  ```
97
104
 
98
105
 
99
- This creates `app/models/spree/product_decorator.rb`:
106
+ This creates `server/app/models/spree/product_decorator.rb`:
100
107
 
101
108
  ```ruby
102
109
  module Spree
@@ -114,18 +121,18 @@ end
114
121
 
115
122
 
116
123
  ```bash Spree CLI (Docker)
117
- spree generate spree:controller_decorator Spree::Admin::ProductsController
124
+ spree generate controller_decorator Spree::Api::V3::Admin::ProductsController
118
125
  ```
119
126
 
120
127
  ```bash Without Spree CLI
121
- bin/rails g spree:controller_decorator Spree::Admin::ProductsController
128
+ bin/rails g spree:controller_decorator Spree::Api::V3::Admin::ProductsController
122
129
  ```
123
130
 
124
131
 
125
- This creates `app/controllers/spree/admin/products_controller_decorator.rb`:
132
+ This creates `server/app/controllers/spree/api/v3/admin/products_controller_decorator.rb`:
126
133
 
127
134
  ```ruby
128
- module Spree::Admin
135
+ module Spree::Api::V3::Admin
129
136
  module ProductsControllerDecorator
130
137
  def self.prepended(base)
131
138
  # base.before_action :my_filter
@@ -135,7 +142,7 @@ module Spree::Admin
135
142
  end
136
143
  end
137
144
 
138
- Spree::Admin::ProductsController.prepend Spree::Admin::ProductsControllerDecorator
145
+ Spree::Api::V3::Admin::ProductsController.prepend Spree::Api::V3::Admin::ProductsControllerDecorator
139
146
  ```
140
147
 
141
148
  The generator accepts any namespace depth. `Spree::ProductsController` produces a top-level `module Spree` wrapper; `Spree::Api::V3::Store::ProductsController` produces a `module Spree::Api::V3::Store` wrapper. The final `.prepend` line is always fully qualified.
@@ -146,7 +153,7 @@ The generator accepts any namespace depth. `Spree::ProductsController` produces
146
153
 
147
154
  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:
148
155
 
149
- ```ruby app/models/spree/product_decorator.rb
156
+ ```ruby server/app/models/spree/product_decorator.rb
150
157
  module Spree
151
158
  module ProductDecorator
152
159
  def available?
@@ -168,7 +175,7 @@ end
168
175
 
169
176
  Add new instance methods directly in the decorator module:
170
177
 
171
- ```ruby app/models/spree/product_decorator.rb
178
+ ```ruby server/app/models/spree/product_decorator.rb
172
179
  module Spree
173
180
  module ProductDecorator
174
181
  def featured?
@@ -189,7 +196,7 @@ end
189
196
 
190
197
  Use the `self.prepended(base)` callback to add associations:
191
198
 
192
- ```ruby app/models/spree/product_decorator.rb
199
+ ```ruby server/app/models/spree/product_decorator.rb
193
200
  module Spree
194
201
  module ProductDecorator
195
202
  def self.prepended(base)
@@ -204,7 +211,7 @@ end
204
211
 
205
212
  ### Adding Validations
206
213
 
207
- ```ruby app/models/spree/product_decorator.rb
214
+ ```ruby server/app/models/spree/product_decorator.rb
208
215
  module Spree
209
216
  module ProductDecorator
210
217
  def self.prepended(base)
@@ -219,7 +226,7 @@ end
219
226
 
220
227
  ### Adding Scopes
221
228
 
222
- ```ruby app/models/spree/product_decorator.rb
229
+ ```ruby server/app/models/spree/product_decorator.rb
223
230
  module Spree
224
231
  module ProductDecorator
225
232
  def self.prepended(base)
@@ -237,7 +244,7 @@ end
237
244
 
238
245
  Use `extend` within the `prepended` callback to add class methods:
239
246
 
240
- ```ruby app/models/spree/product_decorator.rb
247
+ ```ruby server/app/models/spree/product_decorator.rb
241
248
  module Spree
242
249
  module ProductDecorator
243
250
  def self.prepended(base)
@@ -263,131 +270,103 @@ Spree::Product.search_by_name('shirt')
263
270
 
264
271
  ## Decorating Controllers
265
272
 
266
- > **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.
273
+ The only controllers Spree ships are the API ones, under
274
+ `Spree::Api::V3::Store` and `Spree::Api::V3::Admin`. There is no server-rendered
275
+ storefront or admin to decorate — the dashboard is a React app that talks to the
276
+ Admin API, and the storefront is yours.
267
277
 
268
- ### Recommended: Create a New Controller
278
+ > **WARNING:** **Reach for a decorator last.** A `ResourceController` subclass exposes named
279
+ > hooks — `scope`, `permitted_params`, `serializer_class`, `collection_includes`
280
+ > — and overriding one of those in a decorator is safe. Overriding an *action*
281
+ > couples your code to Spree's internals and is what breaks on upgrade.
269
282
 
270
- Instead of decorating `Spree::ProductsController` to add a new action, create your own controller:
283
+ ### Narrowing what an endpoint returns
271
284
 
272
- ```ruby app/controllers/spree/product_quick_views_controller.rb
273
- module Spree
274
- class ProductQuickViewsController < StoreController
275
- def show
276
- @product = current_store.products.friendly.find(params[:product_id])
277
- render partial: 'spree/products/quick_view', locals: { product: @product }
278
- end
279
- end
280
- end
281
- ```
285
+ The most common reason to decorate: restrict a listing beyond what the store
286
+ scope already does.
282
287
 
283
- ```ruby config/routes.rb
284
- Spree::Core::Engine.add_routes do
285
- get 'products/:product_id/quick_view', to: 'product_quick_views#show', as: :product_quick_view
286
- end
288
+ ```bash
289
+ spree generate controller_decorator Spree::Api::V3::Admin::ProductsController
287
290
  ```
288
291
 
289
- This approach:
290
- - Won't break when Spree updates `ProductsController`
291
- - Is easier to test in isolation
292
- - Makes your customizations clearly visible in your codebase
293
-
294
- ### Adding a New Action via Decorator
295
-
296
- If you must add an action to an existing controller:
297
-
298
- ```ruby app/controllers/spree/products_controller_decorator.rb
299
- module Spree
292
+ ```ruby server/app/controllers/spree/api/v3/admin/products_controller_decorator.rb
293
+ module Spree::Api::V3::Admin
300
294
  module ProductsControllerDecorator
301
- def self.prepended(base)
302
- base.before_action :load_product, only: [:quick_view]
303
- end
304
-
305
- def quick_view
306
- respond_to do |format|
307
- format.html { render partial: 'quick_view', locals: { product: @product } }
308
- format.json { render json: @product }
309
- end
310
- end
295
+ protected
311
296
 
312
- private
297
+ # `super` is already scoped to the current store. Chain onto it — never
298
+ # replace it, or the endpoint stops being scoped and starts returning
299
+ # another store's records. `current_user` is nil for a secret-key
300
+ # request, hence `&.` — a caller that is not a staff member gets the
301
+ # narrowed scope.
302
+ def scope
303
+ return super if current_user&.spree_admin?
313
304
 
314
- def load_product
315
- @product = current_store.products.friendly.find(params[:id])
305
+ super.where(discontinue_on: nil)
316
306
  end
317
307
  end
318
-
319
- ProductsController.prepend(ProductsControllerDecorator)
320
308
  end
321
- ```
322
-
323
- Don't forget to add the route:
324
309
 
325
- ```ruby config/routes.rb
326
- Spree::Core::Engine.add_routes do
327
- get 'products/:id/quick_view', to: 'products#quick_view', as: :product_quick_view
328
- end
310
+ Spree::Api::V3::Admin::ProductsController.prepend(
311
+ Spree::Api::V3::Admin::ProductsControllerDecorator
312
+ )
329
313
  ```
330
314
 
331
- ### Modifying Existing Actions
315
+ `current_user` is the signed-in staff member, and `spree_admin?` asks whether
316
+ they hold the admin role for the current store.
332
317
 
333
- > **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](../core-concepts/events.md) for post-action side effects instead.
318
+ > **WARNING:** `current_user` is only set when the request carries a staff token. A request
319
+ > authenticated with a [secret API key](../../api-reference/admin-api/authentication.md)
320
+ > has no user, so `current_user` is `nil` — guard for it, or a server-to-server
321
+ > integration hits `NoMethodError` on every call.
334
322
 
335
- ```ruby app/controllers/spree/admin/products_controller_decorator.rb
336
- module Spree
337
- module Admin
338
- module ProductsControllerDecorator
339
- def create
340
- # Add custom logic before
341
- log_product_creation_attempt
342
-
343
- # Call original method
344
- super
323
+ ### Accepting an extra attribute
345
324
 
346
- # Add custom logic after
347
- notify_team_of_new_product if @product.persisted?
348
- end
325
+ If your decorator added a column to a core model, the controller has to permit
326
+ it — but you rarely need a decorator for that. Extensions append to the model's
327
+ own list from an initializer:
349
328
 
350
- private
329
+ ```ruby server/config/initializers/spree.rb
330
+ Spree::Product.additional_permitted_attributes += [:brand_id]
331
+ ```
351
332
 
352
- def log_product_creation_attempt
353
- Rails.logger.info "Product creation attempted by #{current_spree_user.email}"
354
- end
333
+ Always `+=`, never `=`, or you drop what another extension added.
355
334
 
356
- def notify_team_of_new_product
357
- ProductNotificationJob.perform_later(@product)
358
- end
359
- end
335
+ ### A new endpoint is a new controller
360
336
 
361
- ProductsController.prepend(ProductsControllerDecorator)
362
- end
363
- end
364
- ```
337
+ To add an action, write your own controller rather than decorating one of
338
+ Spree's. It inherits the same pagination, filtering and authorization:
365
339
 
366
- > **INFO:** **Better alternative:** For post-action side effects like notifications, use [Events subscribers](../core-concepts/events.md) instead. Subscribe to `product.created` to be notified when products are created, without coupling to controller internals.
340
+ ```ruby server/app/controllers/spree/api/v3/admin/product_audits_controller.rb
341
+ module Spree::Api::V3::Admin
342
+ class ProductAuditsController < ResourceController
343
+ # Names the API-key scope this endpoint needs: `read_products` for
344
+ # index/show, `write_products` for everything else. Every Admin API
345
+ # controller must declare one.
346
+ scoped_resource :products
367
347
 
368
- ### Adding Before Actions
348
+ protected
369
349
 
370
- ```ruby app/controllers/spree/checkout_controller_decorator.rb
371
- module Spree
372
- module CheckoutControllerDecorator
373
- def self.prepended(base)
374
- base.before_action :check_minimum_order, only: [:update]
350
+ def model_class
351
+ Spree::ProductAudit
375
352
  end
376
353
 
377
- private
378
-
379
- def check_minimum_order
380
- if @order.total < 25.0 && params[:state] == 'payment'
381
- flash[:error] = 'Minimum order amount is $25'
382
- redirect_to checkout_state_path(@order.state)
383
- end
354
+ def serializer_class
355
+ MyApp::ProductAuditSerializer
384
356
  end
385
357
  end
386
-
387
- CheckoutController.prepend(CheckoutControllerDecorator)
388
358
  end
389
359
  ```
390
360
 
361
+ Add the route to your application's `config/routes.rb`, inside the engine's
362
+ route hook. See [extending the API](api.md).
363
+
364
+ > **INFO:** **For side effects, use events rather than a decorator.** Subscribing to
365
+ > `product.created` runs your code for every caller — the dashboard, a secret
366
+ > API key, an import, the console. A controller override only fires for
367
+ > requests that happen to pass through that controller. See
368
+ > [Events](../core-concepts/events.md).
369
+
391
370
  ## Best Practices
392
371
 
393
372
 
@@ -405,7 +384,7 @@ end
405
384
  If you have many customizations for a single class, consider splitting them into focused decorators:
406
385
 
407
386
  ```
408
- app/models/spree/
387
+ server/app/models/spree/
409
388
  ├── product_decorator.rb # Main decorator (loads others)
410
389
  ├── product/
411
390
  │ ├── brand_decorator.rb # Brand association
@@ -413,13 +392,22 @@ app/models/spree/
413
392
  │ └── seo_decorator.rb # SEO-related methods
414
393
  ```
415
394
 
416
- ```ruby app/models/spree/product_decorator.rb
417
- # Load focused decorators
418
- require_dependency 'spree/product/brand_decorator'
419
- require_dependency 'spree/product/inventory_decorator'
420
- require_dependency 'spree/product/seo_decorator'
395
+ ```ruby server/app/models/spree/product_decorator.rb
396
+ module Spree
397
+ module ProductDecorator
398
+ include Product::BrandDecorator
399
+ include Product::InventoryDecorator
400
+ include Product::SeoDecorator
401
+ end
402
+
403
+ Product.prepend(ProductDecorator)
404
+ end
421
405
  ```
422
406
 
407
+ Each focused module is a plain module — only the top-level decorator calls
408
+ `prepend`. Autoloading resolves them from their paths, so nothing needs
409
+ requiring by hand.
410
+
423
411
  ## Common Pitfalls
424
412
 
425
413
  ### Forgetting to Call Super
@@ -475,7 +463,7 @@ If you have existing decorators that use callbacks for side effects, consider mi
475
463
 
476
464
  **Before (Decorator with callback):**
477
465
 
478
- ```ruby app/models/spree/product_decorator.rb
466
+ ```ruby server/app/models/spree/product_decorator.rb
479
467
  module Spree
480
468
  module ProductDecorator
481
469
  def self.prepended(base)
@@ -495,7 +483,7 @@ end
495
483
 
496
484
  **After (Events subscriber):**
497
485
 
498
- ```ruby app/subscribers/product_sync_subscriber.rb
486
+ ```ruby server/app/subscribers/product_sync_subscriber.rb
499
487
  class ProductSyncSubscriber < Spree::Subscriber
500
488
  subscribes_to 'product.updated'
501
489
 
@@ -503,14 +491,16 @@ class ProductSyncSubscriber < Spree::Subscriber
503
491
  product = Spree::Product.find_by_prefix_id(event.payload['id'])
504
492
  return unless product
505
493
 
506
- # The payload includes changes, check if name changed
507
- if event.payload['previous_changes']&.key?('name')
508
- ExternalSyncJob.perform_later(product)
509
- end
494
+ ExternalSyncJob.perform_later(product)
510
495
  end
511
496
  end
512
497
  ```
513
498
 
499
+ The payload is the record as the API serializes it, so it carries the new
500
+ values rather than a list of what changed. When a subscriber only cares about
501
+ one attribute, compare against what you last synced rather than looking for a
502
+ changeset in the event.
503
+
514
504
  ### Benefits of Migration
515
505
 
516
506
 
@@ -528,7 +518,7 @@ end
528
518
  - [Events](../core-concepts/events.md) - Learn about Spree's event system
529
519
  - [Webhooks](../core-concepts/webhooks.md) - HTTP callbacks for external integrations
530
520
  - [Dependencies](dependencies.md) - Swap core services with your own
531
- - [Admin Navigation](../dashboard/customization/navigation.md) - Extend admin menu without decorators
532
- - [Admin Partials](../dashboard/customization/quickstart.md) - Extend admin UI without view decorators
533
- - [Extending Core Models Tutorial](../tutorial/model.md) - Step-by-step guide to connecting custom models with Spree core
521
+ - [Extending the API](api.md) - Add your own endpoints
522
+ - [Dashboard customization](../dashboard/customization/quickstart.md) - Extend the dashboard from its own React app
523
+ - [Extending Core Models Tutorial](../tutorial/model-and-api.md) - Step-by-step guide to connecting custom models with Spree core
534
524
  - [Customization Overview](quickstart.md) - General customization patterns
@@ -6,11 +6,11 @@ title: Metadata
6
6
 
7
7
  Metadata provides simple, unstructured key-value storage on Spree resources — similar to [Stripe's metadata](https://docs.stripe.com/api/metadata). It's ideal for storing integration IDs, tracking data, or any arbitrary information that doesn't need validation or admin UI.
8
8
 
9
- Metadata is a **permanent, first-class system** in Spree. It is designed to coexist alongside [Metafields](../core-concepts/metafields.md) (structured, typed, admin-managed). The two systems serve different purposes and are not interchangeable — think of it as **metadata for machines, metafields for humans**.
9
+ Metadata is a **permanent, first-class system** in Spree. It is designed to coexist alongside [Custom Fields](../core-concepts/metafields.md) (structured, typed, admin-managed). The two systems serve different purposes and are not interchangeable — think of it as **metadata for machines, custom fields for humans**.
10
10
 
11
11
  Metadata is **write-only** in the Store API — you can set it when creating or updating resources, but it is never returned in Store API responses. It is visible in Admin API responses for administrative use.
12
12
 
13
- > **INFO:** For structured, type-safe custom attributes with admin UI support, use [Metafields](../core-concepts/metafields.md) instead.
13
+ > **INFO:** For structured, type-safe custom attributes with admin UI support, use [Custom Fields](../core-concepts/metafields.md) instead.
14
14
 
15
15
  ## Store API
16
16
 
@@ -186,14 +186,14 @@ Metadata updates use **merge semantics** — existing keys are preserved, new ke
186
186
  { "source": "mobile_app", "campaign": "winter", "new_key": "value" }
187
187
  ```
188
188
 
189
- ## Metadata vs Metafields
189
+ ## Metadata vs Custom Fields
190
190
 
191
191
  Spree has two permanent, complementary systems for custom data. They are not interchangeable and neither is going away.
192
192
 
193
- | | Metadata | [Metafields](../core-concepts/metafields.md) |
193
+ | | Metadata | [Custom Fields](../core-concepts/metafields.md) |
194
194
  |---|---|---|
195
195
  | **Purpose** | Developer escape hatch — integration data, sync state, ad-hoc flags | Merchant-defined structured attributes with admin UI |
196
- | **Schema** | Schemaless JSON — no definition required | Defined via MetafieldDefinitions (typed, validated) |
196
+ | **Schema** | Schemaless JSON — no definition required | Defined via CustomFieldDefinitions (typed, validated) |
197
197
  | **Validation** | None — accepts any JSON-serializable data | Type-specific (text, number, boolean, rich text, JSON) |
198
198
  | **Visibility** | Write-only in Store API, readable in Admin API | Configurable (front-end, back-end, both) |
199
199
  | **Admin UI** | JSON preview only | Dedicated management forms |
@@ -208,7 +208,7 @@ Spree has two permanent, complementary systems for custom data. They are not int
208
208
  - Any write-and-forget data that only needs to be read by backend systems
209
209
  - Syncing state with external integrations (webhooks, ETL pipelines)
210
210
 
211
- ### When to use metafields
211
+ ### When to use custom fields
212
212
 
213
213
  - Custom product specifications shown to customers (material, dimensions, certifications)
214
214
  - Admin-managed fields with validation and type safety
@@ -231,7 +231,7 @@ The Store API currently supports writing metadata on:
231
231
 
232
232
  The `public_metadata` column was never exposed in Store API responses and in practice served the same purpose as `private_metadata`. Calling `public_metadata=` emits a deprecation warning.
233
233
 
234
- Always use the single `metadata` accessor for all schemaless key-value storage. If you need data visible to customers on the storefront, use [Metafields](../core-concepts/metafields.md) instead.
234
+ Always use the single `metadata` accessor for all schemaless key-value storage. If you need data visible to customers on the storefront, use [Custom Fields](../core-concepts/metafields.md) instead.
235
235
 
236
236
  ```ruby
237
237
  # Deprecated — use `metadata` instead
@@ -240,6 +240,6 @@ order.public_metadata = { 'gift_message' => 'Happy Birthday!' }
240
240
  # Use metadata for internal storage
241
241
  order.metadata = { 'gift_message' => 'Happy Birthday!' }
242
242
 
243
- # Use metafields for customer-visible structured data
244
- order.set_metafield('custom.gift_message', 'Happy Birthday!')
243
+ # Use custom fields for customer-visible structured data
244
+ order.set_custom_field('custom.gift_message', 'Happy Birthday!')
245
245
  ```
@@ -6,8 +6,8 @@ description: Add typed configuration options to Spree models using model prefere
6
6
  Model preferences allow you to easily extend Spree models with configuration options. Thanks to this you can store useful information on Spree models, eg.
7
7
 
8
8
  ```ruby
9
- user.preferred_language = "English"
10
- user.save
9
+ store.preferred_timezone = 'Europe/Warsaw'
10
+ store.save
11
11
  ```
12
12
 
13
13
  ## Defining Model Preferences
@@ -16,41 +16,26 @@ To define a model preference, you need to add them to your model class.
16
16
 
17
17
  Make sure to generate a migration to add the `preferences` column to the table. This column will store the preferences in a serialized format.
18
18
 
19
-
20
- ```bash Spree CLI (Docker)
21
- spree generate migration AddPreferencesToSpreeUsers preferences:text
22
- ```
23
-
24
- ```bash Without Spree CLI
25
- bin/rails g migration AddPreferencesToSpreeUsers preferences:text
19
+ ```bash
20
+ spree generate migration AddPreferencesToSpreeBrands preferences:text
26
21
  ```
27
22
 
28
-
29
23
  Run the migration.
30
24
 
31
-
32
- ```bash Spree CLI (Docker)
25
+ ```bash
33
26
  spree migrate
34
27
  ```
35
28
 
36
- ```bash Without Spree CLI
37
- bin/rails db:migrate
38
- ```
39
-
40
-
41
- ```ruby
42
- class Spree::User < ApplicationRecord
43
- # ... you existing code ...
44
-
45
- # include the preferable module
46
- include Spree::Preferences::Preferable
47
- # define the preferences
48
- preference :language, :string, default: "English"
49
- preference :sms_marketing, :boolean, default: false
29
+ ```ruby server/app/models/spree/brand.rb
30
+ module Spree
31
+ class Brand < Spree.base_class
32
+ preference :featured, :boolean, default: false
33
+ preference :display_name, :string
34
+ end
50
35
  end
51
36
  ```
52
37
 
53
- This will add a `language` preference to the `User` model. The preference will be stored in the newly created `preferences` column of the `spree_users` table. You can add more preferences to the model in the same way.
38
+ Any model inheriting from `Spree.base_class` can declare preferences — the reader and writer methods come with it, so there is nothing to include. The values live in that model's `preferences` column.
54
39
 
55
40
  ## Accessing Model Preferences
56
41
 
@@ -63,59 +48,38 @@ There are several shortcut methods that are generated. They are shown below.
63
48
  Query methods:
64
49
 
65
50
  ```ruby
66
- user.prefers_sms_marketing? # => false
51
+ brand.prefers_featured? # => false
67
52
  ```
68
53
 
69
54
  Reader methods:
70
55
 
71
56
  ```ruby
72
- user.preferred_sms_marketing # => false
73
- user.preferred_language # => "English"
57
+ brand.preferred_featured # => false
58
+ brand.preferred_display_name # => "English"
74
59
  ```
75
60
 
76
61
  Writer methods:
77
62
 
78
63
  ```ruby
79
- user.prefers_sms_marketing = false # => false
80
- user.preferred_language = "English" # => "English"
64
+ brand.prefers_featured = false # => false
65
+ brand.preferred_display_name = "English" # => "English"
81
66
  ```
82
67
 
83
- > **NOTE:** Remember to run `user.save` after setting the preference value to save the changes to the database.
68
+ > **NOTE:** Remember to run `brand.save` after setting the preference value to save the changes to the database.
84
69
 
85
70
  Check if a preference is available:
86
71
 
87
72
  ```ruby
88
- user.has_preference? :sms_marketing
73
+ brand.has_preference? :featured
89
74
  ```
90
75
 
91
76
  ### Generic Methods
92
77
 
93
- Each shortcut method is essentially a wrapper for the various generic methods shown below:
94
-
95
- Query method:
78
+ When the preference name is only known at runtime, read and write it generically:
96
79
 
97
80
  ```ruby
98
- user.prefers?(:sms_marketing) # => false
99
- user.prefers?(:language) # => false
100
- ```
101
-
102
- Reader methods:
103
-
104
- ```ruby
105
- user.preferred(:sms_marketing) # => false
106
- user.preferred(:language) # => "English"
107
- ```
108
-
109
- ```ruby
110
- user.get_preference :sms_marketing
111
- user.get_preference :language
112
- ```
113
-
114
- Writer method:
115
-
116
- ```ruby
117
- user.set_preference(:sms_marketing, false) # => false
118
- user.set_preference(:language, "English") # => "English"
81
+ brand.get_preference(:featured) # => false
82
+ brand.set_preference(:display_name, 'Wilson')
119
83
  ```
120
84
 
121
85
  ### Accessing All Preferences
@@ -123,21 +87,23 @@ user.set_preference(:language, "English") # => "English"
123
87
  You can get a hash of all stored preferences by accessing the `preferences` helper:
124
88
 
125
89
  ```ruby
126
- user.preferences # => {"language"=>"English", "sms_marketing"=>false}
90
+ brand.preferences # => { 'featured' => false, 'display_name' => 'Wilson' }
127
91
  ```
128
92
 
129
93
  This hash will contain the value for every preference that has been defined for the model instance, whether the value is the default or one that has been previously stored.
130
94
 
131
95
  ## Models with preferences
132
96
 
133
- The following Spree models already have preferences defined:
97
+ Around fifty models use preferences, and the pattern is always the same: a family of subclasses that share a table but need different settings.
98
+
99
+ | Family | What the preferences hold |
100
+ |---|---|
101
+ | `Spree::Calculator` | The numbers behind a delivery charge or a discount — flat rate, percentage, tiers |
102
+ | `Spree::PaymentMethod` | Gateway credentials and per-provider options |
103
+ | `Spree::Promotion::Rules` and `Spree::Promotion::Actions` | What a promotion matches on, and what it does |
104
+ | `Spree::Integration` | Per-service configuration and API keys |
105
+ | `Spree::PriceRules` and `Spree::DeliveryMethodRules` | When a price list or a delivery method applies |
106
+ | `Spree::Store`, `Spree::Channel` and `Spree::Market` | Merchant settings — timezone, units, currency behaviour |
134
107
 
135
- * `Spree::Calculator`
136
- * `Spree::Gateway`
137
- * `Spree::PageBlock`
138
- * `Spree::PageSection`
139
- * `Spree::Page`
140
- * `Spree::PaymentMethod`
141
- * `Spree::PromotionRule`
142
- * `Spree::Store`
108
+ This is why preferences exist rather than columns: `Spree::Calculator::FlatRate` and `Spree::Calculator::TieredPercent` are rows in one table with entirely different settings, and neither wants a column the other leaves null.
143
109
  * `Spree::Theme`