关于 spree-extensions
指导开发者构建 Spree 6 扩展的技能,扩展可采用 Rails engine gem,并可选配仪表板插件。内容包括扩展结构、API 资源、注册机制、提供方、测试和常见集成问题。
主要功能
- 01创建 Rails engine 结构的扩展 gem
- 02添加 Store 和 Admin API 资源
- 03注册钩子、提供方和订阅器
- 04构建可选仪表板插件
- 05使用 RSpec 和 Factory Bot 测试扩展
适用场景
- 01将定制功能打包并在多个 Spree 应用间共享
- 02添加店铺范围的资源和 API 端点
- 03集成支付或履约提供方
- 04通过仪表板插件添加 Admin 页面
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-extensions安装后在 Claude Code 中输入/spree-extensions确认已加载。
详细介绍
Spree 扩展
命令使用
spreeCLI 形式(Rails 应用位于server/)。在经典 Rails 应用中,从应用根目录运行bundle …/bin/rails …— 参见spree-project。
Spree 6 扩展分为两部分:
| 部分 | 内容 | 包含 |
|---|---|---|
| Gem(Rails 引擎) | spree_<name> Ruby gem | 模型、迁移、Store/Admin API 端点、序列化器、权限范围、工作流钩子、订阅器、提供程序类、集成 |
| Dashboard 插件(可选) | 使用 defineDashboardPlugin 的 npm 包 | 管理界面、导航项、商品页组件 — 与 gem 的 Admin API 端点通信 |
无需添加视图:Spree 6 没有 Rails 管理后台,也没有 Rails 店面。店面页面位于你的店面应用中,通过 @spree/sdk 实现。
Gem 是合适的形式吗? 只有当定制内容需要在多个应用间共享或发布时才合适。对于单个应用,将相同代码放在 server/app/ 中,并在 server/config/initializers/spree.rb 中注册 — 参见 spree-customization。
安装扩展 gem
spree bundle add spree_reviews # or edit server/Gemfile, then `spree bundle install`
spree generate spree_reviews:install # convention: every extension ships <gem>:install
spree migrate
spree restart # Gemfile changes need a full restart (Ctrl+C `spree dev` if running)
对于使用预构建镜像的新建 create-spree-app 项目,运行一次 spree eject,以便容器从 server/ Gemfile 构建。
安装生成器通常会复制迁移文件(db/migrate/<ts>_<name>.<engine_name>.rb),并可能添加初始化程序。如果扩展包含 Dashboard 插件,还需在 apps/dashboard/ 中运行 pnpm add @scope/plugin — 插件会通过其 package.json 中的 spree.dashboard.plugin 标记自动发现,无需编辑宿主代码。
脚手架项目中的 Gems
spree-starter 服务端 Gemfile(create-spree-app 克隆到 server/ 的文件)包含:
| Gem | 提供功能 |
|---|---|
spree | 元 gem → spree_core + spree_api |
spree_dashboard | 在 /dashboard 提供 React 管理界面 |
spree_emails | 事务性电子邮件 |
spree_stripe | Stripe 支付 + Stripe Connect 付款提供程序 |
spree_easypost | EasyPost 承运商费率、面单、追踪(配送费率 + 履约提供程序) |
spree_meilisearch | Meilisearch 搜索提供程序(SpreeMeilisearch::SearchProvider) |
spree_opentelemetry | OpenTelemetry 跟踪(设置 OTEL_* 环境变量后才会启用) |
spree_i18n | UI 翻译 |
spree_adyen 和 spree_paypal_checkout 已存在,但处于注释状态 — 启用前,请检查各 gem 的 gemspec/CHANGELOG,确认是否有兼容 Spree 6 的版本。Spree 4/5 时代的社区 gem(spree_reviews、spree_avatax_official 等)也同样需要检查:先确认 spree_core 约束;大多数都需要移植(没有 spree_admin、Spree::Adjustment 或状态机)。集成目录:https://spreecommerce.org/docs/integrations。
多店铺共享(在多个店铺之间共享一个商品/促销/支付方式)并非核心功能 — 核心记录只属于一个店铺。请使用 spree_multi_store 扩展。
构建扩展
gem install spree_extension -v '>= 2.0' # 1.x generates a Spree 5 scaffold (spree_admin, importmap) — don't use it
spree-extension create reviews # → ./spree_reviews
cd spree_reviews
2.x 脚手架依赖 spree_core + spree_api(>= 6.0.0.beta),并提供:
lib/spree_reviews/engine.rb— 使用config.to_prepare加载所有app/**/*_decorator*.rb(启动时及每次开发环境重新加载时),并附有注释掉的additional_permitted_attributes +=示例。config/initializers/spree.rb— 扩展的注册文件,与其他初始化程序一样,由宿主在启动时加载,并附有注释掉的register_scope、Spree.hooks.register示例,以及用于订阅器和注册表的after_initialize块示例。config/routes.rb—Spree::Core::Engine.add_routes钩子、lib/spree_reviews/factories.rb,以及用于复制迁移文件的install生成器。
模型
# db/migrate/20260901000000_create_spree_reviews.rb
create_table :spree_reviews do |t|
t.references :store, null: false, index: true, foreign_key: false
t.references :product, null: false, index: true, foreign_key: false
t.integer :rating, null: false
t.text :body
t.boolean :approved, null: false, default: false
t.timestamps
end
# app/models/spree/review.rb
module Spree
class Review < Spree.base_class
include Spree::SingleStoreResource # store filled in; API scopes to current store
has_prefix_id :review # review_k5nR8xLq on the wire
publishes_lifecycle_events # review.created / .updated / .deleted
belongs_to :product, class_name: 'Spree::Product' # required by default ("must exist")
validates :rating, presence: true, inclusion: { in: 1..5 }
scope :approved, -> { where(approved: true) }
self.whitelisted_ransackable_attributes = %w[rating approved]
end
end
约定:使用 Spree.base_class,不设外键约束,关联中使用 class_name,数据按店铺划分作用域,ID 加前缀,使用事件而非回调。
API
Store API 控制器继承 Spree::Api::V3::Store::ResourceController;Admin API 控制器继承 Spree::Api::V3::Admin::ResourceController,并且必须声明 scoped_resource :reviews(权限门控)和 resource_permitted_attributes。序列化器继承 Spree::Api::V3::BaseSerializer(admin 序列化器扩展 store 序列化器)。路由:
# config/routes.rb
Spree::Core::Engine.add_routes do
namespace :api, defaults: { format: 'json' } do
namespace :v3 do
namespace(:store) { resources :reviews, only: [:index, :show] }
namespace(:admin) { resources :reviews }
end
end
end
完整的控制器/序列化器模板:spree-resource(spree:api_resource 生成器会生成这些模板)和 spree-api-v3。为插件拥有的资源添加命名空间路径,以避免与未来的核心资源冲突。
注册 — 在何处以及何时
注册项放在扩展的 config/initializers/spree.rb 中;触及模型的代码放在引擎的 config.to_prepare 中。时机很重要,因为核心会在自己的 config.after_initialize 中使用 = 赋值某些注册表,从而清除之前追加的内容:
| 注册项 | 放在何处 |
|---|---|
Spree.payment_methods <<、Spree.fulfillment_providers <<、Spree.integrations <<、Spree.stock_splitters <<、Spree.tracking_carriers[...] =、Spree.adjusters <<、Spree.promotions.actions <<、Spree.*_authentication_strategies.add | Rails.application.config.after_initialize do … end — 核心会在自己的 after_initialize 中赋值这些注册表,并且会先运行 |
Spree.subscribers、Spree.delivery_rate_providers、tax_providers、payout_providers、digital_asset_providers、pricing_providers、inventory_providers、order_routing.rules/strategies、promotions.rules、Spree.reporting | 在应用初始化器运行前完成预置,核心会拼接内容 — 可在任意时机注册;统一放在 after_initialize 是安全的选择(也可避免在启动过程中自动加载类) |
Spree.hooks.register(...)、Spree.permissions.register_scope(...)、Spree.search_provider = | 初始化器的顶层(基于字符串/注册表,可安全重载) |
Spree::Product.additional_permitted_attributes += [...]、装饰器加载,以及任何触及模型类的内容 | 引擎的 config.to_prepare(在开发环境重新加载时会再次运行) |
# config/initializers/spree.rb (inside the gem)
Spree.permissions.register_scope(:reviews, group: :catalog, resources: -> { [Spree::Review] })
Spree.hooks.register('products.activate.validate', 'SpreeReviews::RequireDescription')
Rails.application.config.after_initialize do
Spree.subscribers << SpreeReviews::ReviewRequestSubscriber # `bin/rails g spree:subscriber` adds these; pass the class — a String crashed production boot through 6.0.0.beta4
Spree.integrations << 'SpreeReviews::Integration' # if the gem needs per-store credentials
end
# lib/spree_reviews/engine.rb
module SpreeReviews
class Engine < Rails::Engine
require 'spree/core'
isolate_namespace Spree
engine_name 'spree_reviews'
config.to_prepare do
Dir.glob(SpreeReviews::Engine.root.join('app/**/*_decorator*.rb')) do |decorator|
Rails.configuration.cache_classes ? require(decorator) : load(decorator)
end
Spree::Product.additional_permitted_attributes += [:reviews_enabled] # += never << (frozen array)
end
end
end
宿主应用也以相同方式在 server/config/initializers/spree.rb 中注册相同内容。对于核心赋值的注册表,仅使用 to_prepare 是不够的 — 启动时它会在 after_initialize 之前运行,因此核心会覆盖它。
各注册项的作用:
register_scope会生成read_reviews/write_reviews,显示在员工角色编辑器中,也可授予密钥 API key。请在config/locales/en.yml的en.spree.permissions_catalog.resources.reviews.{label,description}下为其添加标签。选项:write: false、audiences:、read_only_for:。参见spree-auth-permissions。- 工作流钩子在核心流程中运行,并可中止流程(
workflow.reject!('…'))。启用急切加载时,启动阶段会验证钩子键 — 拼写错误会导致启动失败。参见spree-workflows。 - 订阅器(
Spree::Subscriber、subscribes_to 'order.placed')会在事件发生后作出响应。参见spree-events-webhooks。 - 提供方可接入税费、配送费率、履约、支付、搜索、付款、数字资产和身份验证。所有基类和注册表请参见
spree-providers。 Spree.dependencies可替换核心服务/工作流(*_workflow键)。参见spree-dependencies。优先使用钩子 — 替换的工作流必须与核心保持同步。
最后再考虑使用装饰器(例如向 Spree::Product 添加 has_many :reviews)— 参见 spree-decorators。
Dashboard 插件部分
npx @spree/cli plugin new reviews # or `spree plugin new reviews`
插件会调用 defineDashboardPlugin(...) 来添加导航项、路由和插槽小组件,并通过 @spree/admin-sdk 的 client.request 逃生接口读写你的端点。将它作为带有 spree.dashboard.plugin 标记的 npm 包发布;使用者通过 pnpm add 添加后,它会自动注册。请为导航键、本地化键和 API 路径添加命名空间(使用 acme-brands,而不是 brands)— 重复的键会导致启动失败。参见 spree-dashboard-plugins。
测试扩展
bundle exec rake test_app # generate spec/dummy (re-run after adding migrations)
bundle exec rspec
通过 spree_dev_tools 使用 RSpec + Factory Bot。将工厂放在 lib/spree_reviews/factories.rb 中。API 规格使用 spree/api/testing_support/v3/base 中的共享上下文——'API v3 Store'、'API v3 Admin authenticated'、'API v3 Admin with custom permissions'、'API v3 Seller authenticated'。请参阅 spree-testing。
注意事项
- 在错误的阶段注册 = 悄然失效。 在
after_initialize外追加支付方式、履约服务提供商或集成,会在启动时消失(核心会重新分配这些数组)。表现为:无法在控制面板中选择 / 出现“type must be registered” 422 错误。 additional_permitted_attributes << :x会引发FrozenError。务必使用+=。- Admin 控制器没有
scoped_resource会在请求时引发错误——每个 Admin API 控制器都必须指定其权限作用域。 - 迁移不会自动应用。 升级扩展后,重新运行其安装生成器(或运行
spree rails railties:install:migrations),然后运行spree migrate。单独运行spree migrate只会安装核心迁移。 - 不要重命名复制过来的迁移——
.<engine_name>.rb后缀用于让 Rails 跳过已复制的迁移,也用于让 Spree 的启动检查发现缺失的迁移。 - 凭据应存放在
Spree::Integration中(按店铺区分,:password偏好设置会被遮蔽),而不是 ENV 中——多店铺应用会使用不同账户付款 / 发货。 - 同一方法上有两个装饰器——最后加载的会生效。优先使用钩子 / 事件,以便扩展能够组合。
- 订阅者代码会热重载;更改 注册项(初始化器)则需要重启。
延伸阅读
- 扩展教程:
node_modules/@spree/docs/dist/developer/contributing/creating-an-extension.md - 服务提供商与集成:
node_modules/@spree/docs/dist/developer/providers/overview.md - 控制面板插件:
node_modules/@spree/docs/dist/developer/dashboard/plugins/{overview,scaffolding,distributing}.md - 定制快速入门:https://spreecommerce.org/docs/developer/customization/quickstart
- 相关技能:
spree-customization、spree-providers、spree-workflows、spree-resource、spree-auth-permissions、spree-dashboard-plugins、spree-testing