Back to Spree

Decisions

docs/plans/decisions.md

5.4.27.0 KB
Original Source

2026-03-17: Rename StockItem → StockLevel

Spree::StockItemSpree::StockLevel, spree_stock_itemsspree_stock_levels. Prefix ID: si_sl_.

Every other platform uses "level" for this concept — Shopify (InventoryLevel), Medusa (InventoryLevel), Vendure (StockLevel), Saleor (Stock). "Item" sounds like a physical object; "level" correctly describes "the quantity of a variant at a location."

Part of the 6.0 model rename wave. Includes renaming the FK columns (stock_item_idstock_level_id) on StockMovement, StockReservation, and any other referencing tables.

2026-03-16: Rename user_id → customer_id on customer-facing models

As part of the User → Customer rename (6.0-platform-auth.md), rename user_id foreign key columns to customer_id on all models where the FK references a storefront customer (not an admin user).

Rename to customer_id (11 models — FK references Spree.customer_class):

  • spree_orders.user_idcustomer_id
  • spree_addresses.user_idcustomer_id
  • spree_credit_cards.user_idcustomer_id
  • spree_store_credits.user_idcustomer_id
  • spree_wishlists.user_idcustomer_id
  • spree_gift_cards.user_idcustomer_id
  • spree_gateway_customers.user_idcustomer_id
  • spree_payment_sources.user_idcustomer_id
  • spree_newsletter_subscribers.user_idcustomer_id
  • spree_promotion_rule_users.user_idcustomer_id
  • spree_customer_group_users.user_idcustomer_id

Keep as user_id (5 models — FK references Spree.admin_user_class or is polymorphic):

  • spree_imports.user_id — admin who ran the import
  • spree_exports.user_id — admin who ran the export
  • spree_reports.user_id — admin who generated the report
  • spree_state_changes.user_id — admin who triggered the change
  • spree_user_identities.user_id — polymorphic (Customer or AdminUser)

Single migration renames all 11 columns. Model associations updated: belongs_to :userbelongs_to :customer with class_name: Spree.customer_class.

2026-03-16: PaymentMethod and DeliveryMethod become SingleStoreResource

Both PaymentMethod and DeliveryMethod (renamed from ShippingMethod) switch from multi-store join tables (StorePaymentMethod, StoreShippingMethod) to SingleStoreResource with direct belongs_to :store.

In practice, different stores have different currencies, zones, and provider accounts — sharing the same payment/delivery config across stores is rare. If a merchant wants the same config on two stores, they create two records.

Changes:

  • Add store_id column to spree_payment_methods and spree_delivery_methods
  • Data migration: for each join record, set store_id; duplicate methods linked to multiple stores
  • Drop spree_store_payment_methods and spree_store_shipping_methods join tables
  • Both models include Spree::SingleStoreResource concern

2026-03-16: Fix promotion rule/action STI namespacing

Rename Spree::Promotion::Rules::*Spree::PromotionRules::* and Spree::Promotion::Actions::*Spree::PromotionActions::*.

The convention for STI subtypes is Spree::{BaseClass}s::{Subtype} — pluralized base class as the namespace. Every other hierarchy follows this already:

  • Spree::PriceRules::VolumeRule
  • Spree::Metafields::ShortText
  • Spree::CollectionRules::Tag (from categories plan)
  • Spree::ReimbursementType::Credit

Promotion was the only one nesting under the parent model (Spree::Promotion::Rules) instead of the base class (Spree::PromotionRules).

Changes:

  • Move files from app/models/spree/promotion/rules/app/models/spree/promotion_rules/
  • Move files from app/models/spree/promotion/actions/app/models/spree/promotion_actions/
  • Data migration: update type column in spree_promotion_rules and spree_promotion_actions (e.g., Spree::Promotion::Rules::ProductSpree::PromotionRules::Product)
  • Deprecation aliases for one release

2026-03-16: Normalize state → status across all models

Settle on status as the standard column name for state machines. Newer models (Product, PriceList, PaymentSession, Import, Invitation) already use status.

Order.state and Adjustment.state are removed entirely by other 6.0 plans (cart-order-split, split-adjustments). Five remaining models need a column rename from statestatus in 6.0:

  • Paymentstatestatus
  • Shipmentstatestatus
  • InventoryUnitstatestatus
  • ReturnAuthorizationstatestatus
  • GiftCardstatestatus

Single migration renaming all five columns. State machine declarations updated to state_machine :status, initial: .... Deprecation aliases (alias_attribute :state, :status) for one release.

Models already correct (no change): Product, PriceList, PaymentSession, PaymentSetupSession, Import, ImportRow, Invitation, ReturnItem (reception_status/acceptance_status), Reimbursement (reimbursement_status).

2026-03-28: Simplify metafield visibility — display_on → storefront_visible boolean (6.0)

Replace three-way display_on (both/front_end/back_end) with storefront_visible boolean (default: true) on CustomFieldDefinition. front_end-only was already excluded from MetafieldDefinition::DISPLAY and never made sense.

This makes the two-system boundary razor-sharp:

  • Custom Fields (storefront_visible: true) = public structured data
  • Custom Fields (storefront_visible: false) = admin-only structured data
  • Metadata = private developer-owned data (never exposed)

Matches Vendure (public: boolean) and Saleor (visibleInStorefront: boolean). Ships with the 6.0 model rename wave. See 5.4-6.0-custom-fields-rename.md.

2026-03-16: Consolidate metadata — drop public_metadata, keep metadata JSON column

Drop public_metadata column (never exposed in Store API, unused). Rename private_metadatametadata in the database. Simplify the Spree::Metadata concern to a single metadata JSON column with no alias indirection.

Metadata (JSON column) is a permanent, first-class system — the schemaless developer escape hatch for integration IDs, sync state, ad-hoc flags. No definition required, one-step API: PATCH /product { metadata: { erp_id: "123" } }. Never exposed in Store API (Stripe convention: write-only). Metadata is here to stay.

Metafields (→ Custom Fields in 6.0, see 5.4-6.0-custom-fields-rename.md) stay as merchant-defined structured data — typed values (short_text, number, boolean, json, rich_text, long_text), require a MetafieldDefinition, have storefront_visible boolean, searchable, CSV importable. With the ProductType plan (6.0-product-types.md), metafields become schema-enforced custom attributes driven by ProductType.

Two systems, two purposes, no overlap. No consolidation into one. Metadata for machines, metafields/custom fields for humans.

2026-03-10: Product descriptions stay as plain column

Considered Action Text. Rejected for API-first performance — serializing rich text adds overhead for every product response. Also in the new Admin UI we will use TipTap for rich text editing.