@spree/docs 0.1.248 → 0.1.249
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-reference/admin-api/authentication.md +34 -14
- package/dist/api-reference/admin-api/endpoints.md +366 -14
- package/dist/api-reference/admin-api/errors.md +2 -2
- package/dist/api-reference/admin-api/introduction.md +3 -3
- package/dist/api-reference/admin-api/querying.md +6 -6
- package/dist/api-reference/store-api/monetary-amounts.md +5 -5
- package/dist/api-reference/webhooks-events.md +330 -335
- package/dist/developer/agentic/agent-skills.md +5 -2
- package/dist/developer/agentic/llm-docs.md +2 -1
- package/dist/developer/cli/admin-api.md +1 -1
- package/dist/developer/cli/quickstart.md +2 -2
- package/dist/developer/contributing/creating-an-extension.md +292 -146
- package/dist/developer/contributing/developing-spree.md +13 -17
- package/dist/developer/core-concepts/catalogs.md +2 -2
- package/dist/developer/core-concepts/channels.md +3 -3
- package/dist/developer/core-concepts/companies.md +2 -1
- package/dist/developer/core-concepts/delivery-setup.md +2 -2
- package/dist/developer/core-concepts/discounts.md +3 -3
- package/dist/developer/core-concepts/events.md +6 -5
- package/dist/developer/core-concepts/freight.md +3 -2
- package/dist/developer/core-concepts/fulfillments.md +10 -8
- package/dist/developer/core-concepts/imports-exports.md +11 -8
- package/dist/developer/core-concepts/inventory.md +2 -2
- package/dist/developer/core-concepts/media.md +14 -14
- package/dist/developer/core-concepts/orders.md +2 -2
- package/dist/developer/core-concepts/payments.md +1 -2
- package/dist/developer/core-concepts/products.md +6 -6
- package/dist/developer/core-concepts/reporting.md +4 -3
- package/dist/developer/core-concepts/returns-exchanges-claims.md +7 -7
- package/dist/developer/core-concepts/search-filtering.md +3 -3
- package/dist/developer/core-concepts/sellers.md +3 -3
- package/dist/developer/core-concepts/staff-roles.md +4 -2
- package/dist/developer/core-concepts/store-credits-gift-cards.md +1 -1
- package/dist/developer/core-concepts/stores.md +2 -2
- package/dist/developer/core-concepts/translations.md +12 -8
- package/dist/developer/core-concepts/webhooks.md +19 -18
- package/dist/developer/create-spree-app/quickstart.md +2 -7
- package/dist/developer/customization/api.md +1 -1
- package/dist/developer/customization/checkout.md +2 -2
- package/dist/developer/customization/dependencies.md +53 -37
- package/dist/developer/customization/permissions.md +2 -2
- package/dist/developer/dashboard/concepts.md +1 -1
- package/dist/developer/dashboard/customization/navigation.md +3 -2
- package/dist/developer/dashboard/customization/permissions.md +6 -6
- package/dist/developer/dashboard/plugins/publishing.md +4 -4
- package/dist/developer/dashboard/plugins/scaffolding.md +1 -1
- package/dist/developer/dashboard/public-api.md +1 -1
- package/dist/developer/dashboard/recipes/attribute-end-to-end.md +3 -18
- package/dist/developer/deployment/aws.md +1 -1
- package/dist/developer/deployment/aws_ecs.md +3 -3
- package/dist/developer/deployment/background_jobs.md +9 -3
- package/dist/developer/deployment/docker.md +1 -2
- package/dist/developer/deployment/emails.md +3 -1
- package/dist/developer/deployment/environment_variables.md +2 -2
- package/dist/developer/deployment/render.md +2 -2
- package/dist/developer/how-to/build-a-marketplace.md +2 -2
- package/dist/developer/how-to/custom-api-authentication.md +1 -1
- package/dist/developer/how-to/custom-delivery-rate-provider.md +11 -3
- package/dist/developer/how-to/custom-document-numbers.md +1 -1
- package/dist/developer/how-to/custom-order-routing.md +15 -14
- package/dist/developer/how-to/custom-payment-method.md +17 -19
- package/dist/developer/how-to/custom-promotion.md +4 -4
- package/dist/developer/how-to/custom-search-provider.md +12 -5
- package/dist/developer/how-to/custom-stock-splitter.md +25 -24
- package/dist/developer/how-to/sell-digital-products.md +1 -1
- package/dist/developer/multi-tenant/quickstart.md +2 -2
- package/dist/developer/providers/payouts.md +6 -2
- package/dist/developer/sdk/admin/querying-and-errors.md +1 -1
- package/dist/developer/sdk/admin/quickstart.md +4 -4
- package/dist/developer/sdk/authentication.md +5 -2
- package/dist/developer/sdk/store/cart-checkout.md +4 -4
- package/dist/developer/storefront/nextjs/emails.md +4 -2
- package/dist/developer/storefront/nextjs/testing.md +1 -1
- package/dist/developer/upgrades/5.6-to-6.0.md +51 -20
- package/dist/integrations/search/meilisearch.md +4 -4
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "Agent Skills"
|
|
3
3
|
sidebarTitle: Agent Skills
|
|
4
|
-
description: "Teach your AI coding agent Spree's conventions —
|
|
4
|
+
description: "Teach your AI coding agent Spree's conventions — 38 installable skills, a Spree expert subagent, slash commands, and safety hooks."
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
[spree/agent-skills](https://github.com/spree/agent-skills) is a collection of agent skills for Spree Commerce. Skills are markdown instructions your coding agent loads on demand — they encode Spree's conventions, customization decision trees, API patterns, and upgrade flows, so the agent does things the Spree way instead of improvising from training data.
|
|
@@ -10,6 +10,9 @@ The skills work with Claude Code, Codex, Cursor, GitHub Copilot, Cline, Aider, Z
|
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
|
+
> **NOTE:** The skills target Spree 6. For a Spree 5 project, use the [`v0.3.0` release](https://github.com/spree/agent-skills/tree/v0.3.0) of the skills.
|
|
14
|
+
|
|
15
|
+
|
|
13
16
|
Run from your project directory:
|
|
14
17
|
|
|
15
18
|
```bash
|
|
@@ -30,7 +33,7 @@ npx skills update # update installed skil
|
|
|
30
33
|
|
|
31
34
|
| | |
|
|
32
35
|
|---|---|
|
|
33
|
-
| **
|
|
36
|
+
| **38 skills** | Project conventions, the customization decision tree (workflow hook vs event subscriber vs provider vs dependency swap vs decorator), workflows and hooks, the v3 Store, Admin and Seller APIs, the `spree:model` / `spree:api_resource` generators, TypeScript SDKs, staff permissions, the admin dashboard and plugins, upgrades (including 5.6 → 6.0), and domain deep-dives: catalog, pricing, inventory, checkout, order totals, taxes, payments, promotions, fulfillment, returns, marketplace, B2B, multi-tenant, reporting, i18n, testing, security, performance, deployment. |
|
|
34
37
|
| **`spree-expert` subagent** | A Claude Code agent for multi-step investigations (upgrade audits, checkout-flow debugging, API surface planning). It researches in its own context window and returns a focused report, keeping your main session lean. |
|
|
35
38
|
| **2 slash commands** | `/spree:doctor` diagnoses the local dev stack (containers, env, migrations, job queues) and prescribes fixes. `/spree:audit-upgrade [version]` runs a read-only upgrade-readiness audit against your codebase. |
|
|
36
39
|
| **2 safety hooks** | Block destructive database commands (`db:drop`, `DROP TABLE spree_*`, mass deletes, force-pushes to main) and warn when an edit introduces a hardcoded secret (Stripe live keys, AWS keys, API tokens). Disable with `SPREE_HOOKS_DISABLE=1`. |
|
|
@@ -40,7 +40,8 @@ node_modules/@spree/docs/dist/
|
|
|
40
40
|
│ ├── customization/ # decorators, dependencies, events, …
|
|
41
41
|
│ ├── storefront/ # Next.js storefront guides
|
|
42
42
|
│ ├── sdk/ # TypeScript SDK docs
|
|
43
|
-
│ ├──
|
|
43
|
+
│ ├── dashboard/ # React admin dashboard customization and plugins
|
|
44
|
+
│ ├── providers/ # tax, delivery, payment, search and other providers
|
|
44
45
|
│ └── how-to/ # focused guides
|
|
45
46
|
└── api-reference/
|
|
46
47
|
└── store.yaml # the full Store API OpenAPI spec
|
|
@@ -52,7 +52,7 @@ For remote stores, create a secret key in the admin under **Settings → API Key
|
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
54
|
spree auth login --profile prod --base-url https://store.example.com
|
|
55
|
-
spree api get /orders --profile prod -q "status_eq=
|
|
55
|
+
spree api get /orders --profile prod -q "status_eq=placed"
|
|
56
56
|
spree auth status
|
|
57
57
|
spree auth logout --profile prod
|
|
58
58
|
```
|
|
@@ -11,7 +11,7 @@ The Spree CLI (`@spree/cli`) does two things from your terminal:
|
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
spree dev # boot the project
|
|
14
|
-
spree api get /orders -q status_eq=
|
|
14
|
+
spree api get /orders -q status_eq=placed --limit 10 # query the Admin API
|
|
15
15
|
spree api post /products -d '{"name":"Classic Tee","prices":[{"currency":"USD","amount":"29.99"}]}' # create a resource
|
|
16
16
|
spree api endpoints --search refund # discover endpoints, offline
|
|
17
17
|
```
|
|
@@ -229,7 +229,7 @@ Run a generator inside the container. Bare generator names are auto-prefixed wit
|
|
|
229
229
|
```bash
|
|
230
230
|
spree generate model Brand name:string slug:string:uniq # runs spree:model
|
|
231
231
|
spree generate api_resource Brand name:string slug:string:uniq # runs spree:api_resource — model + v3 Store/Admin API
|
|
232
|
-
spree generate subscriber OmsOrderSync order.
|
|
232
|
+
spree generate subscriber OmsOrderSync order.placed # runs spree:subscriber — event subscriber + registration
|
|
233
233
|
spree generate migration AddPositionToSpreeBrands position:integer # Rails built-in, forwarded as-is
|
|
234
234
|
```
|
|
235
235
|
|
|
@@ -1,265 +1,411 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Creating a Spree extension
|
|
3
|
-
description:
|
|
3
|
+
description: Build a Spree extension gem — models, API endpoints, permissions, workflow hooks and event subscribers — and pair it with a dashboard plugin for the admin UI.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Overview
|
|
7
7
|
|
|
8
|
-
Spree
|
|
8
|
+
A Spree extension is a Ruby gem containing a Rails engine. It adds features to any Spree application without forking Spree: new models, new API endpoints, new permissions, and custom logic that runs inside Spree's workflows. You can share an extension with the Spree community on GitHub so anyone can use it and contribute improvements. For officially supported integrations, see the [Integrations directory](/integrations).
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
An extension is backend code only. Spree 6 has no Rails storefront or Rails admin to add views to:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
| Part of the feature | Where it lives |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Models, migrations, business logic | The extension gem |
|
|
15
|
+
| API endpoints (Store API and Admin API) | The extension gem |
|
|
16
|
+
| Permissions for staff roles and API keys | The extension gem |
|
|
17
|
+
| Admin screens | A [dashboard plugin](../dashboard/plugins/overview.md) — an npm package that talks to your Admin API endpoints |
|
|
18
|
+
| Storefront pages | Your storefront application, through [`@spree/sdk`](../sdk/quickstart.md) |
|
|
13
19
|
|
|
14
|
-
|
|
20
|
+
> **INFO:** Prefer Spree's extension points over [decorators](../customization/decorators.md): [workflow hooks](../customization/workflows.md) to change what happens inside a flow, [events](../core-concepts/events.md) to react after something happened, and `additional_permitted_attributes` to make your own columns writable. They keep working when Spree changes internally. See the [Customization Quickstart](../customization/quickstart.md) for guidance on choosing the right approach.
|
|
15
21
|
|
|
16
|
-
|
|
22
|
+
In this guide we build `spree_reviews`: customers read product reviews through the Store API, and staff manage them through the Admin API.
|
|
17
23
|
|
|
24
|
+
## Generate the extension
|
|
25
|
+
|
|
26
|
+
Install the Spree extension generator:
|
|
18
27
|
|
|
19
28
|
```bash
|
|
20
29
|
gem install spree_extension
|
|
21
30
|
```
|
|
22
31
|
|
|
23
|
-
Run the following command from a directory
|
|
32
|
+
Run the following command from a directory outside your Spree application:
|
|
24
33
|
|
|
25
34
|
```bash
|
|
26
|
-
spree-extension
|
|
35
|
+
spree-extension create reviews
|
|
27
36
|
```
|
|
28
37
|
|
|
29
|
-
This creates a `
|
|
38
|
+
This creates a `spree_reviews` directory containing the engine, a gemspec, a test setup and continuous integration configuration. Change to its directory:
|
|
30
39
|
|
|
31
40
|
```bash
|
|
32
|
-
cd
|
|
41
|
+
cd spree_reviews
|
|
33
42
|
```
|
|
34
43
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
The first thing we need to do is create a migration that adds a sale_price column to [variants](../core-concepts/products.md#variants).
|
|
38
|
-
|
|
39
|
-
We can do this with the following command:
|
|
44
|
+
> **WARNING:** The generated scaffold still declares a dependency on `spree_admin`, the Spree 5 Rails admin, which does not exist in Spree 6. Remove the `spree_admin` lines from both the `.gemspec` and the `Gemfile` before running `bundle install`. The JavaScript, importmap and asset setup in the scaffold is only used by the Spree 5 admin, so you can delete it too.
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
bin/rails g migration add_sale_price_to_spree_variants sale_price:decimal
|
|
43
|
-
```
|
|
46
|
+
## Add a model
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
Create the migration in `db/migrate/`:
|
|
46
49
|
|
|
47
|
-
```ruby
|
|
48
|
-
class
|
|
50
|
+
```ruby db/migrate/20260901000000_create_spree_reviews.rb
|
|
51
|
+
class CreateSpreeReviews < ActiveRecord::Migration[8.1]
|
|
49
52
|
def change
|
|
50
|
-
|
|
53
|
+
create_table :spree_reviews do |t|
|
|
54
|
+
t.references :store, null: false, index: true, foreign_key: false
|
|
55
|
+
t.references :product, null: false, index: true, foreign_key: false
|
|
56
|
+
t.integer :rating, null: false
|
|
57
|
+
t.text :body
|
|
58
|
+
t.boolean :approved, null: false, default: false
|
|
59
|
+
|
|
60
|
+
t.timestamps
|
|
61
|
+
end
|
|
51
62
|
end
|
|
52
63
|
end
|
|
53
64
|
```
|
|
54
65
|
|
|
55
|
-
|
|
66
|
+
Then the model:
|
|
56
67
|
|
|
57
|
-
|
|
68
|
+
```ruby app/models/spree/review.rb
|
|
69
|
+
module Spree
|
|
70
|
+
class Review < Spree.base_class
|
|
71
|
+
include Spree::SingleStoreResource
|
|
58
72
|
|
|
59
|
-
|
|
73
|
+
has_prefix_id :review
|
|
74
|
+
publishes_lifecycle_events
|
|
60
75
|
|
|
61
|
-
|
|
62
|
-
|
|
76
|
+
belongs_to :product, class_name: 'Spree::Product'
|
|
77
|
+
|
|
78
|
+
validates :rating, presence: true, inclusion: { in: 1..5 }
|
|
79
|
+
|
|
80
|
+
scope :approved, -> { where(approved: true) }
|
|
81
|
+
|
|
82
|
+
self.whitelisted_ransackable_attributes = %w[rating approved]
|
|
83
|
+
end
|
|
84
|
+
end
|
|
63
85
|
```
|
|
64
86
|
|
|
65
|
-
|
|
87
|
+
A few Spree conventions are at work here:
|
|
66
88
|
|
|
67
|
-
|
|
89
|
+
- **Reviews belong to a store.** `Spree::SingleStoreResource` fills in the store for new records, and the API only returns the current store's reviews. Commerce data is always per store.
|
|
90
|
+
- **Prefixed IDs.** `has_prefix_id` gives every review an ID like `review_k5nR8xLq` in API responses. Integer database IDs are never exposed.
|
|
91
|
+
- **No foreign key constraints** on business tables, and `class_name` on every association.
|
|
92
|
+
- **Lifecycle events.** `publishes_lifecycle_events` makes the model emit `review.created`, `review.updated` and `review.deleted`, which subscribers and [webhooks](../core-concepts/webhooks.md) can react to.
|
|
68
93
|
|
|
69
|
-
|
|
70
|
-
|
|
94
|
+
## Expose it through the API
|
|
95
|
+
|
|
96
|
+
Add serializers for both APIs. The Admin API serializer extends the Store API one with the fields only staff should see:
|
|
97
|
+
|
|
98
|
+
```ruby app/serializers/spree/api/v3/review_serializer.rb
|
|
99
|
+
module Spree
|
|
100
|
+
module Api
|
|
101
|
+
module V3
|
|
102
|
+
class ReviewSerializer < BaseSerializer
|
|
103
|
+
typelize rating: :number, body: [:string, nullable: true]
|
|
104
|
+
|
|
105
|
+
attributes :rating, :body
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
71
110
|
```
|
|
72
111
|
|
|
73
|
-
|
|
112
|
+
```ruby app/serializers/spree/api/v3/admin/review_serializer.rb
|
|
113
|
+
module Spree
|
|
114
|
+
module Api
|
|
115
|
+
module V3
|
|
116
|
+
module Admin
|
|
117
|
+
class ReviewSerializer < V3::ReviewSerializer
|
|
118
|
+
typelize approved: :boolean
|
|
119
|
+
|
|
120
|
+
attributes :approved, :created_at, :updated_at
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
```
|
|
74
127
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
128
|
+
The Store API controller is read-only and returns approved reviews only:
|
|
129
|
+
|
|
130
|
+
```ruby app/controllers/spree/api/v3/store/reviews_controller.rb
|
|
131
|
+
module Spree
|
|
132
|
+
module Api
|
|
133
|
+
module V3
|
|
134
|
+
module Store
|
|
135
|
+
class ReviewsController < ResourceController
|
|
136
|
+
protected
|
|
137
|
+
|
|
138
|
+
def model_class
|
|
139
|
+
Spree::Review
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def serializer_class
|
|
143
|
+
Spree::Api::V3::ReviewSerializer
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def scope
|
|
147
|
+
super.approved
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
78
154
|
```
|
|
79
155
|
|
|
80
|
-
|
|
156
|
+
The Admin API controller gives staff full create, read, update and delete access:
|
|
81
157
|
|
|
82
|
-
|
|
158
|
+
```ruby app/controllers/spree/api/v3/admin/reviews_controller.rb
|
|
159
|
+
module Spree
|
|
160
|
+
module Api
|
|
161
|
+
module V3
|
|
162
|
+
module Admin
|
|
163
|
+
class ReviewsController < ResourceController
|
|
164
|
+
scoped_resource :reviews
|
|
83
165
|
|
|
84
|
-
|
|
166
|
+
protected
|
|
85
167
|
|
|
86
|
-
|
|
168
|
+
def model_class
|
|
169
|
+
Spree::Review
|
|
170
|
+
end
|
|
87
171
|
|
|
88
|
-
|
|
89
|
-
|
|
172
|
+
def serializer_class
|
|
173
|
+
Spree::Api::V3::Admin::ReviewSerializer
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def resource_permitted_attributes
|
|
177
|
+
[:product_id, :rating, :body, :approved]
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
90
184
|
```
|
|
91
185
|
|
|
92
|
-
|
|
186
|
+
`scoped_resource :reviews` means only staff roles and secret API keys holding the `read_reviews` or `write_reviews` permission can call these endpoints. The next section registers that permission.
|
|
187
|
+
|
|
188
|
+
Finally, add the routes. The scaffold's `config/routes.rb` already contains the route hook:
|
|
93
189
|
|
|
94
|
-
```ruby
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
190
|
+
```ruby config/routes.rb
|
|
191
|
+
Spree::Core::Engine.add_routes do
|
|
192
|
+
namespace :api, defaults: { format: 'json' } do
|
|
193
|
+
namespace :v3 do
|
|
194
|
+
namespace :store do
|
|
195
|
+
resources :reviews, only: [:index, :show]
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
namespace :admin do
|
|
199
|
+
resources :reviews
|
|
200
|
+
end
|
|
99
201
|
end
|
|
100
202
|
end
|
|
101
203
|
end
|
|
102
|
-
|
|
103
|
-
Spree::HomeController.prepend SpreeSimpleSales::HomeControllerDecorator
|
|
104
204
|
```
|
|
105
205
|
|
|
106
|
-
|
|
206
|
+
Pagination, [Ransack](https://github.com/activerecord-hackery/ransack) filtering (`?q[rating_gteq]=4`), sorting and prefixed ID lookup all come from the base `ResourceController`. See [Customizing the API](../customization/api.md) for everything you can override.
|
|
107
207
|
|
|
108
|
-
|
|
208
|
+
## Register permissions
|
|
109
209
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
210
|
+
Register your model as a permission scope, so the `read_reviews` and `write_reviews` permissions appear in the staff role editor and can be granted to secret API keys:
|
|
211
|
+
|
|
212
|
+
```ruby config/initializers/spree.rb
|
|
213
|
+
Spree.permissions.register_scope(:reviews, group: :catalog, resources: -> {
|
|
214
|
+
[Spree::Review]
|
|
215
|
+
})
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Label the permission for the dashboard in your locale file:
|
|
219
|
+
|
|
220
|
+
```yaml config/locales/en.yml
|
|
221
|
+
en:
|
|
222
|
+
spree:
|
|
223
|
+
permissions_catalog:
|
|
224
|
+
resources:
|
|
225
|
+
reviews:
|
|
226
|
+
label: Reviews
|
|
227
|
+
description: Product reviews left by customers
|
|
114
228
|
```
|
|
115
229
|
|
|
116
|
-
|
|
230
|
+
See [Permissions](../customization/permissions.md) for audiences and read-only scopes.
|
|
117
231
|
|
|
118
|
-
|
|
232
|
+
## Extend core models and behavior
|
|
119
233
|
|
|
120
|
-
|
|
234
|
+
Extensions often need to change Spree's own resources as well as add new ones. Reach for these options in order.
|
|
121
235
|
|
|
122
|
-
|
|
123
|
-
|
|
236
|
+
### Make a new column writable
|
|
237
|
+
|
|
238
|
+
Suppose each product gets a `reviews_enabled` switch. Add the column with a migration, then let the existing product endpoints accept it — no controller changes needed:
|
|
239
|
+
|
|
240
|
+
```ruby lib/spree_reviews/engine.rb
|
|
241
|
+
config.to_prepare do
|
|
242
|
+
Spree::Product.additional_permitted_attributes += [:reviews_enabled]
|
|
243
|
+
end
|
|
124
244
|
```
|
|
125
245
|
|
|
126
|
-
|
|
246
|
+
Always append with `+=`, so you don't remove attributes that other extensions added.
|
|
247
|
+
|
|
248
|
+
### Run code inside a Spree workflow
|
|
127
249
|
|
|
128
|
-
|
|
129
|
-
> product = Spree::Product.first
|
|
130
|
-
=> #<Spree::Product id: 107377505, name: "Spree Bag", description: "Lorem ipsum dolor sit amet, consectetuer adipiscing...", available_on: "2013-02-13 18:30:16", deleted_at: nil, permalink: "spree-bag", meta_description: nil, meta_keywords: nil, tax_category_id: 25484906, shipping_category_id: nil, count_on_hand: 10, created_at: "2013-02-13 18:30:16", updated_at: "2013-02-13 18:30:16", on_demand: false>
|
|
250
|
+
[Workflow hooks](../customization/workflows.md) run your code at a named point inside a core flow — checkout completion, cancellations, refunds, product changes — and can stop the operation. For example, to require a description before a product with reviews enabled goes on sale:
|
|
131
251
|
|
|
132
|
-
|
|
133
|
-
|
|
252
|
+
```ruby config/initializers/spree.rb
|
|
253
|
+
Spree.hooks.register('products.activate.validate', 'SpreeReviews::RequireDescription')
|
|
254
|
+
```
|
|
134
255
|
|
|
135
|
-
|
|
136
|
-
|
|
256
|
+
```ruby app/services/spree_reviews/require_description.rb
|
|
257
|
+
module SpreeReviews
|
|
258
|
+
class RequireDescription
|
|
259
|
+
def call(workflow)
|
|
260
|
+
product = workflow.product
|
|
261
|
+
return unless product.reviews_enabled
|
|
262
|
+
return if product.description.present?
|
|
137
263
|
|
|
138
|
-
|
|
139
|
-
|
|
264
|
+
workflow.reject!('Add a description before activating a product with reviews.')
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
140
268
|
```
|
|
141
269
|
|
|
142
|
-
|
|
270
|
+
Hook names are checked when the application boots, so a typo fails at startup rather than silently never running.
|
|
143
271
|
|
|
144
|
-
|
|
272
|
+
### React to events
|
|
145
273
|
|
|
146
|
-
|
|
274
|
+
To do something *after* an event, without influencing it, write an [event subscriber](../core-concepts/events.md). For example, to ask customers for a review once their order arrives:
|
|
147
275
|
|
|
148
|
-
```ruby
|
|
149
|
-
module
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
276
|
+
```ruby app/subscribers/spree_reviews/review_request_subscriber.rb
|
|
277
|
+
module SpreeReviews
|
|
278
|
+
class ReviewRequestSubscriber < Spree::Subscriber
|
|
279
|
+
subscribes_to 'order.delivered'
|
|
280
|
+
|
|
281
|
+
def handle(event)
|
|
282
|
+
order = Spree::Order.find_by_prefix_id(event.payload['id'])
|
|
283
|
+
return unless order
|
|
284
|
+
|
|
285
|
+
SpreeReviews::ReviewRequestMailer.request_review(order).deliver_later
|
|
154
286
|
end
|
|
155
287
|
end
|
|
156
288
|
end
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Subscribers are not discovered automatically. Register them from the engine:
|
|
157
292
|
|
|
158
|
-
|
|
293
|
+
```ruby lib/spree_reviews/engine.rb
|
|
294
|
+
config.after_initialize do
|
|
295
|
+
Spree.subscribers << SpreeReviews::ReviewRequestSubscriber
|
|
296
|
+
end
|
|
159
297
|
```
|
|
160
298
|
|
|
161
|
-
|
|
299
|
+
### Decorators, as a last resort
|
|
162
300
|
|
|
163
|
-
|
|
301
|
+
When none of the above fits — adding an association to a core model, for example — use a [decorator](../customization/decorators.md):
|
|
164
302
|
|
|
165
|
-
|
|
303
|
+
```ruby app/models/spree_reviews/product_decorator.rb
|
|
304
|
+
module SpreeReviews
|
|
305
|
+
module ProductDecorator
|
|
306
|
+
def self.prepended(base)
|
|
307
|
+
base.has_many :reviews, class_name: 'Spree::Review', dependent: :destroy
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
166
311
|
|
|
167
|
-
|
|
312
|
+
Spree::Product.prepend SpreeReviews::ProductDecorator
|
|
313
|
+
```
|
|
168
314
|
|
|
169
|
-
|
|
315
|
+
Decorators are not loaded automatically. Load them from your engine:
|
|
170
316
|
|
|
171
|
-
|
|
317
|
+
```ruby lib/spree_reviews/engine.rb
|
|
318
|
+
config.to_prepare do
|
|
319
|
+
Dir.glob(root.join('app/**/*_decorator*.rb')) do |decorator|
|
|
320
|
+
Rails.configuration.cache_classes ? require(decorator) : load(decorator)
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## Add admin screens
|
|
326
|
+
|
|
327
|
+
Admin screens are built as a [dashboard plugin](../dashboard/plugins/overview.md): a React package that registers navigation entries, pages and product page widgets, and reads and writes data through your Admin API endpoints. Scaffold one with the Spree CLI:
|
|
172
328
|
|
|
173
329
|
```bash
|
|
174
|
-
|
|
330
|
+
npx @spree/cli plugin new reviews
|
|
175
331
|
```
|
|
176
332
|
|
|
177
|
-
|
|
333
|
+
See [Scaffolding](../dashboard/plugins/scaffolding.md) and [Backend integration](../dashboard/customization/backend.md) to connect it to the endpoints above, and [Distributing](../dashboard/plugins/distributing.md) to ship the gem and the npm package together.
|
|
178
334
|
|
|
179
|
-
|
|
180
|
-
|
|
335
|
+
## Add the extension to your application
|
|
336
|
+
|
|
337
|
+
From your Spree application's backend directory, add the gem to the `Gemfile`. Adjust the path to point at your extension:
|
|
181
338
|
|
|
182
|
-
|
|
183
|
-
|
|
339
|
+
```ruby Gemfile
|
|
340
|
+
gem 'spree_reviews', path: '../spree_reviews'
|
|
184
341
|
```
|
|
185
342
|
|
|
186
|
-
|
|
343
|
+
Install it, then copy the extension's migrations into the application and run them:
|
|
187
344
|
|
|
188
345
|
```bash
|
|
189
|
-
|
|
346
|
+
bundle install
|
|
347
|
+
bin/rails g spree_reviews:install
|
|
190
348
|
```
|
|
191
349
|
|
|
192
|
-
|
|
350
|
+
The Store API now serves reviews at `/api/v3/store/reviews`, and the Admin API manages them at `/api/v3/admin/reviews`.
|
|
193
351
|
|
|
194
|
-
|
|
195
|
-
require 'spec_helper'
|
|
196
|
-
|
|
197
|
-
describe Spree::Variant do
|
|
198
|
-
describe "#price_in" do
|
|
199
|
-
it "returns the sale price if it is present" do
|
|
200
|
-
variant = create(:variant, sale_price: 8.00)
|
|
201
|
-
expected = Spree::Price.new(variant_id: variant.id, currency: "USD", amount: variant.sale_price)
|
|
352
|
+
## Test the extension
|
|
202
353
|
|
|
203
|
-
|
|
354
|
+
An extension is not a full Rails application, so its tests run against a small generated Spree application. Create it from the extension's root directory:
|
|
204
355
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
it "returns the normal price if it is not on sale" do
|
|
211
|
-
variant = create(:variant, price: 15.00)
|
|
212
|
-
expected = Spree::Price.new(variant_id: variant.id, currency: "USD", amount: variant.price)
|
|
356
|
+
```bash
|
|
357
|
+
bundle exec rake test_app
|
|
358
|
+
```
|
|
213
359
|
|
|
214
|
-
|
|
360
|
+
Run this again whenever you add a migration. Tests use RSpec and Factory Bot, with Spree's factories and test helpers coming from [`spree_dev_tools`](https://github.com/spree/spree_dev_tools). Add a factory for your model:
|
|
215
361
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
362
|
+
```ruby lib/spree_reviews/factories.rb
|
|
363
|
+
FactoryBot.define do
|
|
364
|
+
factory :review, class: Spree::Review do
|
|
365
|
+
product
|
|
366
|
+
rating { 5 }
|
|
367
|
+
approved { true }
|
|
220
368
|
end
|
|
221
369
|
end
|
|
222
370
|
```
|
|
223
371
|
|
|
224
|
-
|
|
372
|
+
Then test your endpoints:
|
|
225
373
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
In this tutorial, you learned how to both install extensions and create your own. A lot of core Spree development concepts were covered and you gained exposure to some of the Spree internals.
|
|
374
|
+
```ruby spec/controllers/spree/api/v3/admin/reviews_controller_spec.rb
|
|
375
|
+
require 'spec_helper'
|
|
229
376
|
|
|
230
|
-
|
|
377
|
+
RSpec.describe Spree::Api::V3::Admin::ReviewsController, type: :controller do
|
|
378
|
+
render_views
|
|
379
|
+
routes { Spree::Core::Engine.routes }
|
|
231
380
|
|
|
232
|
-
|
|
381
|
+
include_context 'API v3 Admin authenticated'
|
|
233
382
|
|
|
234
|
-
|
|
235
|
-
|----------|---------------------|
|
|
236
|
-
| Structural changes (associations, validations) | Decorators (as shown in this tutorial) |
|
|
237
|
-
| React to model changes | [Events subscribers](../core-concepts/events.md) |
|
|
238
|
-
| External service integration | [Webhooks](../core-concepts/webhooks.md) |
|
|
239
|
-
| Replace core services | [Dependencies injection](../customization/dependencies.md) |
|
|
240
|
-
| Add admin UI elements | [Admin Partials](../dashboard/customization/quickstart.md) |
|
|
241
|
-
| Add admin menu items | [Admin Navigation](../dashboard/customization/navigation.md) |
|
|
383
|
+
let!(:review) { create(:review, store: store) }
|
|
242
384
|
|
|
243
|
-
|
|
385
|
+
before { request.headers.merge!(headers) }
|
|
244
386
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
class ProductSyncSubscriber < Spree::Subscriber
|
|
248
|
-
subscribes_to 'product.updated'
|
|
387
|
+
it 'lists reviews' do
|
|
388
|
+
get :index, as: :json
|
|
249
389
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
return unless product
|
|
253
|
-
|
|
254
|
-
ExternalService.sync(product)
|
|
255
|
-
end
|
|
390
|
+
expect(response).to have_http_status(:ok)
|
|
391
|
+
expect(json_response['data'].map { |r| r['id'] }).to include(review.prefixed_id)
|
|
256
392
|
end
|
|
257
393
|
end
|
|
258
394
|
```
|
|
259
395
|
|
|
260
|
-
|
|
396
|
+
Run the suite:
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
bundle exec rspec
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Test the logic you wrote — the approved-only scope, your hook handler, your subscriber — rather than behavior that Rails and Spree already guarantee.
|
|
403
|
+
|
|
404
|
+
## Related documentation
|
|
261
405
|
|
|
406
|
+
- [Customization Quickstart](../customization/quickstart.md) - Choose the right extension point
|
|
407
|
+
- [Customizing the API](../customization/api.md) - Controllers, serializers and permitted attributes
|
|
408
|
+
- [Services & Workflows](../customization/workflows.md) - The full list of workflow hooks
|
|
262
409
|
- [Events](../core-concepts/events.md) - Subscribe to Spree events
|
|
263
|
-
- [
|
|
264
|
-
- [
|
|
265
|
-
- [Customization Quickstart](../customization/quickstart.md) - Choose the right approach
|
|
410
|
+
- [Permissions](../customization/permissions.md) - Register permission scopes
|
|
411
|
+
- [Dashboard plugins](../dashboard/plugins/overview.md) - Admin UI for your extension
|