@spree/docs 0.1.169 → 0.1.170
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.
|
@@ -279,6 +279,40 @@ is reported through `Rails.error` so it's visible rather than mysterious.
|
|
|
279
279
|
| `products.update` | `after_update` | lifecycle | After the product is saved (in transaction) |
|
|
280
280
|
| `products.destroy` | `validate` | validate | Before the product is soft-deleted — refuse a deletion your store shouldn't allow |
|
|
281
281
|
| `products.destroy` | `after_destroy` | lifecycle | After the soft-delete, for host cleanup (in transaction) |
|
|
282
|
+
| `products.activate` | `validate` | validate | Before the product goes on sale — the place to require an image, a price or a category |
|
|
283
|
+
| `products.activate` | `after_activate` | lifecycle | After the status is written (in transaction) |
|
|
284
|
+
| `products.archive` | `validate` | validate | Before the product is taken off sale |
|
|
285
|
+
| `products.archive` | `after_archive` | lifecycle | After the status is written (in transaction) |
|
|
286
|
+
| `products.draft` | `validate` | validate | Before the product returns to draft |
|
|
287
|
+
| `products.draft` | `after_draft` | lifecycle | After the status is written (in transaction) |
|
|
288
|
+
| `gift_cards.apply` | `validate` | validate | Before a card is drawn against — who may spend a card, on what, up to how much |
|
|
289
|
+
| `gift_cards.apply` | `after_apply` | lifecycle | After the store credit and payment exist (in transaction) |
|
|
290
|
+
| `gift_cards.remove` | `validate` | validate | Before a card is taken back off an order |
|
|
291
|
+
| `gift_cards.remove` | `after_remove` | lifecycle | After the balance is returned to the card (in transaction) |
|
|
292
|
+
| `gift_cards.redeem` | `validate` | validate | Before the card is recorded as spent |
|
|
293
|
+
| `gift_cards.redeem` | `after_redeem` | lifecycle | After the status is written (in transaction) |
|
|
294
|
+
| `gift_cards.cancel` | `validate` | validate | Before a card is voided |
|
|
295
|
+
| `gift_cards.cancel` | `after_cancel` | lifecycle | After the card is voided (in transaction) |
|
|
296
|
+
| `price_lists.create` | `validate` | validate | After the list is built, before it is saved |
|
|
297
|
+
| `price_lists.create` | `after_create` | lifecycle | After the list and its product membership exist (in transaction) |
|
|
298
|
+
| `price_lists.update` | `validate` | validate | With the pending attributes assigned, before anything is written |
|
|
299
|
+
| `price_lists.update` | `after_update` | lifecycle | After membership and price overrides are applied (in transaction) |
|
|
300
|
+
| `price_lists.activate` | `validate` | validate | Before a price list takes effect |
|
|
301
|
+
| `price_lists.activate` | `after_activate` | lifecycle | After the list goes live or is scheduled (in transaction) |
|
|
302
|
+
| `price_lists.deactivate` | `validate` | validate | Before a price list stops applying |
|
|
303
|
+
| `price_lists.deactivate` | `after_deactivate` | lifecycle | After the list is switched off (in transaction) |
|
|
304
|
+
| `invitations.accept` | `validate` | validate | After the expiry and invitee checks pass, before any access is granted |
|
|
305
|
+
| `invitations.accept` | `after_accept` | lifecycle | After the role is granted and the invitation marked accepted (in transaction) |
|
|
306
|
+
| `imports.start_mapping` | `validate` | validate | Before the uploaded file is read |
|
|
307
|
+
| `imports.start_mapping` | `after_start_mapping` | lifecycle | After the column mappings are built (in transaction) |
|
|
308
|
+
| `imports.complete_mapping` | `validate` | validate | Before the mapping is accepted — refuse a mapping your store considers incomplete |
|
|
309
|
+
| `imports.complete_mapping` | `after_complete_mapping` | lifecycle | After the mapping is accepted, before row creation is dispatched (in transaction) |
|
|
310
|
+
| `imports.start_processing` | `validate` | validate | Before the import starts working through its rows |
|
|
311
|
+
| `imports.start_processing` | `after_start_processing` | lifecycle | After the status is written (in transaction) |
|
|
312
|
+
| `imports.complete` | `validate` | validate | Before the import is closed out |
|
|
313
|
+
| `imports.complete` | `after_complete` | lifecycle | After the import is completed and the store touched (in transaction) |
|
|
314
|
+
| `imports.retry_failed_rows` | `validate` | validate | Before failed rows are queued again |
|
|
315
|
+
| `imports.retry_failed_rows` | `after_retry` | lifecycle | After the import returns to processing, before re-dispatch (in transaction) |
|
|
282
316
|
|
|
283
317
|
`before_cancel` and `before_resume` accept `reject!` like a `validate` hook.
|
|
284
318
|
|
|
@@ -396,11 +430,66 @@ Two rules worth internalising:
|
|
|
396
430
|
connection open across a network round trip, and a timeout leaves your database
|
|
397
431
|
and the payment processor disagreeing about what happened.
|
|
398
432
|
|
|
399
|
-
**
|
|
433
|
+
**Models get a plain `status` string, not a state machine.** Transitions are
|
|
400
434
|
workflows: `MyStore::Subscriptions::Cancel.call(...)`, not `subscription.cancel!`.
|
|
401
435
|
Transition callbacks hide side effects inside a save, cannot take arguments, and
|
|
402
436
|
have no compensation story.
|
|
403
437
|
|
|
438
|
+
As of 6.0 this is not just advice for new models — Spree has no state machines
|
|
439
|
+
left. Every status a record can hold is declared with `Spree::HasStatus`, and
|
|
440
|
+
every move between two of them is a workflow you can hook.
|
|
441
|
+
|
|
442
|
+
## Statuses
|
|
443
|
+
|
|
444
|
+
`Spree::HasStatus` declares the statuses a model can hold:
|
|
445
|
+
|
|
446
|
+
```ruby
|
|
447
|
+
class MyStore::Subscription < Spree.base_class
|
|
448
|
+
include Spree::HasStatus
|
|
449
|
+
has_status :trialing, :active, :paused, :canceled, default: :trialing
|
|
450
|
+
end
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
That gives you an inclusion validation, a predicate per value
|
|
454
|
+
(`subscription.paused?`), a scope per value (`Subscription.paused`) and
|
|
455
|
+
`with_status(:active, :trialing)` for several at once. It deliberately does
|
|
456
|
+
*not* give you a transition graph — deciding which moves are legal is the
|
|
457
|
+
workflow's job, which is what lets a transition take arguments, call out to a
|
|
458
|
+
gateway outside a transaction, and undo itself when a later step fails.
|
|
459
|
+
|
|
460
|
+
Statuses are additive, so an extension can add its own without reopening the
|
|
461
|
+
model:
|
|
462
|
+
|
|
463
|
+
```ruby
|
|
464
|
+
Spree::GiftCard.add_status(:on_hold, after: :active)
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
A custom status needs a custom workflow to move records into it. That is the
|
|
468
|
+
design, not a gap: a central place validating transitions would be a state
|
|
469
|
+
machine again.
|
|
470
|
+
|
|
471
|
+
> **NOTE:** `has_status` never overwrites something the model already defines. Where a
|
|
472
|
+
> status name means more than the column value — `Spree::GiftCard#active?` also
|
|
473
|
+
> requires the card not to have expired, and `Spree::GiftCard.active` includes
|
|
474
|
+
> partially redeemed cards — the model's own definition wins and the generated
|
|
475
|
+
> one is skipped.
|
|
476
|
+
|
|
477
|
+
### Moving a record between statuses
|
|
478
|
+
|
|
479
|
+
Call the workflow, not the model:
|
|
480
|
+
|
|
481
|
+
```ruby
|
|
482
|
+
result = Spree.product_archive_workflow.call(product: product)
|
|
483
|
+
result.success?
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Spree ships one workflow per transition — `Spree::Products::Activate`,
|
|
487
|
+
`Spree::GiftCards::Redeem`, `Spree::Imports::Complete`, and so on — each with
|
|
488
|
+
its own `validate` and `after_*` hooks, all in
|
|
489
|
+
[Available hooks](#available-hooks) above. Because the write and the event it
|
|
490
|
+
publishes happen in the same place, registering against a hook is enough to
|
|
491
|
+
see every transition, wherever it was triggered from.
|
|
492
|
+
|
|
404
493
|
## Observability
|
|
405
494
|
|
|
406
495
|
Every step emits an `ActiveSupport::Notifications` event, so your APM sees the
|