mcpField
S
spree-resource

spree-resource

github.com/spree开发10221 次安装4 周前更新
代码仓库

关于 spree-resource

spree-resource 是面向 Spree 开发者的 API 资源生成指南,介绍如何用 CLI 创建模型、迁移及 Store 和 Admin API 表面。它还涵盖字段约定、权限、路由和生成文件的维护方式。

主要功能

  1. 01生成模型与数据库迁移
  2. 02生成 Store 和 Admin API 控制器与序列化器
  3. 03配置路由及权限范围
  4. 04支持存储范围、生命周期事件和自定义字段选项
  5. 05说明生成文件的维护约定

适用场景

  1. 01为 Spree 添加新的 API 资源
  2. 02创建只读目录资源或可写客户资源
  3. 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 / :datetimenull: 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_texthas_spree_rich_text + Spree::SanitizableRichText
logo:attachment / photos:attachmentshas_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::FooSpree.base_class父类表达式。
--permission-group=settingscatalog在角色编辑器和 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, …) 不会重复添加。权限本地化文件仅创建一次,因此编辑过的标签在重新运行后仍会保留。

运行生成器后

  1. 检查模型 — 添加验证、关联和作用域。生成的 belongs_to 是必需的(Spree 模型默认要求 belongs_to;失败信息为 "must exist")。对于可选关联,添加 optional: true,并移除迁移中的 null: false 限制。

  2. 运行迁移 — spree migrate。

  3. 授予权限 — 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。

  4. 读取时按店铺限定范围 — 基础控制器使用 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。权限决定某人能否读取品牌,而不是能读取哪些品牌——查询时仍须限定店铺范围。

  5. 允许的属性 — 生成的控制器在 resource_permitted_attributes 中声明可写列(而非覆盖 permitted_params),因此扩展声明的属性(Spree::Brand.additional_permitted_attributes += [...])和参数规范化仍会生效:

    def resource_permitted_attributes
      [:name, :slug, :active]
    end
    

    编辑列表以移除客户端不应写入的列——但控制器受生成器管理,重新运行会恢复模板。

  6. 运行规格 — 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_scope
  • node_modules/@spree/docs/dist/developer/customization/api.md — 扩展 API
  • 生成器源码:spree/core/lib/generators/spree/{api_resource,model}/

评论

支持 Markdown