关于 spree-data-model
spree-data-model 是面向 Spree 开发者的模型参考技能,梳理目录、订单、结账、商店、频道和市场之间的关系,并指出影响查询与 API 使用的实现细节。
主要功能
- 01目录到订单的模型关系
- 02商店、频道与市场结构
- 03结账与支付模型概览
- 04API 前缀 ID 约定
- 05状态字段与请求上下文说明
适用场景
- 01追踪商品到购物车和订单的模型关系
- 02按销售频道查询商品
- 03区分进行中的购物车与已完成订单
- 04在代码中使用模型 ID 和请求上下文
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-data-model安装后在 Claude Code 中输入/spree-data-model确认已加载。
详细介绍
Spree 数据模型
最常被问及的 Spree 模型关系图。字段级文档位于已安装的 @spree/docs 包中的 node_modules/@spree/docs/dist/developer/core-concepts/。
商品目录 → 购物车流程
Product → Variant → LineItem → Order
- Product 是品牌级实体(名称、slug、描述、类别)。
- Variant 是可售 SKU。每个 Product 至少有一个 Variant。Variant 包含 SKU、价格、尺寸,并关联库存。
- LineItem 将 Variant 关联到 Order,包含
quantity,并在添加时冻结价格。 - Order 是客户的交易记录——进行中的购物车,以及结账后的已完成交易(同一条记录,不同的
state)。
Variant 通过 StockItem(每个 Variant 在每个 StockLocation 中各有一个)和 StockMovement 历史记录关联到库存。
主变体与默认变体
Product 有一个 master variant(旧版概念,is_master: true),以及一个计算得出的 default_variant 方法:当 Spree::Config[:track_inventory_levels] 开启时,返回第一个可购买的 variant;否则返回按 position 排序的第一个非 master variant;只有当商品没有其他 variant 时,才会使用 master 作为后备。product.default_variant_id 只会返回该计算所得 variant 的 id。在 5.5 中,两者都不是数据库列——不要针对 default_variant_id 进行查询或迁移(计划在 6.0 中为 spree_products 添加 default_variant_id 外键,但尚未开始实现;参见 docs/plans/6.0-remove-master-variant.md)。非 master 的可售变体请使用 product.variants;只有确实需要包含 master 行时,才使用 product.variants_including_master。
多渠道/多商店维度
Store → Channel → ProductPublication → Product
自 Spree 5.5 起提供。
- Store 是顶层品牌(通常一个组织对应一个 Store)。
- Channel 是 Store 内的销售界面:在线商店、线下 POS、市场集成(Amazon、eBay)、B2B 批发、移动应用。每个 Store 至少有一个名为 "Online Store" 的默认 Channel。
- ProductPublication 是关联记录:指定哪些 Product 在哪些 Channel 上可见,并可选用
published_at/unpublished_at时间窗口进行排期。 - Order 有
channel_id,因此可以按渠道归属收入。
Store API 会根据每个请求中的 X-Spree-Channel 标头解析渠道(与 channels.code 或带 ch_… 前缀的 ID 匹配);如果没有该标头,则使用该商店的默认渠道。Admin API 不会读取 X-Spree-Channel——管理端查询会返回当前商店所有渠道的数据。
Markets(区域配置)
Market has_many :countries
Market columns: currency (string), default_locale (string)
Order belongs_to :market
Market 是一种区域配置,包含其国家/地区集合、货币和默认语言区域。通常会在创建 Store 时(已知默认国家/地区的情况下)自动创建默认 Market,但 Market 并非必需——请检查 store.has_markets?;如果没有 Market,货币和语言区域会回退到商店级默认值。Order 会在某个 Market 中创建——它决定客户看到的货币以及适用的税务规则。
完整的 Market 文档请参阅 node_modules/@spree/docs/dist/developer/core-concepts/markets.md。
购物车与 Order
在 Spree 中,Spree::Order 既表示进行中的购物车,也表示已完成的交易。state 列会记录所处阶段:cart、address、delivery、payment、confirm、complete。可按状态筛选以区分:
Spree::Order.where(state: 'cart') # in-progress carts
Spree::Order.where(state: 'complete') # finalized orders
Spree::Order.complete # named scope — NOT equivalent: defined as where.not(completed_at: nil), so it matches any order that ever completed checkout, including ones later canceled or returned
Order#token(has_secure_token :token, length: 35)用于在多个请求间标识匿名购物车。已登录用户的购物车通过 user_id 外键归属用户。
结账相关模型
Order → Payment → PaymentMethod
Order → Shipment → ShippingRate → ShippingMethod
Order → Address (bill_address, ship_address)
- Payment 有自己的状态机(
checkout → processing → pending → completed,以及failed、void和invalid)。对应列为state。 - Shipment 有自己的状态机(
pending → ready → shipped,以及canceled)。对应列为state。 - ShippingRate 是每个 Shipment 提供的选项(例如 UPS Ground $5.99、USPS Priority $8.99)。客户从中选择一项。
客户 / 用户
Spree.user_class (typically Spree::User)
↓
Address (many, via spree_addresses)
CreditCard (many)
GiftCard (many)
StoreCredit (many)
使用 Spree.user_class 和 Spree.admin_user_class 引用用户模型,切勿直接使用 Spree::User。应用可以通过配置替换为自己的用户模型。
调整项(多态)
Adjustable (Order, LineItem, Shipment) ← Adjustment
Adjustment 是多态的:它通过 adjustable_type + adjustable_id 关联到任意订单、行项目或配送。每个 Adjustment 都有一个 source(创建它的对象,例如 TaxRate、PromotionAction、ReturnAuthorization 等),并内置了可按来源类型筛选的作用域:
order.adjustments.tax # source_type: 'Spree::TaxRate'
order.adjustments.promotion # source_type: 'Spree::PromotionAction'
order.adjustments.return_authorization
order.all_adjustments # adjustments on order + its line_items + shipments
带前缀的 ID
通过 v3 API 暴露的每个 Spree 模型都有类似 Stripe 的带前缀 ID:
product.prefixed_id # => "prod_86Rf07xd4z"
order.prefixed_id # => "or_m3Rp9wXz"
variant.prefixed_id # => "variant_k5nR8xLq"
ID 根据整数主键通过 Sqids 计算得出,不对应数据库列。前缀通过模型上的 has_prefix_id :<prefix> 按类声明。v3 API 在所有位置都接受并返回带前缀的 ID;find_by_prefix_id! 会将它们解析回整数主键。
前缀约定:
- 部分资源使用较长形式:
prod(Product)、variant(Variant) - 大多数其他资源使用短代码:
or(Order)、py(Payment,与 Stripe 保持一致)、adj(Adjustment)、li(LineItem)、ctg(Category/Taxon)、cus(客户,与 Stripe 保持一致)、ch(Channel)、mkt(Market)
切勿在 API 响应中暴露原始整数主键。
state 与 status(5.5 中混用)
不同模型使用不同的列名,具体取决于它们的引入时间:
Order.state、Payment.state、Shipment.state— 较早的状态机OrderApproval.status— 较新的状态列Channel不使用状态机,而是使用active布尔值
编写模型代码时,遵循该模型实际拥有的列所采用的约定。查询时,如果不确定,请查看模型的源代码。
Spree::Current(每个请求的上下文)
避免将店铺 / 货币 / 地区设置作为参数传递。使用当前上下文:
Spree::Current.store # The store handling this request
Spree::Current.currency # The currency to display prices in
Spree::Current.locale # The locale for translations
Spree::Current.channel # The resolved sales channel (falls back to the store's default channel)
Spree::Current.market # The resolved market (falls back to the store's default market)
模型、控制器、作业和服务中均可使用。API 上的控制器会通过 before_action 自动设置(Spree::Current 内置了回退到店铺默认值的机制);在需要针对特定店铺的作业和 rake 任务中,则需手动设置。
延伸阅读
- 字段级文档: 每个模型对应的
node_modules/@spree/docs/dist/developer/core-concepts/<topic>.md。 - OpenAPI 规范:
node_modules/@spree/docs/dist/api-reference/store.yaml列出了每个 API 字段及其类型,比根据模型源代码猜测更可靠。 - 添加新模型 / API 资源: 使用
spree-resource技能。 - 扩展现有 Spree 模型(通过装饰器添加关联、验证、作用域、方法):使用
spree-decorators技能。