plutonium 0.44.0 → 0.45.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 +88 -11
- data/.claude/skills/plutonium-assets/SKILL.md +1 -1
- data/.claude/skills/plutonium-controller/SKILL.md +6 -2
- data/.claude/skills/plutonium-create-resource/SKILL.md +1 -1
- data/.claude/skills/plutonium-definition/SKILL.md +445 -53
- data/.claude/skills/plutonium-definition-actions/SKILL.md +2 -2
- data/.claude/skills/plutonium-definition-query/SKILL.md +2 -2
- data/.claude/skills/plutonium-forms/SKILL.md +6 -2
- data/.claude/skills/plutonium-installation/SKILL.md +3 -3
- data/.claude/skills/plutonium-interaction/SKILL.md +3 -3
- data/.claude/skills/plutonium-invites/SKILL.md +1 -1
- data/.claude/skills/plutonium-model/SKILL.md +228 -55
- data/.claude/skills/plutonium-nested-resources/SKILL.md +9 -2
- data/.claude/skills/plutonium-package/SKILL.md +3 -3
- data/.claude/skills/plutonium-policy/SKILL.md +6 -2
- data/.claude/skills/plutonium-portal/SKILL.md +97 -59
- data/.claude/skills/plutonium-profile/SKILL.md +2 -2
- data/.claude/skills/plutonium-rodauth/SKILL.md +1 -1
- data/.claude/skills/plutonium-theming/SKILL.md +1 -1
- data/.claude/skills/plutonium-views/SKILL.md +2 -2
- data/CHANGELOG.md +25 -0
- data/app/assets/plutonium.css +1 -1
- data/gemfiles/rails_7.gemfile.lock +3 -3
- data/gemfiles/rails_8.0.gemfile.lock +3 -3
- data/gemfiles/rails_8.1.gemfile.lock +3 -3
- data/lib/generators/pu/invites/install_generator.rb +2 -2
- data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/show.html.erb.tt +7 -7
- data/lib/generators/pu/saas/portal_generator.rb +17 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +21 -0
- data/lib/plutonium/engine.rb +1 -1
- data/lib/plutonium/railtie.rb +1 -1
- data/lib/plutonium/ui/form/components/resource_select.rb +1 -1
- data/lib/plutonium/ui/form/components/secure_association.rb +2 -2
- data/lib/plutonium/ui/form/components/secure_polymorphic_association.rb +6 -11
- data/lib/plutonium/version.rb +1 -1
- data/package.json +1 -1
- data/plutonium.gemspec +1 -1
- data/src/css/tokens.css +2 -0
- metadata +4 -8
- data/.claude/skills/plutonium-connect-resource/SKILL.md +0 -130
- data/.claude/skills/plutonium-definition-fields/SKILL.md +0 -535
- data/.claude/skills/plutonium-model-features/SKILL.md +0 -286
- data/.claude/skills/plutonium-resource/SKILL.md +0 -281
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-model
|
|
3
|
-
description:
|
|
3
|
+
description: Use when working with Plutonium resource models - setup, structure, has_cents, associations, SGID support, entity scoping, and routing
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Resource Models
|
|
7
7
|
|
|
8
|
+
**Always use generators to create models** - never create model files manually:
|
|
9
|
+
```bash
|
|
10
|
+
rails g pu:res:scaffold Post title:string content:text --dest=main_app
|
|
11
|
+
```
|
|
12
|
+
See `plutonium-create-resource` for full field type syntax and generator options.
|
|
13
|
+
|
|
8
14
|
A model becomes a Plutonium resource by including `Plutonium::Resource::Record`. This provides enhanced ActiveRecord functionality for routing, labeling, field introspection, associations, and monetary handling.
|
|
9
15
|
|
|
10
16
|
## Setup
|
|
@@ -122,88 +128,220 @@ end
|
|
|
122
128
|
13. **Misc attribute macros** - `has_rich_text`, `has_secure_token`, `has_secure_password`
|
|
123
129
|
14. **Methods** - Public methods above, private methods below
|
|
124
130
|
|
|
125
|
-
##
|
|
131
|
+
## Monetary Handling (has_cents)
|
|
126
132
|
|
|
127
|
-
|
|
133
|
+
Store monetary values as integers (cents) while exposing decimal interfaces.
|
|
128
134
|
|
|
129
|
-
|
|
130
|
-
class Property < ResourceRecord
|
|
131
|
-
enum :state, archived: 0, active: 1
|
|
135
|
+
### Basic Usage
|
|
132
136
|
|
|
133
|
-
|
|
134
|
-
|
|
137
|
+
```ruby
|
|
138
|
+
class Product < ResourceRecord
|
|
139
|
+
has_cents :price_cents # Creates price getter/setter
|
|
140
|
+
has_cents :cost_cents, name: :wholesale # Custom accessor name
|
|
141
|
+
has_cents :tax_cents, rate: 1000 # 3 decimal places
|
|
142
|
+
has_cents :quantity_cents, rate: 1 # Whole numbers only
|
|
143
|
+
end
|
|
135
144
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
product = Product.new
|
|
146
|
+
product.price = 19.99
|
|
147
|
+
product.price_cents # => 1999
|
|
148
|
+
product.price # => 19.99
|
|
139
149
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
end
|
|
150
|
+
# Truncates (doesn't round)
|
|
151
|
+
product.price = 10.999
|
|
152
|
+
product.price_cents # => 1099
|
|
144
153
|
```
|
|
145
154
|
|
|
146
|
-
###
|
|
155
|
+
### Options
|
|
147
156
|
|
|
148
157
|
```ruby
|
|
149
|
-
|
|
150
|
-
|
|
158
|
+
has_cents :field_cents,
|
|
159
|
+
name: :custom_name, # Accessor name (default: field without _cents)
|
|
160
|
+
rate: 100, # Conversion rate (default: 100)
|
|
161
|
+
suffix: "amount" # Suffix for generated name (default: "amount")
|
|
162
|
+
```
|
|
151
163
|
|
|
152
|
-
|
|
153
|
-
validates :property_code, uniqueness: {scope: :company_id}
|
|
164
|
+
### Validation
|
|
154
165
|
|
|
155
|
-
|
|
156
|
-
|
|
166
|
+
```ruby
|
|
167
|
+
class Product < ResourceRecord
|
|
168
|
+
has_cents :price_cents
|
|
169
|
+
|
|
170
|
+
# Validate the cents field
|
|
171
|
+
validates :price_cents, numericality: {greater_than: 0}
|
|
157
172
|
end
|
|
173
|
+
|
|
174
|
+
product = Product.new(price: -10)
|
|
175
|
+
product.valid? # => false
|
|
176
|
+
product.errors[:price_cents] # => ["must be greater than 0"]
|
|
177
|
+
product.errors[:price] # => ["is invalid"] (propagated)
|
|
158
178
|
```
|
|
159
179
|
|
|
160
|
-
###
|
|
180
|
+
### Introspection
|
|
161
181
|
|
|
162
182
|
```ruby
|
|
163
|
-
|
|
164
|
-
|
|
183
|
+
Product.has_cents_attributes
|
|
184
|
+
# => {price_cents: {name: :price, rate: 100}, ...}
|
|
165
185
|
|
|
166
|
-
|
|
186
|
+
Product.has_cents_attribute?(:price_cents) # => true
|
|
187
|
+
```
|
|
167
188
|
|
|
168
|
-
|
|
189
|
+
## Association SGID Support
|
|
169
190
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
191
|
+
All associations get Signed Global ID (SGID) methods for secure serialization.
|
|
192
|
+
|
|
193
|
+
### Singular Associations (belongs_to, has_one)
|
|
194
|
+
|
|
195
|
+
```ruby
|
|
196
|
+
class Post < ResourceRecord
|
|
197
|
+
belongs_to :user
|
|
198
|
+
has_one :featured_image
|
|
174
199
|
end
|
|
200
|
+
|
|
201
|
+
post = Post.first
|
|
202
|
+
|
|
203
|
+
# Get SGID
|
|
204
|
+
post.user_sgid # => "BAh7CEkiCG..."
|
|
205
|
+
post.featured_image_sgid # => "BAh7CEkiCG..."
|
|
206
|
+
|
|
207
|
+
# Set by SGID (finds and assigns)
|
|
208
|
+
post.user_sgid = "BAh7CEkiCG..."
|
|
209
|
+
post.featured_image_sgid = "BAh7CEkiCG..."
|
|
175
210
|
```
|
|
176
211
|
|
|
177
|
-
###
|
|
212
|
+
### Collection Associations (has_many, has_and_belongs_to_many)
|
|
178
213
|
|
|
179
214
|
```ruby
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
has_one :commercial_profile, class_name: "CommercialTenantProfile"
|
|
215
|
+
class User < ResourceRecord
|
|
216
|
+
has_many :posts
|
|
217
|
+
has_and_belongs_to_many :roles
|
|
184
218
|
end
|
|
185
219
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
#
|
|
220
|
+
user = User.first
|
|
221
|
+
|
|
222
|
+
# Get SGIDs
|
|
223
|
+
user.post_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
|
224
|
+
user.role_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
|
225
|
+
|
|
226
|
+
# Bulk assignment
|
|
227
|
+
user.post_sgids = ["BAh7CEkiCG...", ...]
|
|
228
|
+
|
|
229
|
+
# Individual manipulation
|
|
230
|
+
user.add_post_sgid("BAh7CEkiCG...") # Add to collection
|
|
231
|
+
user.remove_post_sgid("BAh7CEkiCG...") # Remove from collection
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Entity Scoping (associated_with)
|
|
235
|
+
|
|
236
|
+
Query records associated with another record. Essential for multi-tenant apps.
|
|
237
|
+
|
|
238
|
+
### Basic Usage
|
|
239
|
+
|
|
240
|
+
```ruby
|
|
241
|
+
class Comment < ResourceRecord
|
|
242
|
+
belongs_to :post
|
|
190
243
|
end
|
|
244
|
+
|
|
245
|
+
# Find comments for a post
|
|
246
|
+
Comment.associated_with(post)
|
|
247
|
+
# => Comment.where(post: post)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Association Detection
|
|
251
|
+
|
|
252
|
+
Works with:
|
|
253
|
+
- `belongs_to` - Uses WHERE clause (most efficient)
|
|
254
|
+
- `has_one` - Uses JOIN + WHERE
|
|
255
|
+
- `has_many` - Uses JOIN + WHERE
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
# Direct association (preferred)
|
|
259
|
+
Comment.associated_with(post) # WHERE post_id = ?
|
|
260
|
+
|
|
261
|
+
# Reverse association (less efficient, logs warning)
|
|
262
|
+
Post.associated_with(comment) # JOIN comments WHERE comments.id = ?
|
|
191
263
|
```
|
|
192
264
|
|
|
193
|
-
###
|
|
265
|
+
### Custom Scopes
|
|
266
|
+
|
|
267
|
+
For optimal performance, define custom scopes:
|
|
194
268
|
|
|
195
269
|
```ruby
|
|
196
270
|
class Comment < ResourceRecord
|
|
197
|
-
|
|
271
|
+
# Custom scope naming: associated_with_{model_name}
|
|
272
|
+
scope :associated_with_user, ->(user) do
|
|
273
|
+
joins(:post).where(posts: {user_id: user.id})
|
|
274
|
+
end
|
|
198
275
|
end
|
|
199
276
|
|
|
200
|
-
|
|
201
|
-
|
|
277
|
+
# Automatically uses custom scope
|
|
278
|
+
Comment.associated_with(user)
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Error Handling
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
# When no association exists
|
|
285
|
+
UnrelatedModel.associated_with(user)
|
|
286
|
+
# Raises: Could not resolve the association between 'UnrelatedModel' and 'User'
|
|
287
|
+
#
|
|
288
|
+
# Define:
|
|
289
|
+
# 1. the associations between the models
|
|
290
|
+
# 2. a named scope on UnrelatedModel e.g.
|
|
291
|
+
#
|
|
292
|
+
# scope :associated_with_user, ->(user) { do_something_here }
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## URL Routing
|
|
296
|
+
|
|
297
|
+
### Default Behavior
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
user = User.find(1)
|
|
301
|
+
user.to_param # => "1"
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Custom Path Parameters
|
|
305
|
+
|
|
306
|
+
Use a stable, unique field instead of ID:
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
class User < ResourceRecord
|
|
310
|
+
private
|
|
311
|
+
|
|
312
|
+
def path_parameter(param_name)
|
|
313
|
+
:username # Must be unique
|
|
314
|
+
end
|
|
202
315
|
end
|
|
203
316
|
|
|
204
|
-
|
|
205
|
-
|
|
317
|
+
user = User.create(username: "john_doe")
|
|
318
|
+
user.to_param # => "john_doe"
|
|
319
|
+
# URLs: /users/john_doe
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Dynamic Path Parameters (SEO-friendly)
|
|
323
|
+
|
|
324
|
+
Include ID prefix for uniqueness with human-readable suffix:
|
|
325
|
+
|
|
326
|
+
```ruby
|
|
327
|
+
class Article < ResourceRecord
|
|
328
|
+
private
|
|
329
|
+
|
|
330
|
+
def dynamic_path_parameter(param_name)
|
|
331
|
+
:title
|
|
332
|
+
end
|
|
206
333
|
end
|
|
334
|
+
|
|
335
|
+
article = Article.create(id: 1, title: "My Great Article")
|
|
336
|
+
article.to_param # => "1-my-great-article"
|
|
337
|
+
# URLs: /articles/1-my-great-article
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Path Parameter Lookup
|
|
341
|
+
|
|
342
|
+
```ruby
|
|
343
|
+
User.from_path_param("john_doe")
|
|
344
|
+
Article.from_path_param("1-my-great-article") # Extracts ID
|
|
207
345
|
```
|
|
208
346
|
|
|
209
347
|
## Labeling
|
|
@@ -244,6 +382,49 @@ User.has_one_attached_field_names # Active Storage single
|
|
|
244
382
|
User.has_many_attached_field_names # Active Storage multiple
|
|
245
383
|
```
|
|
246
384
|
|
|
385
|
+
## Common Patterns
|
|
386
|
+
|
|
387
|
+
### Archiving (State-Based)
|
|
388
|
+
|
|
389
|
+
```ruby
|
|
390
|
+
class Property < ResourceRecord
|
|
391
|
+
enum :state, archived: 0, active: 1
|
|
392
|
+
|
|
393
|
+
scope :active, -> { where(state: :active) }
|
|
394
|
+
scope :archived, -> { where(state: :archived) }
|
|
395
|
+
end
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### Multi-Tenant Scoping
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
class Property < ResourceRecord
|
|
402
|
+
belongs_to :company
|
|
403
|
+
|
|
404
|
+
# Compound uniqueness for multi-tenant
|
|
405
|
+
validates :property_code, uniqueness: {scope: :company_id}
|
|
406
|
+
|
|
407
|
+
# Custom scope for entity scoping
|
|
408
|
+
scope :associated_with_company, ->(company) { where(company: company) }
|
|
409
|
+
end
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Performance Tips
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
# Efficient: Direct belongs_to
|
|
416
|
+
Comment.associated_with(post) # Simple WHERE
|
|
417
|
+
|
|
418
|
+
# Less efficient: Reverse has_many (logs warning)
|
|
419
|
+
Post.associated_with(comment) # JOIN required
|
|
420
|
+
|
|
421
|
+
# Optimal: Custom scope when direct isn't possible
|
|
422
|
+
scope :associated_with_user, ->(user) { where(user_id: user.id) }
|
|
423
|
+
|
|
424
|
+
# SGID: Batch assignment over individual adds
|
|
425
|
+
user.post_sgids = sgid_array # Single operation
|
|
426
|
+
```
|
|
427
|
+
|
|
247
428
|
## Best Practices
|
|
248
429
|
|
|
249
430
|
1. **Use enums for state** - `enum :state, archived: 0, active: 1` instead of soft-delete
|
|
@@ -253,15 +434,7 @@ User.has_many_attached_field_names # Active Storage multiple
|
|
|
253
434
|
5. **Validate at boundaries** - Validate user input, trust internal code
|
|
254
435
|
6. **Use scopes** - Define commonly used queries as scopes
|
|
255
436
|
|
|
256
|
-
## Integration
|
|
257
|
-
|
|
258
|
-
Models integrate with:
|
|
259
|
-
- **Policies** - `resource_field_names` for auto-detection
|
|
260
|
-
- **Definitions** - Field introspection for forms/displays
|
|
261
|
-
- **Controllers** - `from_path_param` for lookups
|
|
262
|
-
- **Query Objects** - Association detection for sorting
|
|
263
|
-
|
|
264
437
|
## Related Skills
|
|
265
438
|
|
|
266
|
-
- `plutonium-model-features` - has_cents, associations, scopes, routes
|
|
267
439
|
- `plutonium-create-resource` - Scaffold generator for new resources
|
|
440
|
+
- `plutonium-definition` - Definition overview, fields, inputs, displays
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-nested-resources
|
|
3
|
-
description:
|
|
3
|
+
description: Use when configuring parent/child resource relationships, nested routes, or scoped URL generation in Plutonium
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Nested Resources
|
|
7
7
|
|
|
8
|
+
**Always use generators** to create both parent and child resources, then connect them to portals:
|
|
9
|
+
```bash
|
|
10
|
+
rails g pu:res:scaffold Company name:string --dest=main_app
|
|
11
|
+
rails g pu:res:scaffold Property company:belongs_to name:string --dest=main_app
|
|
12
|
+
rails g pu:res:conn Company Property --dest=admin_portal
|
|
13
|
+
```
|
|
14
|
+
|
|
8
15
|
Plutonium automatically creates nested routes for `has_many` and `has_one` associations, scopes queries to the parent, and handles URL generation.
|
|
9
16
|
|
|
10
17
|
## How It Works
|
|
@@ -333,4 +340,4 @@ Generates nested routes:
|
|
|
333
340
|
- `plutonium-portal` - Route registration
|
|
334
341
|
- `plutonium-policy` - Authorization and scoping
|
|
335
342
|
- `plutonium-controller` - Presentation hooks
|
|
336
|
-
- `plutonium-model
|
|
343
|
+
- `plutonium-model` - associated_with scope
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-package
|
|
3
|
-
description:
|
|
3
|
+
description: Use when creating feature packages or portal packages to organize a Plutonium app into modular engines
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Packages
|
|
@@ -186,6 +186,6 @@ rails db:migrate # Runs migrations from all packages
|
|
|
186
186
|
## Related Skills
|
|
187
187
|
|
|
188
188
|
- `plutonium-portal` - Portal-specific features (auth, entity scoping, routes)
|
|
189
|
-
- `plutonium
|
|
190
|
-
- `plutonium-
|
|
189
|
+
- `plutonium` - Resource architecture overview
|
|
190
|
+
- `plutonium-portal` - Connecting resources to portals
|
|
191
191
|
- `plutonium-create-resource` - Creating resources
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-policy
|
|
3
|
-
description:
|
|
3
|
+
description: Use when configuring authorization - action permissions, attribute visibility, relation scoping, or per-portal policies
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Policies
|
|
7
7
|
|
|
8
|
+
**Policies are generated automatically** - never create them manually:
|
|
9
|
+
- `rails g pu:res:scaffold` creates the base policy
|
|
10
|
+
- `rails g pu:res:conn` creates portal-specific policies with attribute permissions
|
|
11
|
+
|
|
8
12
|
Policies control WHO can do WHAT with resources. Built on [ActionPolicy](https://actionpolicy.evilmartians.io/).
|
|
9
13
|
|
|
10
14
|
Plutonium extends ActionPolicy with:
|
|
@@ -456,6 +460,6 @@ end
|
|
|
456
460
|
|
|
457
461
|
## Related Skills
|
|
458
462
|
|
|
459
|
-
- `plutonium
|
|
463
|
+
- `plutonium` - How policies fit in the resource architecture
|
|
460
464
|
- `plutonium-definition-actions` - Actions that need policy methods
|
|
461
465
|
- `plutonium-controller` - How controllers use policies
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-portal
|
|
3
|
-
description:
|
|
3
|
+
description: Use when creating portals, connecting resources to portals, configuring authentication, entity scoping, or portal routes
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Portals
|
|
@@ -32,10 +32,7 @@ rails g pu:pkg:portal custom --byo
|
|
|
32
32
|
rails g pu:pkg:portal admin --auth=admin --scope=Organization
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Without flags, the generator prompts interactively
|
|
36
|
-
- **Rodauth account** - Use existing Rodauth authentication
|
|
37
|
-
- **Public access** - No authentication required
|
|
38
|
-
- **Bring your own** - Implement custom `current_user`
|
|
35
|
+
Without flags, the generator prompts interactively.
|
|
39
36
|
|
|
40
37
|
## Portal Engine
|
|
41
38
|
|
|
@@ -53,6 +50,86 @@ module DashboardPortal
|
|
|
53
50
|
end
|
|
54
51
|
```
|
|
55
52
|
|
|
53
|
+
## Connecting Resources to Portals
|
|
54
|
+
|
|
55
|
+
Resources must be connected to a portal to be accessible via its web interface.
|
|
56
|
+
|
|
57
|
+
### Command Syntax
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
rails g pu:res:conn RESOURCE [RESOURCE...] --dest=PORTAL_NAME [--singular]
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**Always specify resources directly** - this avoids interactive prompts.
|
|
64
|
+
|
|
65
|
+
### Usage Patterns
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Main app resources
|
|
69
|
+
rails g pu:res:conn Post Comment Tag --dest=dashboard_portal
|
|
70
|
+
|
|
71
|
+
# Namespaced resources (from a feature package)
|
|
72
|
+
rails g pu:res:conn Blogging::Post Blogging::Comment --dest=admin_portal
|
|
73
|
+
|
|
74
|
+
# Singular resources (profile, dashboard, settings)
|
|
75
|
+
rails g pu:res:conn Profile --dest=customer_portal --singular
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### What Gets Generated
|
|
79
|
+
|
|
80
|
+
For a resource `Post` connected to `admin_portal`:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
packages/admin_portal/app/
|
|
84
|
+
├── controllers/admin_portal/posts_controller.rb # Portal controller
|
|
85
|
+
├── policies/admin_portal/post_policy.rb # Portal policy
|
|
86
|
+
└── definitions/admin_portal/post_definition.rb # Portal definition
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Plus route registration in `packages/admin_portal/config/routes.rb`.
|
|
90
|
+
|
|
91
|
+
### Generated Controller
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
class AdminPortal::PostsController < ::PostsController
|
|
95
|
+
include AdminPortal::Concerns::Controller
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Generated Policy
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
class AdminPortal::PostPolicy < ::PostPolicy
|
|
103
|
+
include AdminPortal::ResourcePolicy
|
|
104
|
+
|
|
105
|
+
def permitted_attributes_for_create
|
|
106
|
+
[:title, :content, :user_id]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def permitted_attributes_for_read
|
|
110
|
+
[:title, :content, :user_id, :created_at, :updated_at]
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def permitted_associations
|
|
114
|
+
%i[]
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Route Registration
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
# In packages/admin_portal/config/routes.rb
|
|
123
|
+
register_resource ::Post
|
|
124
|
+
register_resource ::Profile, singular: true # With --singular
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Important Notes
|
|
128
|
+
|
|
129
|
+
1. **Always specify resources directly** - avoids prompts, no `--src` needed
|
|
130
|
+
2. **Always use the generator** - never manually connect resources
|
|
131
|
+
3. **Run after migrations** - the generator reads model columns for policy attributes
|
|
132
|
+
|
|
56
133
|
## Authentication
|
|
57
134
|
|
|
58
135
|
### Rodauth Integration
|
|
@@ -224,13 +301,6 @@ register_resource ::Post do
|
|
|
224
301
|
end
|
|
225
302
|
```
|
|
226
303
|
|
|
227
|
-
This generates:
|
|
228
|
-
- `GET /posts/:id/preview`
|
|
229
|
-
- `GET /posts/:id/analytics`
|
|
230
|
-
- `POST /posts/:id/publish`
|
|
231
|
-
- `GET /posts/archived`
|
|
232
|
-
- `POST /posts/bulk_publish`
|
|
233
|
-
|
|
234
304
|
### Mounting in Main App
|
|
235
305
|
|
|
236
306
|
```ruby
|
|
@@ -263,8 +333,6 @@ end
|
|
|
263
333
|
|
|
264
334
|
### Portal ResourceController
|
|
265
335
|
|
|
266
|
-
The portal's `ResourceController` serves as the base class for resource controllers when no feature package controller exists. It includes the portal's `Concerns::Controller` so individual resource controllers don't need to.
|
|
267
|
-
|
|
268
336
|
```ruby
|
|
269
337
|
# packages/dashboard_portal/app/controllers/dashboard_portal/resource_controller.rb
|
|
270
338
|
module DashboardPortal
|
|
@@ -279,7 +347,6 @@ end
|
|
|
279
347
|
For portal pages not tied to a resource (dashboard, settings, etc.), inherit from `PlutoniumController`:
|
|
280
348
|
|
|
281
349
|
```ruby
|
|
282
|
-
# packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
|
|
283
350
|
module DashboardPortal
|
|
284
351
|
class DashboardController < PlutoniumController
|
|
285
352
|
def index
|
|
@@ -294,10 +361,7 @@ end
|
|
|
294
361
|
### Override Definition
|
|
295
362
|
|
|
296
363
|
```ruby
|
|
297
|
-
# packages/dashboard_portal/app/definitions/dashboard_portal/post_definition.rb
|
|
298
364
|
class DashboardPortal::PostDefinition < ::PostDefinition
|
|
299
|
-
# Hide certain actions from this portal
|
|
300
|
-
# Add portal-specific scopes
|
|
301
365
|
scope :my_posts, -> { where(user: current_user) }
|
|
302
366
|
end
|
|
303
367
|
```
|
|
@@ -305,7 +369,6 @@ end
|
|
|
305
369
|
### Override Policy
|
|
306
370
|
|
|
307
371
|
```ruby
|
|
308
|
-
# packages/dashboard_portal/app/policies/dashboard_portal/post_policy.rb
|
|
309
372
|
class DashboardPortal::PostPolicy < ::PostPolicy
|
|
310
373
|
include DashboardPortal::ResourcePolicy
|
|
311
374
|
|
|
@@ -322,7 +385,6 @@ end
|
|
|
322
385
|
### Override Controller
|
|
323
386
|
|
|
324
387
|
```ruby
|
|
325
|
-
# packages/dashboard_portal/app/controllers/dashboard_portal/posts_controller.rb
|
|
326
388
|
module DashboardPortal
|
|
327
389
|
class PostsController < ResourceController
|
|
328
390
|
private
|
|
@@ -334,39 +396,6 @@ module DashboardPortal
|
|
|
334
396
|
end
|
|
335
397
|
```
|
|
336
398
|
|
|
337
|
-
## Layout and Views
|
|
338
|
-
|
|
339
|
-
### Portal Layout
|
|
340
|
-
|
|
341
|
-
```erb
|
|
342
|
-
<!-- packages/dashboard_portal/app/views/layouts/dashboard_portal.html.erb -->
|
|
343
|
-
<!DOCTYPE html>
|
|
344
|
-
<html>
|
|
345
|
-
<head>
|
|
346
|
-
<title>Dashboard</title>
|
|
347
|
-
<%= csrf_meta_tags %>
|
|
348
|
-
<%= stylesheet_link_tag "application" %>
|
|
349
|
-
</head>
|
|
350
|
-
<body>
|
|
351
|
-
<nav><!-- Portal navigation --></nav>
|
|
352
|
-
<main><%= yield %></main>
|
|
353
|
-
</body>
|
|
354
|
-
</html>
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### Dashboard Controller
|
|
358
|
-
|
|
359
|
-
```ruby
|
|
360
|
-
# packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
|
|
361
|
-
module DashboardPortal
|
|
362
|
-
class DashboardController < PlutoniumController
|
|
363
|
-
def index
|
|
364
|
-
# Dashboard home page
|
|
365
|
-
end
|
|
366
|
-
end
|
|
367
|
-
end
|
|
368
|
-
```
|
|
369
|
-
|
|
370
399
|
## Multiple Portals Example
|
|
371
400
|
|
|
372
401
|
```ruby
|
|
@@ -400,17 +429,26 @@ module PublicPortal
|
|
|
400
429
|
end
|
|
401
430
|
```
|
|
402
431
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
432
|
+
## Typical Workflow
|
|
433
|
+
|
|
434
|
+
```bash
|
|
435
|
+
# 1. Create portal
|
|
436
|
+
rails g pu:pkg:portal admin --auth=admin --scope=Organization
|
|
437
|
+
|
|
438
|
+
# 2. Create resources
|
|
439
|
+
rails g pu:res:scaffold Post user:belongs_to title:string 'content:text?' --dest=main_app
|
|
440
|
+
rails db:migrate
|
|
441
|
+
|
|
442
|
+
# 3. Connect resources to portal
|
|
443
|
+
rails g pu:res:conn Post --dest=admin_portal
|
|
444
|
+
|
|
445
|
+
# 4. Customize portal-specific definitions/policies as needed
|
|
446
|
+
```
|
|
408
447
|
|
|
409
448
|
## Related Skills
|
|
410
449
|
|
|
411
450
|
- `plutonium-package` - Package overview (features vs portals)
|
|
412
451
|
- `plutonium-rodauth` - Authentication setup and configuration
|
|
413
|
-
- `plutonium-connect-resource` - Connecting resources to portals
|
|
414
452
|
- `plutonium-policy` - Portal-specific policies
|
|
415
453
|
- `plutonium-definition` - Portal-specific definitions
|
|
416
454
|
- `plutonium-controller` - Portal-specific controllers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-profile
|
|
3
|
-
description:
|
|
3
|
+
description: Use when adding a user profile or account settings page with Rodauth security features
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium User Profile
|
|
@@ -273,4 +273,4 @@ current_user.profile || current_user.create_profile
|
|
|
273
273
|
- `plutonium-rodauth` - Authentication configuration
|
|
274
274
|
- `plutonium-definition` - Customizing the profile definition
|
|
275
275
|
- `plutonium-views` - Custom pages and components
|
|
276
|
-
- `plutonium-
|
|
276
|
+
- `plutonium-portal` - Connecting resources to portals
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-rodauth
|
|
3
|
-
description:
|
|
3
|
+
description: Use when setting up authentication - Rodauth configuration, account types, login flows, or multi-account support
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Rodauth Authentication
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plutonium-theming
|
|
3
|
-
description:
|
|
3
|
+
description: Use when customizing colors, typography, spacing, or component appearance via Plutonium design tokens
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Plutonium Design Token System
|