spree-customization について
spree-customizationは、コアをフォークせずに拡張する方法を選ぶためのSpree 6開発者向けスキルです。設定、フック、イベント、プロバイダー、APIリソースなどのカスタマイズ手段を用途別に示します。
主な機能
- 01変更に適した拡張ポイントを選択
- 02ワークフローフックで処理を検証・変更
- 03サブスクライバーやWebhookでイベントに対応
- 04プロバイダーと認証戦略を設定
- 05リソース、ダッシュボード、モデルを拡張
活用シーン
- 01チェックアウト中にポリシーを適用
- 02注文イベントを外部システムへ送信
- 03APIリソースやダッシュボード機能を追加
- 04コアワークフローを独自の処理に置換
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ spree/ agent-skills --skill spree-customizationインストール後、Claude Code で/spree-customizationを入力して読み込まれたか確認します。
詳細
Spree のカスタマイズ — コードはどこに配置すればよいですか?
Spree 6 は、フォークせずに拡張できるように構築されています。難しいのは、どのパターンを選ぶかです。表を上から順に確認してください。上位の選択肢はデータや設定で、実装コストが低く、アップグレードの影響を受けません。下位の選択肢は Spree の内部実装との結びつきが強くなります。
コマンドは
spreeCLI 形式を使用しています。従来の Rails アプリでは、アプリのルートからbin/rails …を使用し、server/のパスプレフィックスを省いてください。詳細はspree-projectを参照してください。
判断フロー
| 実現したいこと… | 使用する機能 | スキル |
|---|---|---|
| 通貨、市場、言語、配送地域/配送方法、税率、決済方法、注文番号の採番を変更する | ストア設定 — ダッシュボード → Settings、または Admin API。データの変更であり、デプロイは不要です | spree-dashboard、spree-api-v3 |
| インストール全体に適用される上限を調整する(パスワードの長さ、JWT の有効期限、レート制限、カートの有効期限、Webhook の有効/無効) | SPREE_* 環境変数(起動時に型チェックと検証を実施)。Spree.config { |c| … } も引き続き config/initializers/spree.rb で使用できます | —(ドキュメント:customization/configuration) |
レコードタイプごとに型付き設定を持たせる(Brand の「featured」フラグ、計算機のレートなど) | モデルの設定項目 — preferences 列を持つモデルに preference :featured, :boolean, default: false を追加します | spree-resource |
| 操作を拒否する(購入上限、地域ポリシー、B2B の利用資格) | ワークフローの validate フック — Spree.hooks.register('carts.add_item.validate', 'MyApp::CheckLimit') を登録し、ハンドラーで workflow.reject! を呼び出します | spree-workflows |
| コアフローの内部でコードを実行する(確定後、キャンセル前、価格/プロバイダーのコンテキストを渡す) | ワークフローのライフサイクル/コンテキストフック — carts.complete.after_finalize、orders.cancel.before_cancel、set_*_context | spree-workflows |
| アプリ内で何かが起きた後に処理を行う(インデックス作成、同期、メール送信、監査) | イベントサブスクライバー — order.placed、product.updated などを Spree::Subscriber で処理します | spree-events-webhooks |
| Ruby を使わずに、外部システムへイベントを通知する | Webhook エンドポイント — ダッシュボード → Settings → Webhooks、または Admin API | spree-events-webhooks |
| チェックアウト完了前に何かを必須にする(PO 番号、電話番号、年齢確認)、またはチェックアウトのステップを追加する | Spree::Checkout::Registry.add_requirement / register_step — Cart API の requirements[] に反映されます | spree-checkout |
| 税金、配送レート、フルフィルメント、検索、価格設定、在庫、売上金の支払い、デジタル商品の配送、決済の仕組みを置き換える | プロバイダー — クラスを登録し(Spree.tax_providers、Spree.delivery_rate_providers、Spree.search_provider = …、Spree.payment_methods など)、ストア/市場/方法に設定します | spree-providers、spree-taxes、spree-payments |
| 独自の IdP またはトークンで顧客/スタッフを認証する | 認証ストラテジー — Spree.store_authentication_strategies.add(:idp, MyApp::Auth::IdpStrategy)(admin_/seller_ も使用可能) | spree-auth-permissions |
| 販売者向けの追加データを保存する(素材、お手入れ方法、ギフトメッセージ) | カスタムフィールド — CustomFieldDefinition と record.set_custom_field('custom.material', 'Cotton') を使用します。型付きで、フィルタリング可能、storefront_visible に対応しています | spree-catalog |
| 連携/システム用データを保存する(ERP ID、UTM ソース、同期状態) | metadata — スキーマレス JSON。マージ方式で、Store API では書き込み専用、Admin API では読み取り可能です | spree-api-v3 |
q[...] で属性をフィルタリング/ソート可能にする | Spree.ransack.add_attribute / add_association / add_scope | spree-api-v3 |
| 住所ルールを追加/削除する(私書箱、電話番号形式) | Spree.validators.addresses.register / unregister(config.to_prepare から使用) | spree-customization(下記) |
| 新しい列を Admin API 経由で書き込み可能にする | Spree::Product.additional_permitted_attributes += [:brand_id] | spree-resource |
| スタッフのロール/API キーのスコープで新しいリソースへのアクセスを制限する | Spree.permissions.register_scope(:brands, group: :catalog, resources: -> { [Spree::Brand] }) → read_brands / write_brands | spree-auth-permissions |
| 新しいモデルと Admin/Store エンドポイント一式を追加する | spree generate api_resource Brand … | spree-resource |
| コアモデルにステータスを追加する | Spree::Return.add_status('on_hold', after: 'approved') + レコードをそのステータスに移行する独自のワークフロー | spree-workflows |
| コアフローの根本的な動作を変更する | 依存関係の差し替え — Spree::Dependencies.cart_add_item_workflow = 'MyApp::Carts::AddItem' | spree-dependencies |
| コアモデルに関連付け、スコープ、ヘルパーメソッドを追加する | デコレーター(spree generate model_decorator Spree::Product)— 最終手段 | spree-decorators |
| 管理画面、フィールド、列、ナビゲーション項目を追加する | apps/dashboard/src/plugins.ts のダッシュボードプラグイン(defineDashboardPlugin) | spree-dashboard、spree-dashboard-plugins |
| 上記の機能をアプリ間で共有する | 拡張 gem(Rails エンジン)+任意でダッシュボードプラグイン(spree plugin new) | spree-extensions |
フック、イベント、それとも依存関係?
- 処理をブロックしたり、結果を変更したりする必要がありますか? → ワークフローフックです。フローを停止できるのは
validateフック(およびbefore_cancelフック)のみで、いずれもお金が動く前に実行されます。 - 発生したことを把握するだけでよいですか? → イベントサブスクライバー(アプリ内)または Webhook(外部)です。疎結合で、デフォルトでは非同期に動作し、アップグレード後も変更せずに使えます。
- フローを根本的に変える必要がありますか? → 依存関係の差し替えです。そのクラスをアップグレードのたびに自分で管理することになります。
実例
地域ごとにチェックアウトをブロックする — validate フック
# server/config/initializers/spree.rb
Spree.hooks.register('carts.complete.validate', 'MyApp::RegionPolicy')
# server/app/services/my_app/region_policy.rb
module MyApp
class RegionPolicy
def call(workflow)
return unless workflow.cart.ship_address&.country_code == 'KP'
workflow.reject!('We cannot deliver to this region')
end
end
end
フックキーは起動時に検証されます(タイプミスがあると Spree::Hooks::UnknownHookError が発生します)。ハンドラーはクラス名の文字列として保存されるため、再読み込み後も安全に登録されます。複数のハンドラーでキーを共有でき、最初に実行された reject! が優先されます。
注文確定を ERP に送信する — イベントサブスクライバー
spree generate subscriber ErpOrderSync order.placed # creates the class AND registers it
# server/app/subscribers/erp_order_sync_subscriber.rb
class ErpOrderSyncSubscriber < Spree::Subscriber
subscribes_to 'order.placed'
def handle(event)
order = Spree::Order.find_by_prefix_id(event.payload['id'])
MyApp::Erp.push(order) if order
end
end
サブスクライバーは自動検出されません。ジェネレーターを使わない場合は、Spree.subscribers << ErpOrderSyncSubscriber を自分で追加してください。
B2B カートで PO 番号を必須にする — チェックアウト要件
Spree::Checkout::Registry.add_requirement(
step: :payment,
field: :po_number,
message: 'PO number is required',
satisfied: ->(cart) { cart.metadata['po_number'].present? },
applicable: ->(cart) { cart.company.present? }
)
この要件は Cart API の requirements 配列に表示されるため、ストアフロントではルールを重複させずに描画できます。また、要件が満たされるまでは Carts::Complete による完了処理が拒否されます。
商品に brand_id を追加する — リソース+許可属性+デコレーター
spree generate api_resource Brand name:string
spree generate migration AddBrandIdToSpreeProducts brand_id:bigint:index
spree migrate
# server/config/initializers/spree.rb
Rails.application.config.to_prepare do
Spree::Product.additional_permitted_attributes += [:brand_id] # += — the default is frozen
Spree::Product.storefront_ransackable_associations |= %w[brand] # Store API filters follow only this list
end
Spree.ransack.add_association(Spree::Product, :brand) # Admin API filters
Spree.permissions.register_scope(:brands, group: :catalog, resources: -> { [Spree::Brand] })
# server/app/models/spree/product_decorator.rb — structural addition, the one legit decorator job
module Spree
module ProductDecorator
def self.prepended(base)
base.belongs_to :brand, class_name: 'Spree::Brand', optional: true
end
end
Product.prepend(ProductDecorator)
end
データをマーチャントが編集でき、独自のテーブルが不要であれば、上記の方法よりカスタムフィールドのほうが手間がかかりません。
出荷先住所のルールを緩和する — バリデーターレジストリー
# server/config/initializers/spree.rb
Rails.application.config.to_prepare do
Spree.validators.addresses.register(MyApp::PostBoxValidator) # an ActiveModel::Validator
Spree.validators.addresses.unregister(Spree::Addresses::PhoneValidator)
end
いくつかのルールは、単なるストア設定でもあります(address_requires_phone、company_field_enabled、disable_sku_validation)。まずこれらを確認してください。その他のモデル向けに汎用的なバリデーターレジストリーはありません。モデルバリデーションをデコレーターで追加し、操作を拒否するには validate フックを使ってください。
カートへの商品追加処理を置き換える — 依存関係の差し替え
Spree::Dependencies.cart_add_item_workflow = 'MyApp::Carts::AddItem' # subclass Spree::Carts::AddItem
ワークフローのキーは _workflow で終わります(carts_complete_workflow、order_cancel_workflow、cart_recalculate_totals_workflow、fulfillment_update_workflow など)。通常のサービスのキーは _service で終わります(order_update_statuses_service)。動作を追加するだけであれば、フックを優先してください。
アンチパターン
- デコレーターで
finalize!、after_finalize、キャンセルなどのライフサイクルメソッドをオーバーライドする → フックを登録してください(carts.complete.after_finalize、orders.cancel.after_cancel)。フック可能なステートマシンはもうありません。state_machineブロック、after_transition、checkout_flowは存在しません。 - 副作用を起こすために Spree モデルへ
after_save/after_commitコールバックを追加する → イベントサブスクライバーを使ってください。 clear_validators!→ 他の拡張機能のものも含め、モデルのすべてのバリデーションが消去されます。ストア設定、Spree.validators.addresses.unregister、または対象を絞ったデコレーターを使ってください。Spree::Product.additional_permitted_attributes << :brand_id→FrozenErrorが発生します。必ず+=を使ってください。- 従来の
*_service依存関係キー(cart_add_item_service、carts_complete_service)を書く → 書き込みは保留され、警告とともに無視されます。*_workflowキーを使ってください。 payment_status/fulfillment_statusを直接設定する → これらはSpree::Orders::UpdateStatusesによって算出されます。代わりに支払い/フルフィルメントを操作してください。- 新しいステータスフィールドに enum やステートマシンを使う →
include Spree::HasStatus; has_status :a, :b, default: :aを使い、ワークフロー内で遷移を定義してください。 - Rails のビュー/パーシャルを使って管理画面を変更する → 管理画面は React ダッシュボードです。ダッシュボードプラグインを使ってください。
- 1つのアプリ用に非公開の拡張 gem を作る → コードは
server/app/に置いてください。gem は複数のアプリで共有するためのものです。 - Spree をフォークする → まずこの表を確認してください。ほぼ間違いなく適切な拡張ポイントがあります。
さらに詳しく読むには
node_modules/@spree/docs/dist/developer/customization/quickstart.md— 推奨される標準的な順序…/customization/{configuration,workflows,dependencies,decorators,validations,metadata,model-preferences,permissions,checkout,api}.md- 各プロバイダーの仕様については、
…/providers/overview.mdと…/how-to/custom-*.md - 管理者向け UI については、
…/dashboard/customization/quickstart.md - オンライン: https://spreecommerce.org/docs/developer/customization/quickstart