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,360 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-nested-resources
|
|
3
|
-
description: Use BEFORE configuring parent/child resource relationships, nested routes, or scoped URL generation with resource_url_for(parent:).
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Nested Resources
|
|
7
|
-
|
|
8
|
-
## 🚨 Critical (read first)
|
|
9
|
-
- **Use `pu:res:scaffold` + `pu:res:conn`** for both parent and child. Nested routes are generated from the `belongs_to` + association on the parent — no manual route wiring.
|
|
10
|
-
- **Parent scoping beats entity scoping.** When a parent is present, `default_relation_scope` scopes via the parent, not via `entity_scope`. Don't double-scope in the policy.
|
|
11
|
-
- **Plutonium supports one level of nesting.** Grandparent → parent → child nested routes are NOT supported. Use top-level routes for deeper relationships.
|
|
12
|
-
- **Named custom routes only.** When adding member/collection routes on a nested resource, always pass `as:` — otherwise `resource_url_for` will fail.
|
|
13
|
-
- **Related skills:** `plutonium-entity-scoping` (how parent scoping interacts with entity scoping), `plutonium-policy` (parent scoping in `relation_scope`), `plutonium-controller` (presentation hooks), `plutonium-portal` (route registration).
|
|
14
|
-
|
|
15
|
-
**Always use generators** to create both parent and child resources, then connect them to portals:
|
|
16
|
-
```bash
|
|
17
|
-
rails g pu:res:scaffold Company name:string --dest=main_app
|
|
18
|
-
rails g pu:res:scaffold Property company:belongs_to name:string --dest=main_app
|
|
19
|
-
rails g pu:res:conn Company Property --dest=admin_portal
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Plutonium automatically creates nested routes for `has_many` and `has_one` associations, scopes queries to the parent, and handles URL generation.
|
|
23
|
-
|
|
24
|
-
## How It Works
|
|
25
|
-
|
|
26
|
-
When you register resources with parent-child relationships:
|
|
27
|
-
|
|
28
|
-
```ruby
|
|
29
|
-
# In portal routes
|
|
30
|
-
register_resource ::Company
|
|
31
|
-
register_resource ::Property # has belongs_to :company
|
|
32
|
-
register_resource ::CompanyProfile # has belongs_to :company (has_one on Company)
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Plutonium automatically creates nested routes with a `nested_` prefix:
|
|
36
|
-
- `/companies/:company_id/nested_properties` - Properties scoped to company (has_many)
|
|
37
|
-
- `/companies/:company_id/nested_properties/new` - New property for company
|
|
38
|
-
- `/companies/:company_id/nested_properties/:id` - Property in company context
|
|
39
|
-
- `/companies/:company_id/nested_company_profile` - Singular profile (has_one)
|
|
40
|
-
- `/companies/:company_id/nested_company_profile/new` - New profile for company
|
|
41
|
-
|
|
42
|
-
The `nested_` prefix prevents route conflicts when the same resource is registered both as a top-level and nested resource.
|
|
43
|
-
|
|
44
|
-
## Automatic Behavior
|
|
45
|
-
|
|
46
|
-
When accessing nested routes, Plutonium automatically:
|
|
47
|
-
|
|
48
|
-
1. **Resolves the parent** via `current_parent`
|
|
49
|
-
2. **Scopes queries** to only show records belonging to parent
|
|
50
|
-
3. **Assigns parent** to new records on create
|
|
51
|
-
4. **Hides parent field** in forms (already determined by URL)
|
|
52
|
-
5. **Authorizes parent access** before proceeding
|
|
53
|
-
|
|
54
|
-
## Controller Methods
|
|
55
|
-
|
|
56
|
-
### current_parent
|
|
57
|
-
|
|
58
|
-
Returns the parent record from the URL:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
# URL: /companies/123/properties
|
|
62
|
-
current_parent # => Company.find(123)
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### parent_route_param
|
|
66
|
-
|
|
67
|
-
The URL parameter containing the parent ID:
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
parent_route_param # => :company_id
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
### parent_input_param
|
|
74
|
-
|
|
75
|
-
The association name on the child model:
|
|
76
|
-
|
|
77
|
-
```ruby
|
|
78
|
-
parent_input_param # => :company
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Presentation Hooks
|
|
82
|
-
|
|
83
|
-
Control whether parent field appears in views/forms:
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
class PropertiesController < ResourceController
|
|
87
|
-
private
|
|
88
|
-
|
|
89
|
-
# Show parent field in displays (default: false)
|
|
90
|
-
def present_parent?
|
|
91
|
-
true
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Allow changing parent in forms (default: same as present_parent?)
|
|
95
|
-
def submit_parent?
|
|
96
|
-
false # Parent is set from URL, don't allow changing
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Query Scoping
|
|
102
|
-
|
|
103
|
-
Collections are automatically scoped to the parent via policies. The policy receives `parent` and `parent_association` context:
|
|
104
|
-
|
|
105
|
-
```ruby
|
|
106
|
-
class PropertyPolicy < ResourcePolicy
|
|
107
|
-
# parent: the parent record (e.g., Company instance)
|
|
108
|
-
# parent_association: the association name (e.g., :properties)
|
|
109
|
-
|
|
110
|
-
relation_scope do |relation|
|
|
111
|
-
relation = super(relation) # Applies parent scoping automatically
|
|
112
|
-
relation
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
### How Parent Scoping Works
|
|
118
|
-
|
|
119
|
-
For **has_many** associations, scoping uses the association directly:
|
|
120
|
-
```ruby
|
|
121
|
-
# parent.properties => Company#properties
|
|
122
|
-
parent.send(parent_association)
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
For **has_one** associations, scoping uses a where clause:
|
|
126
|
-
```ruby
|
|
127
|
-
# Property.where(company_id: company.id) with limit
|
|
128
|
-
relation.where(foreign_key => parent.id)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Parent vs Entity Scope
|
|
132
|
-
|
|
133
|
-
When a parent is present, parent scoping takes precedence over entity scoping:
|
|
134
|
-
|
|
135
|
-
```ruby
|
|
136
|
-
# With parent: scopes via parent association
|
|
137
|
-
# Without parent: falls back to entity_scope (multi-tenancy)
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
This prevents double-scoping - the parent was already authorized and entity-scoped during its own authorization.
|
|
141
|
-
|
|
142
|
-
### Custom Association Scope
|
|
143
|
-
|
|
144
|
-
For complex relationships, define a custom scope:
|
|
145
|
-
|
|
146
|
-
```ruby
|
|
147
|
-
class Property < ResourceRecord
|
|
148
|
-
scope :associated_with_organization, ->(org) {
|
|
149
|
-
joins(:company).where(companies: { organization_id: org.id })
|
|
150
|
-
}
|
|
151
|
-
end
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## URL Generation
|
|
155
|
-
|
|
156
|
-
Use `resource_url_for` with the `parent:` option:
|
|
157
|
-
|
|
158
|
-
```ruby
|
|
159
|
-
# Child collection (has_many)
|
|
160
|
-
resource_url_for(Property, parent: company)
|
|
161
|
-
# => /companies/123/nested_properties
|
|
162
|
-
|
|
163
|
-
# Child record
|
|
164
|
-
resource_url_for(property, parent: company)
|
|
165
|
-
# => /companies/123/nested_properties/456
|
|
166
|
-
|
|
167
|
-
# New child form
|
|
168
|
-
resource_url_for(Property, action: :new, parent: company)
|
|
169
|
-
# => /companies/123/nested_properties/new
|
|
170
|
-
|
|
171
|
-
# Edit child
|
|
172
|
-
resource_url_for(property, action: :edit, parent: company)
|
|
173
|
-
# => /companies/123/nested_properties/456/edit
|
|
174
|
-
|
|
175
|
-
# Singular resource (has_one)
|
|
176
|
-
resource_url_for(company_profile, parent: company)
|
|
177
|
-
# => /companies/123/nested_company_profile
|
|
178
|
-
|
|
179
|
-
resource_url_for(CompanyProfile, action: :new, parent: company)
|
|
180
|
-
# => /companies/123/nested_company_profile/new
|
|
181
|
-
|
|
182
|
-
# Interactions (composes with parent — see plutonium-interaction skill)
|
|
183
|
-
resource_url_for(property, parent: company, interaction: :archive)
|
|
184
|
-
# => /companies/123/nested_properties/456/record_actions/archive
|
|
185
|
-
|
|
186
|
-
resource_url_for(Property, parent: company, interaction: :import)
|
|
187
|
-
# => /companies/123/nested_properties/resource_actions/import
|
|
188
|
-
|
|
189
|
-
resource_url_for(Property, parent: company, interaction: :bulk_delete, ids: [1, 2])
|
|
190
|
-
# => /companies/123/nested_properties/bulk_actions/bulk_delete?ids[]=1&ids[]=2
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
### Cross-Package URL Generation
|
|
194
|
-
|
|
195
|
-
Generate URLs for resources in a different package:
|
|
196
|
-
|
|
197
|
-
```ruby
|
|
198
|
-
# From AdminPortal, generate URL to CustomerPortal resource
|
|
199
|
-
resource_url_for(property, parent: company, package: CustomerPortal)
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
## Association Panels
|
|
203
|
-
|
|
204
|
-
On the parent's show page, child resources are displayed via association panels:
|
|
205
|
-
|
|
206
|
-
```ruby
|
|
207
|
-
class CompanyPolicy < ResourcePolicy
|
|
208
|
-
def permitted_associations
|
|
209
|
-
%i[properties contacts] # Shows panels for these
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
The panel loads children via the nested route automatically.
|
|
215
|
-
|
|
216
|
-
## Authorization
|
|
217
|
-
|
|
218
|
-
### Parent Authorization
|
|
219
|
-
|
|
220
|
-
The parent is authorized for `:read?` before `current_parent` returns:
|
|
221
|
-
|
|
222
|
-
```ruby
|
|
223
|
-
def current_parent
|
|
224
|
-
# ... resolution logic ...
|
|
225
|
-
authorize! parent, to: :read?
|
|
226
|
-
parent
|
|
227
|
-
end
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
### Policy Context
|
|
231
|
-
|
|
232
|
-
The parent is passed to child policies as `entity_scope`:
|
|
233
|
-
|
|
234
|
-
```ruby
|
|
235
|
-
class PropertyPolicy < ResourcePolicy
|
|
236
|
-
def create?
|
|
237
|
-
# entity_scope is the parent company
|
|
238
|
-
entity_scope.present? && user.member_of?(entity_scope)
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def read?
|
|
242
|
-
entity_scope.present? && record.company == entity_scope
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
## Parameter Handling
|
|
248
|
-
|
|
249
|
-
Parent is automatically injected into resource params:
|
|
250
|
-
|
|
251
|
-
```ruby
|
|
252
|
-
# When creating a property under /companies/123/properties
|
|
253
|
-
resource_params
|
|
254
|
-
# => { name: "...", company: <Company:123>, company_id: 123 }
|
|
255
|
-
```
|
|
256
|
-
|
|
257
|
-
You don't need to include hidden fields for the parent in forms.
|
|
258
|
-
|
|
259
|
-
## has_one Associations
|
|
260
|
-
|
|
261
|
-
Plutonium supports both `has_many` and `has_one` associations:
|
|
262
|
-
|
|
263
|
-
```ruby
|
|
264
|
-
class Company < ResourceRecord
|
|
265
|
-
has_many :properties # Plural routes
|
|
266
|
-
has_one :company_profile # Singular routes
|
|
267
|
-
end
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
Routes generated:
|
|
271
|
-
- `has_many`: `/companies/:id/nested_properties` (plural, with `:id` param)
|
|
272
|
-
- `has_one`: `/companies/:id/nested_company_profile` (singular, no `:id` param)
|
|
273
|
-
|
|
274
|
-
For has_one associations:
|
|
275
|
-
- Index redirects to show (or new if no record exists)
|
|
276
|
-
- Only one record can exist per parent
|
|
277
|
-
- Forms don't show parent field (determined by URL)
|
|
278
|
-
|
|
279
|
-
## Nesting Limitations
|
|
280
|
-
|
|
281
|
-
Plutonium supports **one level of nesting**:
|
|
282
|
-
|
|
283
|
-
- ✅ `/companies/:company_id/nested_properties` (parent → child)
|
|
284
|
-
- ❌ `/companies/:company_id/nested_properties/:property_id/nested_units` (grandparent → parent → child)
|
|
285
|
-
|
|
286
|
-
## Common Patterns
|
|
287
|
-
|
|
288
|
-
### Scoped Uniqueness
|
|
289
|
-
|
|
290
|
-
Validate uniqueness within parent:
|
|
291
|
-
|
|
292
|
-
```ruby
|
|
293
|
-
class Property < ResourceRecord
|
|
294
|
-
belongs_to :company
|
|
295
|
-
validates :code, uniqueness: { scope: :company_id }
|
|
296
|
-
end
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### Conditional Parent Display
|
|
300
|
-
|
|
301
|
-
Show parent only in certain contexts:
|
|
302
|
-
|
|
303
|
-
```ruby
|
|
304
|
-
class PropertiesController < ResourceController
|
|
305
|
-
private
|
|
306
|
-
|
|
307
|
-
def present_parent?
|
|
308
|
-
# Show parent when accessed standalone, hide when nested
|
|
309
|
-
current_parent.nil?
|
|
310
|
-
end
|
|
311
|
-
end
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
### Custom Parent Resolution
|
|
315
|
-
|
|
316
|
-
Override parent lookup:
|
|
317
|
-
|
|
318
|
-
```ruby
|
|
319
|
-
class PropertiesController < ResourceController
|
|
320
|
-
private
|
|
321
|
-
|
|
322
|
-
def current_parent
|
|
323
|
-
@current_parent ||= Company.friendly.find(params[:company_id])
|
|
324
|
-
end
|
|
325
|
-
end
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### Breadcrumbs
|
|
329
|
-
|
|
330
|
-
Breadcrumbs automatically include the parent:
|
|
331
|
-
|
|
332
|
-
```
|
|
333
|
-
Companies > Acme Corp > Properties > Property #123
|
|
334
|
-
```
|
|
335
|
-
|
|
336
|
-
## Route Registration with Custom Routes
|
|
337
|
-
|
|
338
|
-
Add custom member/collection routes to nested resources:
|
|
339
|
-
|
|
340
|
-
```ruby
|
|
341
|
-
register_resource ::Property do
|
|
342
|
-
member do
|
|
343
|
-
get :analytics, as: :analytics
|
|
344
|
-
post :archive, as: :archive
|
|
345
|
-
end
|
|
346
|
-
end
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
**Important:** Always use the `as:` option to name custom routes. This ensures `resource_url_for` can generate correct URLs for nested resources. Without named routes, URL generation will fail.
|
|
350
|
-
|
|
351
|
-
Generates nested routes:
|
|
352
|
-
- `/companies/:company_id/nested_properties/:id/analytics`
|
|
353
|
-
- `/companies/:company_id/nested_properties/:id/archive`
|
|
354
|
-
|
|
355
|
-
## Related Skills
|
|
356
|
-
|
|
357
|
-
- `plutonium-portal` - Route registration
|
|
358
|
-
- `plutonium-policy` - Authorization and scoping
|
|
359
|
-
- `plutonium-controller` - Presentation hooks
|
|
360
|
-
- `plutonium-model` - associated_with scope
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-package
|
|
3
|
-
description: Use BEFORE creating a feature package or portal package via pu:pkg:package / pu:pkg:portal, or organizing a Plutonium app into modular engines.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Plutonium Packages
|
|
7
|
-
|
|
8
|
-
## 🚨 Critical (read first)
|
|
9
|
-
- **Use the generators.** `pu:pkg:package` for feature packages, `pu:pkg:portal` for portal packages — never hand-write engine files or directory structures.
|
|
10
|
-
- **Feature vs portal is a hard split.** Feature packages hold models/policies/definitions/interactions; portal packages hold controllers/views/routes/auth. Don't mix.
|
|
11
|
-
- **Package classes are auto-namespaced** (`packages/blogging/app/models/blogging/post.rb` → `Blogging::Post`). Don't fight the namespacing.
|
|
12
|
-
- **Cross-package resource references** use the full namespace: `rails g pu:res:conn Blogging::Post --dest=admin_portal`.
|
|
13
|
-
- **Related skills:** `plutonium-portal` (portal-specific features), `plutonium-create-resource` (creating resources in packages), `plutonium-installation` (package loading).
|
|
14
|
-
|
|
15
|
-
Packages are specialized Rails engines for organizing code. There are two types:
|
|
16
|
-
|
|
17
|
-
| Type | Purpose | Generator |
|
|
18
|
-
|------|---------|-----------|
|
|
19
|
-
| **Feature** | Business logic (models, policies, interactions) | `rails g pu:pkg:package NAME` |
|
|
20
|
-
| **Portal** | Web interface (controllers, views, auth) | `rails g pu:pkg:portal NAME` |
|
|
21
|
-
|
|
22
|
-
## Feature Packages
|
|
23
|
-
|
|
24
|
-
Contain domain logic without web interface:
|
|
25
|
-
|
|
26
|
-
```bash
|
|
27
|
-
rails g pu:pkg:package blogging
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Structure
|
|
31
|
-
|
|
32
|
-
```
|
|
33
|
-
packages/blogging/
|
|
34
|
-
├── app/
|
|
35
|
-
│ ├── models/blogging/
|
|
36
|
-
│ │ ├── post.rb
|
|
37
|
-
│ │ └── comment.rb
|
|
38
|
-
│ ├── definitions/blogging/
|
|
39
|
-
│ │ ├── post_definition.rb
|
|
40
|
-
│ │ └── comment_definition.rb
|
|
41
|
-
│ ├── policies/blogging/
|
|
42
|
-
│ │ ├── post_policy.rb
|
|
43
|
-
│ │ └── comment_policy.rb
|
|
44
|
-
│ └── interactions/blogging/
|
|
45
|
-
│ └── publish_post_interaction.rb
|
|
46
|
-
├── db/migrate/
|
|
47
|
-
└── lib/
|
|
48
|
-
└── engine.rb
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Engine
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
module Blogging
|
|
55
|
-
class Engine < Rails::Engine
|
|
56
|
-
include Plutonium::Package::Engine
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
### Namespacing
|
|
62
|
-
|
|
63
|
-
All classes are auto-namespaced:
|
|
64
|
-
- `app/models/blogging/post.rb` → `Blogging::Post`
|
|
65
|
-
- `app/policies/blogging/post_policy.rb` → `Blogging::PostPolicy`
|
|
66
|
-
|
|
67
|
-
## Portal Packages
|
|
68
|
-
|
|
69
|
-
Provide web interfaces for specific user types:
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
rails g pu:pkg:portal admin
|
|
73
|
-
rails g pu:pkg:portal dashboard
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Structure
|
|
77
|
-
|
|
78
|
-
```
|
|
79
|
-
packages/admin_portal/
|
|
80
|
-
├── app/
|
|
81
|
-
│ ├── controllers/admin_portal/
|
|
82
|
-
│ │ ├── concerns/controller.rb
|
|
83
|
-
│ │ ├── dashboard_controller.rb
|
|
84
|
-
│ │ ├── plutonium_controller.rb
|
|
85
|
-
│ │ └── resource_controller.rb
|
|
86
|
-
│ ├── definitions/admin_portal/ # Portal-specific overrides
|
|
87
|
-
│ ├── policies/admin_portal/ # Portal-specific overrides
|
|
88
|
-
│ └── views/
|
|
89
|
-
│ └── layouts/admin_portal.html.erb
|
|
90
|
-
├── config/
|
|
91
|
-
│ └── routes.rb
|
|
92
|
-
└── lib/
|
|
93
|
-
└── engine.rb
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
### Engine
|
|
97
|
-
|
|
98
|
-
```ruby
|
|
99
|
-
module AdminPortal
|
|
100
|
-
class Engine < Rails::Engine
|
|
101
|
-
include Plutonium::Portal::Engine
|
|
102
|
-
|
|
103
|
-
config.after_initialize do
|
|
104
|
-
# Optional: multi-tenancy
|
|
105
|
-
scope_to_entity Organization, strategy: :path
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
See `plutonium-portal` skill for portal-specific features.
|
|
112
|
-
|
|
113
|
-
## Package Loading
|
|
114
|
-
|
|
115
|
-
Packages are loaded via `config/packages.rb`:
|
|
116
|
-
|
|
117
|
-
```ruby
|
|
118
|
-
# config/packages.rb (generated during install)
|
|
119
|
-
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) do |package|
|
|
120
|
-
load package
|
|
121
|
-
end
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
This is required in `config/application.rb`.
|
|
125
|
-
|
|
126
|
-
## Creating Resources in Packages
|
|
127
|
-
|
|
128
|
-
```bash
|
|
129
|
-
# In main app
|
|
130
|
-
rails g pu:res:scaffold Post title:string --dest=main_app
|
|
131
|
-
|
|
132
|
-
# In feature package
|
|
133
|
-
rails g pu:res:scaffold Blogging::Post title:string --dest=blogging
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
## Connecting Resources to Portals
|
|
137
|
-
|
|
138
|
-
Resources must be connected to portals to be accessible:
|
|
139
|
-
|
|
140
|
-
```bash
|
|
141
|
-
rails g pu:res:conn Post --dest=admin_portal
|
|
142
|
-
rails g pu:res:conn Blogging::Post --dest=admin_portal
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
This creates:
|
|
146
|
-
- Portal-specific controller
|
|
147
|
-
- Portal-specific policy (optional)
|
|
148
|
-
- Portal-specific definition (optional)
|
|
149
|
-
- Route registration
|
|
150
|
-
|
|
151
|
-
## When to Use Each Type
|
|
152
|
-
|
|
153
|
-
### Feature Packages
|
|
154
|
-
|
|
155
|
-
Use for:
|
|
156
|
-
- Domain-specific models and logic
|
|
157
|
-
- Reusable business functionality
|
|
158
|
-
- Shared code across portals
|
|
159
|
-
|
|
160
|
-
Examples: `blogging`, `billing`, `inventory`, `user_management`
|
|
161
|
-
|
|
162
|
-
### Portal Packages
|
|
163
|
-
|
|
164
|
-
Use for:
|
|
165
|
-
- User-facing interfaces
|
|
166
|
-
- Role-specific access (admin, customer, public)
|
|
167
|
-
- Different authentication requirements
|
|
168
|
-
|
|
169
|
-
Examples: `admin_portal`, `dashboard_portal`, `public_portal`, `api_portal`
|
|
170
|
-
|
|
171
|
-
## Typical Architecture
|
|
172
|
-
|
|
173
|
-
```
|
|
174
|
-
packages/
|
|
175
|
-
├── blogging/ # Feature: blog functionality
|
|
176
|
-
│ └── models, definitions, policies
|
|
177
|
-
├── billing/ # Feature: payment/invoicing
|
|
178
|
-
│ └── models, definitions, policies
|
|
179
|
-
├── admin_portal/ # Portal: admin interface
|
|
180
|
-
│ └── controllers, views, routes
|
|
181
|
-
└── dashboard_portal/ # Portal: user dashboard
|
|
182
|
-
└── controllers, views, routes
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
## Migration Integration
|
|
186
|
-
|
|
187
|
-
Package migrations are automatically integrated:
|
|
188
|
-
|
|
189
|
-
```bash
|
|
190
|
-
rails db:migrate # Runs migrations from all packages
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
## Related Skills
|
|
194
|
-
|
|
195
|
-
- `plutonium-portal` - Portal-specific features (auth, entity scoping, routes)
|
|
196
|
-
- `plutonium` - Resource architecture overview
|
|
197
|
-
- `plutonium-portal` - Connecting resources to portals
|
|
198
|
-
- `plutonium-create-resource` - Creating resources
|