关于 spree-resource
spree-resource 是面向 Spree 开发者的 API 资源生成指南,介绍如何用 CLI 创建模型、迁移及 Store 和 Admin API 表面。它还涵盖字段约定、权限、路由和生成文件的维护方式。
主要功能
- 01生成模型与数据库迁移
- 02生成 Store 和 Admin API 控制器与序列化器
- 03配置路由及权限范围
- 04支持存储范围、生命周期事件和自定义字段选项
- 05说明生成文件的维护约定
适用场景
- 01为 Spree 添加新的 API 资源
- 02创建只读目录资源或可写客户资源
- 03生成仅供后台使用的资源
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-resource安装后在 Claude Code 中输入/spree-resource确认已加载。
详细介绍
添加 Spree 资源
命令使用 Spree CLI(
spree …、Docker、server/中的 Rails 应用)。在没有 CLI 的经典 Rails 应用中,从应用根目录运行bin/rails g spree:api_resource …,并从路径中去掉server/前缀(参见spree-project)。
要添加一个通过 v3 API 暴露的模型,请使用 spree:api_resource 生成器。一条命令即可生成:
- 模型(
server/app/models/spree/<name>.rb)及其迁移 - Store + Admin API 控制器和序列化器
- FactoryBot 工厂,以及两个 API 的控制器规格
- 应用
config/routes.rb中Spree::Core::Engine.add_routes代码块内的路由行 read_<plural>/write_<plural>权限范围(追加到config/initializers/spree.rb的Spree.permissions.register_scope),以及角色编辑器标签(config/locales/spree_<plural>.en.yml)——仅限 Admin 界面
前提条件:先运行 spree eject。生成器在 Web 容器内执行,只有 eject 后的开发 compose 会绑定挂载 ./server,因此生成的文件会出现在主机上并持久保留。
一条命令即可完成
spree generate api_resource Brand name:string:index slug:string:uniq active:boolean
spree migrate
输出摘要:
✓ Generated Spree::Brand API resource
Prefixed ID: brand_xxxxxxxxxx
Store API: /api/v3/store/brands (read-only)
Admin API: /api/v3/admin/brands (full CRUD)
字段语法遵循 Rails 的属性解析器,并应用 Spree 约定:
| 你写入 | 生成结果 |
|---|---|
name:string / :text / :integer / :decimal / :boolean / :date / :datetime | null: false 列;非布尔字段会进行存在性验证 |
name:string:index | 非唯一索引 |
slug:string:uniq | 唯一索引 + 唯一性验证——如果模型按 store 作用域区分(默认如此),则限定在 store_id 范围内,并添加 spree_base_uniqueness_scope |
brand:belongs_to(或 :references) | 带有 class_name 和索引的 belongs_to,没有外键约束,null: false |
category:belongs_to{TaxCategory} | 显式类提示(仅限未限定名称)→ class_name: 'TaxCategory',在 Spree 命名空间内解析 |
description:rich_text | has_spree_rich_text + Spree::SanitizableRichText |
logo:attachment / photos:attachments | has_one_attached / has_many_attached |
belongs_to 类解析规则:user → Spree.customer_class;admin_user、created_by、approver、canceler → Spree.admin_user_class;其他任何名称 → Spree::<Camelized>。
标志
| 标志 | 默认值 | 效果 |
|---|---|---|
--writable | 关闭 | Store API 支持 create/update/destroy。默认的 Store API 为只读(index + show)。 |
--no-store | — | 跳过 Store API 控制器、序列化器和路由。仅 Admin 资源。 |
--no-admin | — | 跳过 Admin API 界面。较少使用。 |
--store-name=Discount | — | 在 Store API 中使用其他名称暴露资源(/api/v3/store/discounts);模型、表和 Admin API 保留规范名称。 |
--no-store-scoped | 按 store 作用域区分 | 默认情况下,模型包含 Spree::SingleStoreResource(store_id 列、belongs_to :store、根据 Spree::Current.store 自动赋值、for_store 作用域、之后无法更改 store)。仅对全局参考数据选择退出。 |
--no-lifecycle-events | 开启 | 默认情况下,模型会调用 publishes_lifecycle_events(为订阅者和 Webhook 发布 brand.created/updated/deleted 事件)。 |
--custom-fields | 关闭 | 包含 Spree::HasCustomFields + Spree::Metadata(商家定义的自定义字段 + metadata JSON)。 |
--paranoid | 关闭 | acts_as_paranoid + deleted_at 列/索引;Admin 序列化器会暴露 deleted_at。 |
--id-prefix=br | 蛇形命名的类名 | 带前缀的 ID 前缀(默认是 brand_…)。Core 使用短前缀:prod_、variant_、cart_、or_、py_、ful_、cust_、sl_。 |
--parent=Spree::Foo | Spree.base_class | 父类表达式。 |
--permission-group=settings | catalog | 在角色编辑器和 API 密钥作用域选择器中显示 read_/write_ 键的位置(orders、catalog、customers、settings 等,也可以是你自己的小写标识符)。 |
--skip-routes | 关闭 | 不修改路由。 |
--skip-specs | 关闭 | 不生成控制器规格。 |
创建的内容
server/app/models/spree/brand.rb (owned-once)
server/db/migrate/<ts>_create_spree_brands.rb (append-only)
server/app/controllers/spree/api/v3/store/brands_controller.rb (managed)
server/app/controllers/spree/api/v3/admin/brands_controller.rb (managed)
server/app/serializers/spree/api/v3/brand_serializer.rb (managed)
server/app/serializers/spree/api/v3/admin/brand_serializer.rb (managed)
server/spec/factories/spree/brand_factory.rb (managed)
server/spec/controllers/spree/api/v3/{store,admin}/brands_controller_spec.rb (managed)
server/config/routes.rb (idempotent inject)
server/config/initializers/spree.rb (idempotent append — register_scope)
server/config/locales/spree_brands.en.yml (owned-once — permission label)
生成的模型(默认按店铺限定):
module Spree
class Brand < Spree.base_class
include Spree::SingleStoreResource
has_prefix_id :brand
publishes_lifecycle_events
validates :name, presence: true
validates :slug, presence: true, uniqueness: { scope: [:store_id, *spree_base_uniqueness_scope] }
self.whitelisted_ransackable_attributes = %w[name slug active]
self.whitelisted_ransackable_associations = %w[]
self.whitelisted_ransackable_scopes = %w[]
end
end
路由。 生成器会在 config/routes.rb 中 Spree::Core::Engine.add_routes 内的 namespace :store / namespace :admin 代码块中插入 resources :brands, only: [:index, :show](店铺)和 resources :brands(管理端)。create-spree-app / spree-starter 提供的此代码块为空。如果代码块或命名空间不存在,生成器会跳过并发出警告——请手动添加:
Rails.application.routes.draw do
Spree::Core::Engine.add_routes do
namespace :api, defaults: { format: 'json' } do
namespace :v3 do
namespace :store do
resources :brands, only: [:index, :show]
end
namespace :admin do
resources :brands
end
end
end
end
mount Spree::Core::Engine, at: '/'
end
“仅创建一次/受管理/仅追加”约定
- 模型(仅创建一次) — 只写入一次;重新运行不会触碰它。验证、作用域和关联都放在这里。
- 迁移(仅追加) — 只生成一次。使用新的迁移更改架构:
spree generate migration AddLogoToSpreeBrands logo_url:string。 - 控制器、序列化器、工厂、规格(受管理) — 重新运行时会覆盖。如果手动编辑了这些文件,请勿针对该资源重新运行生成器(否则请重新应用所做的修改)。
- 路由和权限作用域(幂等) — 已存在的路由行或
register_scope(:brands, …)不会重复添加。权限本地化文件仅创建一次,因此编辑过的标签在重新运行后仍会保留。
运行生成器后
-
检查模型 — 添加验证、关联和作用域。生成的
belongs_to是必需的(Spree 模型默认要求belongs_to;失败信息为 "must exist")。对于可选关联,添加optional: true,并移除迁移中的null: false限制。 -
运行迁移 —
spree migrate。 -
授予权限 — Admin 控制器声明了
scoped_resource :brands,生成器也在config/initializers/spree.rb中注册了对应的作用域:# Permissions for the Brand API — grants read_brands / write_brands # to staff roles and secret API keys. Labels: config/locales/spree_brands.en.yml Spree.permissions.register_scope(:brands, group: :catalog, resources: -> { [Spree::Brand] })在向员工角色授予
read_brands/write_brands(通过仪表板的 Settings → Roles、Admin API 或种子数据),或在密钥上创建这些权限之前,只有拥有完全访问权限的主体才能访问(admin角色、具有read_all/write_all权限的密钥)。标签位于spree.permissions_catalog.resources.brands下(label,以及可选的description)。使用--no-admin时不会注册任何内容。请参阅spree-auth-permissions。 -
读取时按店铺限定范围 — 基础控制器使用
Model.for_store(current_store)限定集合范围。SingleStoreResource提供此作用域;对于--no-store-scoped模型,如果关联存在,for_store会回退到current_store.<plural>,否则使用整张表。若要使用current_store.brands,请通过装饰器向Spree::Store添加has_many :brands, class_name: 'Spree::Brand', dependent: :destroy。权限决定某人能否读取品牌,而不是能读取哪些品牌——查询时仍须限定店铺范围。 -
允许的属性 — 生成的控制器在
resource_permitted_attributes中声明可写列(而非覆盖permitted_params),因此扩展声明的属性(Spree::Brand.additional_permitted_attributes += [...])和参数规范化仍会生效:def resource_permitted_attributes [:name, :slug, :active] end编辑列表以移除客户端不应写入的列——但控制器受生成器管理,重新运行会恢复模板。
-
运行规格 —
spree rspec spec/controllers/spree/api/v3/。规格使用spree_api测试支持中的'API v3 Store guest'/'API v3 Admin authenticated'共享上下文(请参阅spree-testing)。
添加状态
Spree 没有状态机。对于具有生命周期的记录,请添加字符串 status 列并使用 Spree::HasStatus;状态转换应放在工作流中(请参阅 spree-workflows):
include Spree::HasStatus
has_status :draft, :active, :archived, default: :draft # predicates (active?), scopes (.active), inclusion validation, .with_status(...)
扩展可通过 Spree::Brand.add_status('paused', after: 'active') 追加状态。不要使用 Rails 枚举表示状态。
TypeScript 类型
在项目中,生成的序列化器属于应用本地内容——不包含在已发布的 @spree/sdk / @spree/admin-sdk 类型中。请使用手写类型,通过 client.request<T>(...) 调用自定义端点(请参阅 spree-typescript-sdk)。生成的序列化器中的 typelize 行说明了 JSON 类型(小数会序列化为字符串)。
常见模式
# Read-only catalog resource (Store GET, Admin CRUD)
spree generate api_resource Brand name:string:uniq active:boolean
# Writable customer-facing resource
spree generate api_resource SavedList label:string user:belongs_to --writable
# Admin-only back-office data
spree generate api_resource SupplierNote body:text --no-store
# Soft-delete + custom fields
spree generate api_resource Vendor name:string:uniq slug:string:uniq --paranoid --custom-fields
# Global reference data (no store_id)
spree generate api_resource Certification code:string:uniq --no-store-scoped
对于可写的客户资源,请记住,Store API 会根据所有权进行授权:在 Store 控制器中覆盖 scope,通过客户读取数据(例如 current_user.saved_lists),否则任何购物者都能看到商店中的所有记录。
仅模型 — 不包含 API 接口
spree:model 会按照相同约定生成模型和迁移(带前缀的 ID、Spree.base_class、商店范围限定、生命周期事件、null: false、无外键、ransack 允许列表),并接受相同的模型标志(--paranoid、--custom-fields、--id-prefix、--parent、--no-store-scoped、--no-lifecycle-events)。与 api_resource 不同,它会保留 Rails 的测试框架钩子,因此配置好 rspec/factory_bot 后,还会生成一个模型规格桩和工厂。
spree generate model BrandImage position:integer brand:belongs_to
当记录仅供内部使用(日志、联结表)、只能通过父级 API 访问,或你打算手动编写控制器时,请使用它。之后若要添加 API,请运行 spree generate api_resource Brand …——它会检测已有模型并跳过模型和迁移(仅生成一次),只生成 API 接口。
注意事项
- 不要添加外键约束或 Rails 枚举;保留
foreign_key: false,并使用字符串状态。 - 扩展核心模型(例如在产品上添加
brand_id)时,应使用装饰器,并设置Spree::Product.additional_permitted_attributes += [:brand_id](切勿使用<<——该数组已冻结)。请参阅spree-decorators。 - **作业、rake 任务、控制台:**范围限定到商店的记录需要一个商店。设置
Spree::Current.store = store(不要依赖可能为 nil 的Spree::Store.default)。 - 自定义只读成员操作——将其添加到控制器的
read_actions,这样它们会映射到read_*而非write_*。
延伸阅读
node_modules/@spree/docs/dist/developer/tutorial/model-and-api.md— Brand 端到端示例node_modules/@spree/docs/dist/developer/customization/permissions.md—register_scopenode_modules/@spree/docs/dist/developer/customization/api.md— 扩展 API- 生成器源码:
spree/core/lib/generators/spree/{api_resource,model}/