news 2026/9/14 17:54:31

Spree 5.5→6.0 重构指南:display_on 三态字段收敛为 storefront_visible 布尔值

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spree 5.5→6.0 重构指南:display_on 三态字段收敛为 storefront_visible 布尔值

Spree 5.5→6.0 重构指南:display_on 三态字段收敛为 storefront_visible 布尔值

【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree

在 Spree(面向 B2B、Marketplace 与多租户场景的开源电商平台)中,支付与配送方式长期用一个三态字符串字段display_onboth/front_end/back_end)控制可见性。本文基于仓库内的规划文档 5.5-6.0-display-on-to-boolean.md,完整梳理这次已落地(Status: Implemented, 2026-08-19)的重构:为什么三态收敛为布尔值storefront_visible、5.5 版本的 API 桥接层如何设计、6.0 的数据库迁移如何原子化完成,以及模型、Admin API v3、TypeScript SDK 与 Admin SPA 各层的具体实现证据。读完后你能掌握 Spree "先加别名、后改 schema" 的字段重命名迁移模式,并能在自己的扩展中正确使用新字段。

背景:display_on 三态字段的设计缺陷

在 5.4 时代的 Spree 中,Spree::PaymentMethodSpree::ShippingMethod(6.0 更名为DeliveryMethod)的表结构里都有一个display_on字符串列,取值域为'both' | 'front_end' | 'back_end',并共同 include 一个名为Spree::DisplayOn的 concern,提供:

  • scope :available(仅both
  • scope :available_on_front_endfront_end+both
  • scope :available_on_back_endback_end+both
  • validates :display_on, presence: true, inclusion: { in: DISPLAY.map(&:to_s) }
  • available_on_front_end?实例谓词

这次重构的动机来自一个业务判断:front_end(仅前台可见、后台不可见)这个状态并不对应任何真实工作流——后台运营人员必须能看到每一种支付/配送方式,才能查询历史交易、处理退款、编辑订单。既然front_end是死状态,剩下的bothback_end两个状态就可以无损地坍缩成一个布尔值:

  • back_endstorefront_visible: false
  • both(以及遗留的front_end)→storefront_visible: true

规划文档的关键决策(原文 Key Decisions 章节)包括:

  • 用布尔值而非三态:与 custom-fields 重命名计划(5.4-6.0-custom-fields-rename.md)采用同一套推理。
  • 命名storefront_visible而非available_on_storefront:整个产品对同一概念使用同一个词,与 custom fields 计划保持一致。
  • 默认值为true:大多数支付/配送方式是面向顾客的;back_end只是少数例外(手动支票录入、内部电汇、仅退款用途的方式)。
  • front_end边缘数据一律映射为true,与 custom-fields 计划的数据迁移规则一致。
  • 5.5 的迁移是非破坏性的:不做数据迁移、不删列;读display_on的代码继续工作,读storefront_visible的代码拿到布尔值。

两阶段落地路线

重构分两个版本交付,每个版本的目标不同:

阶段一(5.5):API 桥接 +alias_attributeshim,无 schema 变更

  • 在两个模型上新增storefront_visible读写访问器,代理到既有的display_on列;
  • Admin API v3 的读与写都以storefront_visible为规范字段(写请求同时容忍display_on,保证树外集成在 5.5 周期内继续工作);
  • 遗留 Rails 后台引擎(spree/admin)继续通过表单辅助方法写display_on,该路径到 6.0 才迁移;
  • SDK 只发布storefront_visible;Admin SPA 使用<StorefrontVisibleSwitch>组件;
  • 数据库零改动

规划文档给出的 5.5 桥接实现是扩展Spree::DisplayOnconcern(当时三个调用方PaymentMethodShippingMethodMetafieldDefinition都 include 它,加在 concern 里可让三个模型自动继承):

# spree/core/app/models/concerns/spree/display_on.rb (5.5) module Spree module DisplayOn extend ActiveSupport::Concern DISPLAY = [:both, :front_end, :back_end] included do scope :available, -> { where(display_on: [:both]) } scope :available_on_front_end, -> { where(display_on: [:front_end, :both]) } scope :available_on_back_end, -> { where(display_on: [:back_end, :both]) } # New canonical scopes (5.5 → 6.0). scope :storefront_visible, -> { where.not(display_on: 'back_end') } scope :admin_only, -> { where(display_on: 'back_end') } validates :display_on, presence: true, inclusion: { in: DISPLAY.map(&:to_s) } def available_on_front_end? display_on == 'front_end' || display_on == 'both' end # 5.5 bridge — `storefront_visible` is the canonical wire field. # `back_end` → false; everything else → true. def storefront_visible display_on != 'back_end' end def storefront_visible=(value) self.display_on = ActiveModel::Type::Boolean.new.cast(value) ? 'both' : 'back_end' end end end end

往返语义清晰:读时back_endfalse,其余(both或遗留的front_end)→true;写时truebothfalseback_end,round-trip 无损。Spree::MetafieldDefinition原本按 custom-fields 计划单独定义了同名访问器,concern 提供后这些副本可以删除(该协调通过对应计划的 checklist 完成)。

同期,序列化器侧把线上字段从字符串换成布尔:

# Before typelize display_on: :string attributes :display_on # After (5.5) — wire field is the boolean only typelize storefront_visible: :boolean attributes :storefront_visible

display_on从此不再出现在 API 响应中;DB 列仍保留,只是 API 表面变了。允许参数方面,payment_method_attributesshipping_method_attributes在 5.5 周期同时保留:display_on:storefront_visible,concern 上的访问器把两者收敛到同一底层列。文档明确警告:客户端应提交其一,同时提交两者是未定义行为(取决于 JSON 键顺序),storefront_visible是规范名,display_on只是 6.0 即删的兼容 shim。

阶段二(6.0):schema 变更 + 删除 concern

6.0 把display_on字符串列替换为storefront_visible布尔列(default: true, null: false),删除Spree::DisplayOnconcern(三个调用方至此全部迁完,是干净的一次性清除),并移除display_onAPI 别名。这一阶段属于 6.0 的模型重命名波次,与Shipment → FulfillmentShippingMethod → DeliveryMethodMetafield → CustomField同期推进(配送侧上下文见 6.0-fulfillment-and-delivery.md)。

6.0 迁移实现:数据转换内嵌于 migration

以 PaymentMethod 实际交付的迁移 20260819000001_replace_payment_method_display_on_with_storefront_visible.rb 为例:

class ReplacePaymentMethodDisplayOnWithStorefrontVisible < ActiveRecord::Migration[8.1] # Payment methods are the last host of the tri-state display_on column # (docs/plans/5.5-6.0-display-on-to-boolean.md). Only back_end ever meant # "hide from the storefront"; both and the legacy front_end-only value # collapse to true. def up add_column :spree_payment_methods, :storefront_visible, :boolean, default: true, null: false execute(<<~SQL.squish) UPDATE spree_payment_methods SET storefront_visible = #{connection.quoted_false} WHERE display_on = 'back_end' SQL remove_column :spree_payment_methods, :display_on end def down add_column :spree_payment_methods, :display_on, :string, default: 'both' execute(<<~SQL.squish) UPDATE spree_payment_methods SET display_on = 'back_end' WHERE storefront_visible = #{connection.quoted_false} SQL remove_column :spree_payment_methods, :storefront_visible end end

迁移注释与规划文档给出了一个反直觉但重要的工程决策:数据转换写在 migration 里而不是 rake task。惯例上"数据转换放 rake task"的前提是任务执行时源列还在;而这里的 migration 在同一条语句里删掉了display_on,后续任务将无列可读。又因为除back_end外所有值都保留列默认值true,所以一条窄UPDATE即可,且down方向对称可逆。

三个模型的实际切换时间线(见文档 Status 行):

  • DeliveryMethod:2026-08-05,先切——它的三态DISPLAY_ON_*费率过滤常量改成了符号DeliveryMethod::STOREFRONT/BACKOFFICE,migration 只加列,回填留给spree:migrate_shipping_to_delivery(转换后清空display_on,重跑安全);
  • CustomFieldDefinition:2026-08-09,作为 metafields 重命名的一部分;
  • PaymentMethod:2026-08-19,即上面这条 migration。

当前源码中的模型层实现

PaymentMethod:真实布尔列 + 规范 scopes

spree/core/app/models/spree/payment_method.rb 展示了 6.0 的最终形态:

scope :active, -> { where(active: true).order(position: :asc) } # Every tri-state display_on value passed the old filter, so availability # only ever meant "active". scope :available, -> { active } ... # Customer-facing methods vs backoffice-only ones (manual check entry, # internal wire transfers). The backoffice always sees every method. scope :storefront_visible, -> { where(storefront_visible: true) } scope :admin_only, -> { where(storefront_visible: false) } # Real column, so admin clients filter it directly — no ransacker needed. self.whitelisted_ransackable_attributes = %w[storefront_visible] ... validates :storefront_visible, inclusion: { in: [true, false] }

几个值得注意的实现细节:

  1. storefront_visible是真实列,因此管理端客户端可以直接按列过滤,whitelisted_ransackable_attributes白名单里注册了它,不需要写 ransacker 方法。
  2. availablescope 的语义修正:源码头注释说明,旧三态值都能通过available过滤,所以该 scope 实际只等价于active——布尔化之后这个历史含糊被显式记录。
  3. 校验改为布尔包含式inclusion: { in: [true, false] },配合列的null: false, default: true,杜绝了旧presence+ 枚举字符串校验。

同时模型保留了 6.1 才移除的弃用入口(payment_method.rb#L291-L307):

# @deprecated Use {#storefront_visible?}; removed in 6.1. def available_on_front_end? Spree::Deprecation.warn('Spree::PaymentMethod#available_on_front_end? is deprecated and will be removed in Spree 6.1. Use #storefront_visible? instead.') storefront_visible? end # @deprecated Use {#storefront_visible}; removed in 6.1. def display_on Spree::Deprecation.warn('Spree::PaymentMethod#display_on is deprecated and will be removed in Spree 6.1. Use #storefront_visible instead.') storefront_visible? ? 'both' : 'back_end' end # @deprecated Use {#storefront_visible=}; removed in 6.1. def display_on=(value) Spree::Deprecation.warn('Spree::PaymentMethod#display_on= is deprecated and will be removed in Spree 6.1. Use #storefront_visible= instead.') self.storefront_visible = value.to_s != 'back_end' end

这与规划文档"每个模型保留display_on作为弃用读写入口直到 6.1"的约束一致:读时反向翻译回旧字符串词表,写时把任意非back_end值折成true

DeliveryMethod:受众过滤从整数常量改为符号

spree/core/app/models/spree/delivery_method.rb 中可以看到费率报价的受众常量已完成切换:

# Audience a rate refresh is quoting for: the storefront sees only # customer-facing methods, the backoffice sees every method. STOREFRONT = :storefront BACKOFFICE = :backoffice

旧的整数常量DISPLAY_ON_FRONT_END = 1/DISPLAY_ON_BACK_END = 2保留为弃用别名(delivery_method.rb#L28-L31),而 normalize_audience 负责把旧调用方安全地映射到新词汇表:

def self.normalize_audience(audience) case audience when STOREFRONT, BACKOFFICE then audience when DISPLAY_ON_FRONT_END, DISPLAY_ON_BACK_END Spree::Deprecation.warn("...Use #{STOREFRONT.inspect} / #{BACKOFFICE.inspect} instead.") audience == DISPLAY_ON_BACK_END ? BACKOFFICE : STOREFRONT else raise ArgumentError, "unknown delivery audience #{audience.inspect} ..." end end

注意这里的防御性设计:未知受众直接raise,而不是悄悄按前台口径收窄报价集——"typo must not narrow the offer set unnoticed"。配套的实例方法 available_to? 则体现了规划文档中的核心约束"后台看到一切":

def available_to?(audience) case self.class.normalize_audience(audience) when BACKOFFICE then true else storefront_visible? end end

DeliveryMethod 同样拥有布尔属性与规范 scopes(delivery_method.rb#L67、L91-L92、L118):

attribute :storefront_visible, :boolean, default: true ... scope :storefront_visible, -> { where(storefront_visible: true) } scope :admin_only, -> { where(storefront_visible: false) } ... self.whitelisted_ransackable_attributes = %w[storefront_visible available_to_sellers seller_id]

其弃用壳display_on/display_on=(delivery_method.rb#L453-L463)与available_to_display?的处置方式与 PaymentMethod 完全对称。

API 层:序列化器与允许参数

Admin API v3 的序列化器只发布布尔字段。以 Admin::PaymentMethodSerializer 为例:

typelize active: :boolean, ... storefront_visible: :boolean, ... attributes :metadata, :active, :auto_capture, :capture_method, :resolved_capture_method, :storefront_visible, :position, created_at: :iso8601, updated_at: :iso8601

display_on已彻底不在 wire 上。控制器侧的允许参数只暴露新字段,如 admin/payment_methods_controller.rb#L55 中的:name, :description, :active, :storefront_visible, :auto_capture, :capture_method, :position, ...,配送方式控制器(admin/delivery_methods_controller.rb#L131)同样在:pickup_point_provider, :rate_provider, :storefront_visible, ...中注册了该字段。Store API 侧则从未把display_on作为线上字段暴露过(规划文档 Resolved Questions 一节确认:Store 的配送方式接口只是服务端按它过滤,因此不需要 Store API 桥接层)。

SDK 与 Admin SPA 的配套变更

TypeScript 侧按规划一次性删除旧类型而非渐进弃用(admin SDK 在 5.5 周期仍是nexttag、pre-1.0,树外 TS 消费者极少,"删除优于弃用"):

// Before export type DisplayOnValue = 'both' | 'front_end' | 'back_end' export interface PaymentMethod { display_on: DisplayOnValue } // After (5.5) — boolean only; legacy types removed export interface PaymentMethod { storefront_visible: boolean }

DisplayOnValue等类型被整体移除;create/update 参数形状只暴露storefront_visible?: boolean。服务器写请求仍接受display_on,但 SDK 不再为其建模,需要发送遗留字段的 TS 调用方可自行断言。

Admin SPA 侧提供了两个配套 UI 组件。表单控件是 StorefrontVisibleSwitch——一个带标签的Switch(默认文案 "Visible on storefront",带说明文本),组件 JSDoc 明确标注它是 "Canonical 5.5+ 'Visible on storefront' control",供所有在 API 上暴露storefront_visible的资源使用;接入方式与其他表单项一致:

<StorefrontVisibleSwitch control={form.control} name="storefront_visible" />

表格单元则复用既有的 Active 列视觉语言,按规划文档建议渲染为:

<ActiveBadge active={pm.storefront_visible} activeLabel="Visible" inactiveLabel="Admin only" />

旧的三态<DisplayOnSelect>不存在于 6.0 代码库——它是在引入 Switch 的同一变更中被删除的。

迁移路径与调用方行动清单

对商户 / 托管方(5.5 → 6.0)

  1. 升级到 5.5:无需任何操作。display_on继续可用,storefront_visible开始可用;
  2. 升级到 6.0:运行bin/rails db:migrate。支付方式与自定义字段定义在各自 migration 内原子转换,不存在"一半转换"的中间窗口,也没有额外的 rake 任务;配送方式的转换在spree:migrate_shipping_to_delivery中完成,已包含在 5.6 → 6.0 升级清单里。

对消费 Admin / Store API 的集成方

  1. 5.5 起:改为读布尔字段storefront_visible(两个字段曾短暂并行出现);
  2. 5.5 起:create/update 调用改提交storefront_visible
  3. 6.0 起:display_on从响应与参数形状中消失,删除所有读写它的代码。

对扩展 / 插件开发者

  1. 直接读payment_method.display_on的扩展代码,改为payment_method.storefront_visible(6.0 中旧入口只发弃用告警);
  2. where(display_on: 'back_end')这类查询,改用新 scopeadmin_onlywhere(storefront_visible: false)
  3. 6.0 中列已删除,任何残留的display_on数据库级引用都会直接失败。

约束与边界决策

规划文档对后续开发立下了三条约束(Constraints on Current Work),这些约束决定了 Spree 可见性系统的长期形态:

  • storefront_visible是唯一规范名display_on仅作为树外代码的弃用读写入口存在于各模型上,Spree 内部代码不得再调用它;
  • 新模型不得引入可见性 concern。从源码结构看,PaymentMethodDeliveryMethod各自内联了两行 scope 加校验——"四行代码的三份拷贝从未证明过这层间接抽象的必要性",这正是 6.0 选择删除 concern 而非改写的理由;只有当第四个调用方出现时才重新讨论;
  • 后台永远看全部storefront_visible只过滤顾客端表面,管理端列表不得应用该过滤;旧的available_on_back_endscope(返回back_end+both)没有布尔等价物,被直接丢弃而不是强行翻译——这也解释了为何 PaymentMethod 的availablescope 只剩active语义。

规划文档的 Resolved Questions 一节还澄清了两点:available_on_front_end?谓词以弃用壳形式保留在 PaymentMethod 上(委托storefront_visible?,6.1 移除),而 DeliveryMethod 与 CustomFieldDefinition 从未继承该谓词;Store API 不需要ShippingMethoddisplay_on桥接,因为它从来不是线上字段。可见性系统简化相关的后续决策沉淀在 docs/plans/decisions.md 中。

延伸阅读

  • 本次重构的完整规划与决策记录:docs/plans/5.5-6.0-display-on-to-boolean.md
  • 同一桥接模式的先行者(Metafield → CustomField):docs/plans/5.4-6.0-custom-fields-rename.md
  • ShippingMethod → DeliveryMethod 重命名背景:docs/plans/6.0-fulfillment-and-delivery.md
  • 核心模型实现:PaymentMethod、DeliveryMethod
  • 数据迁移:ReplacePaymentMethodDisplayOnWithStorefrontVisible
  • 序列化器与控制器:Admin::PaymentMethodSerializer、admin payment methods controller
  • Admin SPA 组件:StorefrontVisibleSwitch

【免费下载链接】spreeOpen Source eCommerce Platform for B2B, Marketplace, and Enterprise. REST API, TypeScript SDK, and production-ready Next.js storefront. Self-host it. Own your stack. No vendor lock-in. Zero platform fees.项目地址: https://gitcode.com/GitHub_Trending/sp/spree

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 17:53:56

如何为自研库用 stubtest 验证 .pyi 存根与实现的一致性?

如何为自研库用 stubtest 验证 .pyi 存根与实现的一致性&#xff1f; 【免费下载链接】mypy Optional static typing for Python 项目地址: https://gitcode.com/GitHub_Trending/my/mypy 如果你给自研 Python 库维护了一份 .pyi 存根文件&#xff0c;最常见的风险是&am…

作者头像 李华
网站建设 2026/9/14 17:52:00

MV3插件开发:从脚本到工程化架构实战指南

1. MV3 不是“升级补丁”&#xff0c;而是浏览器插件的工业革命分水岭你可能刚在 Chrome Web Store 看到某个插件突然弹出“此扩展已更新至 Manifest V3”提示&#xff0c;顺手点了确认——但这个看似平静的弹窗背后&#xff0c;是一场持续三年、波及全球数百万插件开发者、彻底…

作者头像 李华
网站建设 2026/9/14 17:51:25

Vue虚拟滚动实战:解决上万条DOM渲染卡顿

在业务里碰到过一次很典型的场景&#xff1a;后台管理系统里的日志列表&#xff0c;一天就能攒下几万条数据&#xff0c;接到页面上直接一次性渲染。页面大概卡了三四秒才出来&#xff0c;滚动的时候帧率掉到个位数&#xff0c;CPU直接拉满&#xff0c;风扇响得跟起飞一样。后来…

作者头像 李华
网站建设 2026/9/14 17:51:09

鱼群算法与响应面法结合的工艺参数优化实践

1. 项目概述&#xff1a;鱼群算法与响应面法的工艺参数优化方案在工业生产与实验研究中&#xff0c;工艺参数优化一直是提升产品质量与生产效率的核心环节。传统试错法不仅耗时费力&#xff0c;而且难以找到全局最优解。本文将介绍一种融合鱼群算法&#xff08;Fish School Sea…

作者头像 李华
网站建设 2026/9/14 17:50:12

鸿蒙TextInput组件键盘弹出控制方案详解

1. 问题现象与场景还原在鸿蒙应用开发中&#xff0c;TextArea和TextInput组件是处理用户文本输入的核心控件。近期不少开发者反馈一个特定场景下的交互问题&#xff1a;当用户点击这两个组件获取光标时&#xff0c;系统键盘会自动弹出&#xff0c;但在某些业务场景下这并不是期…

作者头像 李华
网站建设 2026/9/14 17:49:56

触发器开发:审计字段自动维护——业务表 DDL、ORM 适配与事务边界

文章目录每日一句正能量前言1. 背景与问题2. 环境与数据3. 复现过程3.1 复现审计字段遗漏3.2 批量任务更容易暴露问题4. 方案实施4.1 MySQL&#xff1a;使用会话变量传递操作人4.2 BEFORE INSERT 触发器4.3 BEFORE UPDATE 触发器4.4 JDBC&#xff1a;必须在同一个 Connection 设…

作者头像 李华