plutonium 0.50.0 → 0.51.0
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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium/SKILL.md +85 -102
- data/.claude/skills/plutonium-app/SKILL.md +572 -0
- data/.claude/skills/plutonium-auth/SKILL.md +163 -300
- data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
- data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
- data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
- data/.claude/skills/plutonium-testing/SKILL.md +6 -5
- data/.claude/skills/plutonium-ui/SKILL.md +900 -0
- data/CHANGELOG.md +27 -2
- data/Rakefile +2 -1
- data/app/assets/plutonium.css +1 -11
- data/app/assets/plutonium.js +1009 -1214
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +52 -51
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +37 -27
- data/docs/getting-started/index.md +22 -29
- data/docs/getting-started/installation.md +37 -80
- data/docs/getting-started/tutorial/index.md +4 -5
- data/docs/guides/adding-resources.md +66 -377
- data/docs/guides/authentication.md +94 -463
- data/docs/guides/authorization.md +124 -370
- data/docs/guides/creating-packages.md +94 -296
- data/docs/guides/custom-actions.md +121 -441
- data/docs/guides/index.md +22 -42
- data/docs/guides/multi-tenancy.md +116 -187
- data/docs/guides/nested-resources.md +103 -431
- data/docs/guides/search-filtering.md +123 -240
- data/docs/guides/testing.md +5 -4
- data/docs/guides/theming.md +157 -407
- data/docs/guides/troubleshooting.md +5 -3
- data/docs/guides/user-invites.md +106 -425
- data/docs/guides/user-profile.md +76 -243
- data/docs/index.md +1 -1
- data/docs/reference/app/generators.md +517 -0
- data/docs/reference/app/index.md +158 -0
- data/docs/reference/app/packages.md +146 -0
- data/docs/reference/app/portals.md +377 -0
- data/docs/reference/auth/accounts.md +230 -0
- data/docs/reference/auth/index.md +88 -0
- data/docs/reference/auth/profile.md +185 -0
- data/docs/reference/behavior/controllers.md +395 -0
- data/docs/reference/behavior/index.md +22 -0
- data/docs/reference/behavior/interactions.md +341 -0
- data/docs/reference/behavior/policies.md +417 -0
- data/docs/reference/index.md +56 -49
- data/docs/reference/resource/actions.md +423 -0
- data/docs/reference/resource/definition.md +508 -0
- data/docs/reference/resource/index.md +50 -0
- data/docs/reference/resource/model.md +348 -0
- data/docs/reference/resource/query.md +305 -0
- data/docs/reference/tenancy/entity-scoping.md +361 -0
- data/docs/reference/tenancy/index.md +36 -0
- data/docs/reference/tenancy/invites.md +393 -0
- data/docs/reference/tenancy/nested-resources.md +267 -0
- data/docs/reference/testing/index.md +287 -0
- data/docs/reference/ui/assets.md +400 -0
- data/docs/reference/ui/components.md +165 -0
- data/docs/reference/ui/displays.md +104 -0
- data/docs/reference/ui/forms.md +284 -0
- data/docs/reference/ui/index.md +30 -0
- data/docs/reference/ui/layouts.md +106 -0
- data/docs/reference/ui/pages.md +189 -0
- data/docs/reference/ui/tables.md +117 -0
- data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
- data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
- data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/update/update_generator.rb +0 -20
- data/lib/generators/pu/invites/install_generator.rb +1 -0
- data/lib/plutonium/definition/base.rb +1 -1
- data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
- data/lib/plutonium/helpers/turbo_helper.rb +11 -0
- data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
- data/lib/plutonium/resource/controller.rb +1 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
- data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
- data/lib/plutonium/resource/policy.rb +7 -0
- data/lib/plutonium/routing/mapper_extensions.rb +15 -0
- data/lib/plutonium/ui/component/methods.rb +4 -0
- data/lib/plutonium/ui/form/base.rb +6 -2
- data/lib/plutonium/ui/form/components/json.rb +58 -0
- data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
- data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
- data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
- data/lib/plutonium/ui/form/resource.rb +0 -4
- data/lib/plutonium/ui/grid/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +0 -7
- data/lib/plutonium/ui/page/index.rb +4 -4
- data/lib/plutonium/ui/table/resource.rb +1 -1
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium.rb +8 -0
- data/lib/tasks/release.rake +15 -1
- data/package.json +10 -10
- data/src/css/slim_select.css +4 -0
- data/src/js/controllers/slim_select_controller.js +61 -0
- data/src/js/turbo/turbo_actions.js +33 -0
- data/yarn.lock +553 -543
- metadata +44 -33
- data/.claude/skills/plutonium-assets/SKILL.md +0 -512
- data/.claude/skills/plutonium-controller/SKILL.md +0 -396
- data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
- data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
- data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
- data/.claude/skills/plutonium-forms/SKILL.md +0 -465
- data/.claude/skills/plutonium-installation/SKILL.md +0 -331
- data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
- data/.claude/skills/plutonium-invites/SKILL.md +0 -408
- data/.claude/skills/plutonium-model/SKILL.md +0 -440
- data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
- data/.claude/skills/plutonium-package/SKILL.md +0 -198
- data/.claude/skills/plutonium-policy/SKILL.md +0 -456
- data/.claude/skills/plutonium-portal/SKILL.md +0 -410
- data/.claude/skills/plutonium-views/SKILL.md +0 -651
- data/docs/reference/assets/index.md +0 -496
- data/docs/reference/controller/index.md +0 -412
- data/docs/reference/definition/actions.md +0 -462
- data/docs/reference/definition/fields.md +0 -383
- data/docs/reference/definition/index.md +0 -326
- data/docs/reference/definition/query.md +0 -351
- data/docs/reference/generators/index.md +0 -648
- data/docs/reference/interaction/index.md +0 -449
- data/docs/reference/model/features.md +0 -248
- data/docs/reference/model/index.md +0 -218
- data/docs/reference/policy/index.md +0 -456
- data/docs/reference/portal/index.md +0 -379
- data/docs/reference/views/forms.md +0 -411
- data/docs/reference/views/index.md +0 -544
|
@@ -1,381 +1,179 @@
|
|
|
1
1
|
# Creating Packages
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Organize your app into feature and portal packages.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Goal
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|------|---------|-----------|
|
|
9
|
-
| **Feature Package** | Business logic (models, definitions, policies) | `rails g pu:pkg:package NAME` |
|
|
10
|
-
| **Portal Package** | Web interface (routes, auth, UI) | `rails g pu:pkg:portal NAME` |
|
|
7
|
+
Domain code (models, policies, definitions, interactions) lives in **feature packages**. Web interfaces (controllers, views, routes, auth) live in **portal packages**. Both are Rails engines with Plutonium conventions on top.
|
|
11
8
|
|
|
12
|
-
##
|
|
9
|
+
## Two types
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
| Type | Purpose | Generator | Examples |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **Feature** | Business logic | `pu:pkg:package NAME` | `blogging`, `billing`, `inventory` |
|
|
14
|
+
| **Portal** | Web interface | `pu:pkg:portal NAME` | `admin_portal`, `customer_portal`, `public_portal` |
|
|
15
|
+
|
|
16
|
+
🚨 Don't mix the two. Feature packages have NO routes, views, or controllers. Portal packages have NO models or interactions.
|
|
17
|
+
|
|
18
|
+
## Feature package
|
|
19
|
+
|
|
20
|
+
### 1. Generate
|
|
15
21
|
|
|
16
22
|
```bash
|
|
17
23
|
rails g pu:pkg:package blogging
|
|
18
24
|
```
|
|
19
25
|
|
|
20
|
-
###
|
|
26
|
+
### 2. Structure
|
|
21
27
|
|
|
22
28
|
```
|
|
23
29
|
packages/blogging/
|
|
24
30
|
├── app/
|
|
25
|
-
│ ├──
|
|
26
|
-
│
|
|
27
|
-
│ ├──
|
|
28
|
-
│
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
│ ├── models/blogging/
|
|
32
|
-
│ │ └── resource_record.rb
|
|
33
|
-
│ ├── policies/blogging/
|
|
34
|
-
│ │ └── resource_policy.rb
|
|
35
|
-
│ └── views/blogging/
|
|
36
|
-
└── lib/
|
|
37
|
-
└── engine.rb
|
|
31
|
+
│ ├── models/blogging/ # Blogging::Post
|
|
32
|
+
│ ├── definitions/blogging/ # Blogging::PostDefinition
|
|
33
|
+
│ ├── policies/blogging/ # Blogging::PostPolicy
|
|
34
|
+
│ └── interactions/blogging/ # Blogging::PublishPostInteraction
|
|
35
|
+
├── db/migrate/
|
|
36
|
+
└── lib/engine.rb
|
|
38
37
|
```
|
|
39
38
|
|
|
40
|
-
###
|
|
41
|
-
|
|
42
|
-
```ruby
|
|
43
|
-
# packages/blogging/lib/engine.rb
|
|
44
|
-
module Blogging
|
|
45
|
-
class Engine < Rails::Engine
|
|
46
|
-
include Plutonium::Package::Engine
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Namespacing
|
|
52
|
-
|
|
53
|
-
All classes are auto-namespaced:
|
|
54
|
-
- `app/models/blogging/post.rb` → `Blogging::Post`
|
|
55
|
-
- `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
|
|
56
|
-
|
|
57
|
-
## Creating a Portal Package
|
|
58
|
-
|
|
59
|
-
### Using the Generator
|
|
39
|
+
### 3. Create resources inside it
|
|
60
40
|
|
|
61
41
|
```bash
|
|
62
|
-
rails g pu:
|
|
42
|
+
rails g pu:res:scaffold Blogging::Post title:string --dest=blogging
|
|
43
|
+
rails db:migrate
|
|
63
44
|
```
|
|
64
45
|
|
|
65
|
-
###
|
|
66
|
-
|
|
67
|
-
| Option | Description |
|
|
68
|
-
|--------|-------------|
|
|
69
|
-
| `--auth=NAME` | Rodauth account to authenticate with |
|
|
70
|
-
| `--public` | Grant public access (no authentication) |
|
|
71
|
-
| `--byo` | Bring your own authentication |
|
|
46
|
+
### 4. Expose it via a portal
|
|
72
47
|
|
|
73
48
|
```bash
|
|
74
|
-
|
|
75
|
-
rails g pu:pkg:portal admin --auth=admin
|
|
76
|
-
rails g pu:pkg:portal api --public
|
|
77
|
-
rails g pu:pkg:portal custom --byo
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Without flags, the generator prompts interactively.
|
|
81
|
-
|
|
82
|
-
### Generated Structure
|
|
83
|
-
|
|
84
|
-
```
|
|
85
|
-
packages/admin_portal/
|
|
86
|
-
├── app/
|
|
87
|
-
│ ├── controllers/admin_portal/
|
|
88
|
-
│ │ ├── concerns/controller.rb
|
|
89
|
-
│ │ ├── dashboard_controller.rb
|
|
90
|
-
│ │ ├── plutonium_controller.rb
|
|
91
|
-
│ │ └── resource_controller.rb
|
|
92
|
-
│ ├── definitions/admin_portal/
|
|
93
|
-
│ │ └── resource_definition.rb
|
|
94
|
-
│ ├── policies/admin_portal/
|
|
95
|
-
│ │ └── resource_policy.rb
|
|
96
|
-
│ └── views/admin_portal/
|
|
97
|
-
│ └── dashboard/index.html.erb
|
|
98
|
-
├── config/
|
|
99
|
-
│ └── routes.rb
|
|
100
|
-
└── lib/
|
|
101
|
-
└── engine.rb
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Portal Engine
|
|
105
|
-
|
|
106
|
-
```ruby
|
|
107
|
-
# packages/admin_portal/lib/engine.rb
|
|
108
|
-
module AdminPortal
|
|
109
|
-
class Engine < Rails::Engine
|
|
110
|
-
include Plutonium::Portal::Engine
|
|
111
|
-
|
|
112
|
-
config.after_initialize do
|
|
113
|
-
# Multi-tenancy (optional)
|
|
114
|
-
scope_to_entity Organization, strategy: :path
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
end
|
|
49
|
+
rails g pu:res:conn Blogging::Post --dest=admin_portal
|
|
118
50
|
```
|
|
119
51
|
|
|
120
|
-
|
|
52
|
+
## Portal package
|
|
121
53
|
|
|
122
|
-
|
|
54
|
+
### 1. Generate
|
|
123
55
|
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
module AdminPortal
|
|
127
|
-
module Concerns
|
|
128
|
-
module Controller
|
|
129
|
-
extend ActiveSupport::Concern
|
|
130
|
-
include Plutonium::Portal::Controller
|
|
131
|
-
include Plutonium::Auth::Rodauth(:admin)
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
For public access:
|
|
138
|
-
|
|
139
|
-
```ruby
|
|
140
|
-
include Plutonium::Auth::Public
|
|
56
|
+
```bash
|
|
57
|
+
rails g pu:pkg:portal admin --auth=user
|
|
141
58
|
```
|
|
142
59
|
|
|
143
|
-
|
|
60
|
+
Options:
|
|
144
61
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
62
|
+
- `--auth=NAME` — Rodauth account to authenticate with.
|
|
63
|
+
- `--public` — public access, no auth.
|
|
64
|
+
- `--byo` — bring your own auth.
|
|
65
|
+
- `--scope=CLASS` — entity class for multi-tenancy.
|
|
149
66
|
|
|
150
|
-
|
|
151
|
-
# Your authentication logic
|
|
152
|
-
@current_user ||= User.find_by(api_key: request.headers["X-API-Key"])
|
|
153
|
-
end
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Portal Routes
|
|
157
|
-
|
|
158
|
-
The portal generator creates routes and auto-mounts to the main app:
|
|
67
|
+
### 2. Mount it
|
|
159
68
|
|
|
160
69
|
```ruby
|
|
161
|
-
#
|
|
162
|
-
AdminPortal::Engine.routes.draw do
|
|
163
|
-
root to: "dashboard#index"
|
|
164
|
-
|
|
165
|
-
# Register resources here
|
|
166
|
-
register_resource ::Post
|
|
167
|
-
register_resource Blogging::Comment
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# Also adds to main app routes:
|
|
171
|
-
# config/routes.rb (auto-generated)
|
|
70
|
+
# config/routes.rb
|
|
172
71
|
Rails.application.routes.draw do
|
|
173
|
-
constraints Rodauth::Rails.authenticate(:
|
|
72
|
+
constraints Rodauth::Rails.authenticate(:user) do
|
|
174
73
|
mount AdminPortal::Engine, at: "/admin"
|
|
175
74
|
end
|
|
176
75
|
end
|
|
177
76
|
```
|
|
178
77
|
|
|
179
|
-
###
|
|
180
|
-
|
|
181
|
-
Add member or collection routes with a block:
|
|
182
|
-
|
|
183
|
-
```ruby
|
|
184
|
-
register_resource ::Post do
|
|
185
|
-
member do
|
|
186
|
-
get :preview
|
|
187
|
-
post :publish
|
|
188
|
-
end
|
|
189
|
-
collection do
|
|
190
|
-
get :archived
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## Package Loading
|
|
196
|
-
|
|
197
|
-
Packages are loaded via `config/packages.rb` (generated during install):
|
|
198
|
-
|
|
199
|
-
```ruby
|
|
200
|
-
# config/packages.rb
|
|
201
|
-
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
|
202
|
-
load package
|
|
203
|
-
end
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
This is automatically required in `config/application.rb`.
|
|
207
|
-
|
|
208
|
-
## Adding Resources to Packages
|
|
78
|
+
### 3. Connect resources
|
|
209
79
|
|
|
210
80
|
```bash
|
|
211
|
-
|
|
212
|
-
rails g pu:res:scaffold Post title:string --dest=main_app
|
|
213
|
-
|
|
214
|
-
# Add to a feature package
|
|
215
|
-
rails g pu:res:scaffold Post title:string --dest=blogging
|
|
81
|
+
rails g pu:res:conn Post Blogging::Post --dest=admin_portal
|
|
216
82
|
```
|
|
217
83
|
|
|
218
|
-
|
|
84
|
+
See [Reference › App › Portals](/reference/app/portals) for the full portal surface.
|
|
219
85
|
|
|
220
|
-
|
|
221
|
-
# packages/blogging/app/models/blogging/post.rb
|
|
222
|
-
module Blogging
|
|
223
|
-
class Post < Blogging::ResourceRecord
|
|
224
|
-
# Model code
|
|
225
|
-
end
|
|
226
|
-
end
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
## Connecting Resources to Portals
|
|
86
|
+
## Auto-namespacing
|
|
230
87
|
|
|
231
|
-
|
|
88
|
+
Every file under `app/<kind>/blogging/` resolves to `Blogging::*`:
|
|
232
89
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
rails g pu:res:conn Post --dest=admin_portal
|
|
236
|
-
|
|
237
|
-
# Connect namespaced resource
|
|
238
|
-
rails g pu:res:conn Blogging::Post --dest=admin_portal
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Entity Scoping (Multi-tenancy)
|
|
242
|
-
|
|
243
|
-
Automatically scope all data to a parent entity:
|
|
90
|
+
- `app/models/blogging/post.rb` → `Blogging::Post`
|
|
91
|
+
- `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
|
|
244
92
|
|
|
245
|
-
|
|
93
|
+
Each feature package gets base classes — `Blogging::ApplicationRecord`, `Blogging::ResourceRecord`, `Blogging::ResourcePolicy`, `Blogging::ResourceDefinition`, `Blogging::ResourceInteraction` — that inherit from the main app's.
|
|
246
94
|
|
|
247
|
-
|
|
95
|
+
## Cross-package references
|
|
248
96
|
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
config.after_initialize do
|
|
252
|
-
scope_to_entity Organization, strategy: :path
|
|
253
|
-
end
|
|
97
|
+
```bash
|
|
98
|
+
rails g pu:res:scaffold Comment user:belongs_to blogging/post:belongs_to body:text --dest=comments
|
|
254
99
|
```
|
|
255
100
|
|
|
256
|
-
|
|
101
|
+
The `blogging/post` syntax expands to `Blogging::Post`.
|
|
257
102
|
|
|
258
|
-
|
|
103
|
+
## When to use which
|
|
259
104
|
|
|
260
|
-
|
|
105
|
+
### Feature package
|
|
261
106
|
|
|
262
|
-
|
|
263
|
-
config.after_initialize do
|
|
264
|
-
scope_to_entity Organization, strategy: :current_organization
|
|
265
|
-
end
|
|
107
|
+
When the code:
|
|
266
108
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
end
|
|
271
|
-
```
|
|
109
|
+
- Could be reused across multiple portals (admin and customer both edit `Blogging::Post`).
|
|
110
|
+
- Has no inherent UI / auth.
|
|
111
|
+
- You want isolated from other domains (`billing` shouldn't depend on `blogging`).
|
|
272
112
|
|
|
273
|
-
|
|
113
|
+
### Portal package
|
|
274
114
|
|
|
275
|
-
|
|
276
|
-
Each feature package should handle one domain:
|
|
277
|
-
- `blogging` - Posts, comments, categories
|
|
278
|
-
- `inventory` - Products, stock, warehouses
|
|
279
|
-
- `billing` - Invoices, payments, subscriptions
|
|
115
|
+
When the code:
|
|
280
116
|
|
|
281
|
-
|
|
282
|
-
-
|
|
283
|
-
-
|
|
117
|
+
- Has a specific auth flow (admin vs customer vs public).
|
|
118
|
+
- Renders different views of the same underlying resources.
|
|
119
|
+
- Needs different policies / definitions per audience.
|
|
284
120
|
|
|
285
|
-
###
|
|
286
|
-
Limit dependencies between feature packages. If two packages are tightly coupled, consider merging them.
|
|
121
|
+
### When NOT to make a package
|
|
287
122
|
|
|
288
|
-
|
|
289
|
-
Put UI customizations in portal packages, not feature packages:
|
|
123
|
+
For an app that doesn't need cross-portal sharing, just put resources in `--dest=main_app`. Packages add organization, not power.
|
|
290
124
|
|
|
291
|
-
|
|
292
|
-
# Good: Portal-specific definition
|
|
293
|
-
# packages/admin_portal/app/definitions/admin_portal/post_definition.rb
|
|
294
|
-
|
|
295
|
-
# Bad: Feature package with portal-specific code
|
|
296
|
-
# packages/blogging/app/definitions/blogging/admin_post_definition.rb
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
## Multiple Portals Pattern
|
|
300
|
-
|
|
301
|
-
Common pattern for different user types:
|
|
125
|
+
## Typical architecture
|
|
302
126
|
|
|
303
127
|
```
|
|
304
128
|
packages/
|
|
305
|
-
├── blogging/
|
|
306
|
-
├── billing/
|
|
307
|
-
├── admin_portal/
|
|
308
|
-
|
|
309
|
-
└── public_portal/ # Portal: public read-only
|
|
129
|
+
├── blogging/ # Feature: blog functionality
|
|
130
|
+
├── billing/ # Feature: payments/invoicing
|
|
131
|
+
├── admin_portal/ # Portal: admin interface
|
|
132
|
+
└── customer_portal/ # Portal: customer dashboard
|
|
310
133
|
```
|
|
311
134
|
|
|
312
|
-
|
|
313
|
-
- Have different authentication
|
|
314
|
-
- Show different fields
|
|
315
|
-
- Allow different actions
|
|
316
|
-
- Use different layouts
|
|
135
|
+
The portals expose the features. A single feature can be exposed by multiple portals — usually with different policies and definitions per portal.
|
|
317
136
|
|
|
318
|
-
##
|
|
137
|
+
## Package loading
|
|
319
138
|
|
|
320
|
-
|
|
139
|
+
Generated by `pu:core:install`:
|
|
321
140
|
|
|
322
141
|
```ruby
|
|
323
|
-
# packages
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
scope :my_posts, -> { where(user: current_user) }
|
|
142
|
+
# config/packages.rb
|
|
143
|
+
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
|
144
|
+
load package
|
|
327
145
|
end
|
|
328
146
|
```
|
|
329
147
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
```ruby
|
|
333
|
-
# packages/admin_portal/app/policies/admin_portal/post_policy.rb
|
|
334
|
-
class AdminPortal::PostPolicy < ::PostPolicy
|
|
335
|
-
include AdminPortal::ResourcePolicy
|
|
336
|
-
|
|
337
|
-
def destroy?
|
|
338
|
-
true # Admins can delete
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
def permitted_attributes_for_create
|
|
342
|
-
%i[title content featured internal_notes] # More fields
|
|
343
|
-
end
|
|
344
|
-
end
|
|
345
|
-
```
|
|
148
|
+
Loaded from `config/application.rb`. Migrations from all packages are picked up by `rails db:migrate` automatically.
|
|
346
149
|
|
|
347
|
-
|
|
150
|
+
## Per-portal overrides
|
|
348
151
|
|
|
349
152
|
```ruby
|
|
350
|
-
#
|
|
351
|
-
class AdminPortal::
|
|
352
|
-
|
|
153
|
+
# Definition — different fields per portal
|
|
154
|
+
class AdminPortal::PostDefinition < ::PostDefinition
|
|
155
|
+
input :internal_notes, as: :text # admins see this; customers don't
|
|
156
|
+
scope :pending_review
|
|
157
|
+
end
|
|
353
158
|
|
|
354
|
-
|
|
159
|
+
# Policy — different rules per portal
|
|
160
|
+
class AdminPortal::PostPolicy < ::PostPolicy
|
|
161
|
+
include AdminPortal::ResourcePolicy
|
|
355
162
|
|
|
356
|
-
def
|
|
357
|
-
|
|
358
|
-
end
|
|
163
|
+
def destroy? = true
|
|
164
|
+
def permitted_attributes_for_create = %i[title content featured internal_notes]
|
|
359
165
|
end
|
|
360
166
|
```
|
|
361
167
|
|
|
362
|
-
##
|
|
363
|
-
|
|
364
|
-
Portal controllers inherit from the feature package's controller if one exists (and include the portal's `Concerns::Controller`). If no feature package controller exists, they inherit from the portal's `ResourceController`.
|
|
168
|
+
## Common issues
|
|
365
169
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
include AdminPortal::Concerns::Controller
|
|
370
|
-
end
|
|
371
|
-
|
|
372
|
-
# Without feature package controller:
|
|
373
|
-
class AdminPortal::PostsController < AdminPortal::ResourceController
|
|
374
|
-
end
|
|
375
|
-
```
|
|
170
|
+
- **Class not loading** — namespace must match the directory: `app/models/blogging/post.rb` MUST be `Blogging::Post`.
|
|
171
|
+
- **Migration not running** — package migrations are auto-included. If they aren't running, check `config/packages.rb` is loaded from `application.rb`.
|
|
172
|
+
- **Cross-package association fails** — use `blogging/post:belongs_to` in `pu:res:scaffold`, OR manually set `class_name: "Blogging::Post"` on the `belongs_to`.
|
|
376
173
|
|
|
377
174
|
## Related
|
|
378
175
|
|
|
379
|
-
- [
|
|
380
|
-
- [
|
|
381
|
-
- [
|
|
176
|
+
- [Reference › App › Packages](/reference/app/packages) — full package surface
|
|
177
|
+
- [Reference › App › Portals](/reference/app/portals) — portal-specific configuration
|
|
178
|
+
- [Adding resources](./adding-resources) — `pu:res:scaffold` and `pu:res:conn`
|
|
179
|
+
- [Authentication](./authentication) — portal auth setup
|