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,286 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-model-features
|
|
3
|
-
description: Plutonium model features - has_cents, associations, scopes, and routing
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Plutonium Model Features
|
|
7
|
-
|
|
8
|
-
Advanced features available in Plutonium resource models.
|
|
9
|
-
|
|
10
|
-
## Monetary Handling (has_cents)
|
|
11
|
-
|
|
12
|
-
Store monetary values as integers (cents) while exposing decimal interfaces.
|
|
13
|
-
|
|
14
|
-
### Basic Usage
|
|
15
|
-
|
|
16
|
-
```ruby
|
|
17
|
-
class Product < ResourceRecord
|
|
18
|
-
has_cents :price_cents # Creates price getter/setter
|
|
19
|
-
has_cents :cost_cents, name: :wholesale # Custom accessor name
|
|
20
|
-
has_cents :tax_cents, rate: 1000 # 3 decimal places
|
|
21
|
-
has_cents :quantity_cents, rate: 1 # Whole numbers only
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
product = Product.new
|
|
25
|
-
product.price = 19.99
|
|
26
|
-
product.price_cents # => 1999
|
|
27
|
-
product.price # => 19.99
|
|
28
|
-
|
|
29
|
-
# Truncates (doesn't round)
|
|
30
|
-
product.price = 10.999
|
|
31
|
-
product.price_cents # => 1099
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Options
|
|
35
|
-
|
|
36
|
-
```ruby
|
|
37
|
-
has_cents :field_cents,
|
|
38
|
-
name: :custom_name, # Accessor name (default: field without _cents)
|
|
39
|
-
rate: 100, # Conversion rate (default: 100)
|
|
40
|
-
suffix: "amount" # Suffix for generated name (default: "amount")
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Validation
|
|
44
|
-
|
|
45
|
-
```ruby
|
|
46
|
-
class Product < ResourceRecord
|
|
47
|
-
has_cents :price_cents
|
|
48
|
-
|
|
49
|
-
# Validate the cents field
|
|
50
|
-
validates :price_cents, numericality: {greater_than: 0}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
product = Product.new(price: -10)
|
|
54
|
-
product.valid? # => false
|
|
55
|
-
product.errors[:price_cents] # => ["must be greater than 0"]
|
|
56
|
-
product.errors[:price] # => ["is invalid"] (propagated)
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Introspection
|
|
60
|
-
|
|
61
|
-
```ruby
|
|
62
|
-
Product.has_cents_attributes
|
|
63
|
-
# => {price_cents: {name: :price, rate: 100}, ...}
|
|
64
|
-
|
|
65
|
-
Product.has_cents_attribute?(:price_cents) # => true
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Association SGID Support
|
|
69
|
-
|
|
70
|
-
All associations get Signed Global ID (SGID) methods for secure serialization.
|
|
71
|
-
|
|
72
|
-
### Singular Associations (belongs_to, has_one)
|
|
73
|
-
|
|
74
|
-
```ruby
|
|
75
|
-
class Post < ResourceRecord
|
|
76
|
-
belongs_to :user
|
|
77
|
-
has_one :featured_image
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
post = Post.first
|
|
81
|
-
|
|
82
|
-
# Get SGID
|
|
83
|
-
post.user_sgid # => "BAh7CEkiCG..."
|
|
84
|
-
post.featured_image_sgid # => "BAh7CEkiCG..."
|
|
85
|
-
|
|
86
|
-
# Set by SGID (finds and assigns)
|
|
87
|
-
post.user_sgid = "BAh7CEkiCG..."
|
|
88
|
-
post.featured_image_sgid = "BAh7CEkiCG..."
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
### Collection Associations (has_many, has_and_belongs_to_many)
|
|
92
|
-
|
|
93
|
-
```ruby
|
|
94
|
-
class User < ResourceRecord
|
|
95
|
-
has_many :posts
|
|
96
|
-
has_and_belongs_to_many :roles
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
user = User.first
|
|
100
|
-
|
|
101
|
-
# Get SGIDs
|
|
102
|
-
user.post_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
|
103
|
-
user.role_sgids # => ["BAh7CEkiCG...", "BAh7CEkiCG..."]
|
|
104
|
-
|
|
105
|
-
# Bulk assignment
|
|
106
|
-
user.post_sgids = ["BAh7CEkiCG...", ...]
|
|
107
|
-
|
|
108
|
-
# Individual manipulation
|
|
109
|
-
user.add_post_sgid("BAh7CEkiCG...") # Add to collection
|
|
110
|
-
user.remove_post_sgid("BAh7CEkiCG...") # Remove from collection
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Use Cases
|
|
114
|
-
|
|
115
|
-
- Secure form submissions without exposing internal IDs
|
|
116
|
-
- API responses with portable references
|
|
117
|
-
- Caching and serialization
|
|
118
|
-
|
|
119
|
-
## Entity Scoping (associated_with)
|
|
120
|
-
|
|
121
|
-
Query records associated with another record. Essential for multi-tenant apps.
|
|
122
|
-
|
|
123
|
-
### Basic Usage
|
|
124
|
-
|
|
125
|
-
```ruby
|
|
126
|
-
class Comment < ResourceRecord
|
|
127
|
-
belongs_to :post
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
# Find comments for a post
|
|
131
|
-
Comment.associated_with(post)
|
|
132
|
-
# => Comment.where(post: post)
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
### Association Detection
|
|
136
|
-
|
|
137
|
-
Works with:
|
|
138
|
-
- `belongs_to` - Uses WHERE clause (most efficient)
|
|
139
|
-
- `has_one` - Uses JOIN + WHERE
|
|
140
|
-
- `has_many` - Uses JOIN + WHERE
|
|
141
|
-
|
|
142
|
-
```ruby
|
|
143
|
-
# Direct association (preferred)
|
|
144
|
-
Comment.associated_with(post) # WHERE post_id = ?
|
|
145
|
-
|
|
146
|
-
# Reverse association (less efficient, logs warning)
|
|
147
|
-
Post.associated_with(comment) # JOIN comments WHERE comments.id = ?
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### Custom Scopes
|
|
151
|
-
|
|
152
|
-
For optimal performance, define custom scopes:
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class Comment < ResourceRecord
|
|
156
|
-
# Custom scope naming: associated_with_{model_name}
|
|
157
|
-
scope :associated_with_user, ->(user) do
|
|
158
|
-
joins(:post).where(posts: {user_id: user.id})
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Automatically uses custom scope
|
|
163
|
-
Comment.associated_with(user)
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Error Handling
|
|
167
|
-
|
|
168
|
-
```ruby
|
|
169
|
-
# When no association exists
|
|
170
|
-
UnrelatedModel.associated_with(user)
|
|
171
|
-
# Raises: Could not resolve the association between 'UnrelatedModel' and 'User'
|
|
172
|
-
#
|
|
173
|
-
# Define:
|
|
174
|
-
# 1. the associations between the models
|
|
175
|
-
# 2. a named scope on UnrelatedModel e.g.
|
|
176
|
-
#
|
|
177
|
-
# scope :associated_with_user, ->(user) { do_something_here }
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## URL Routing
|
|
181
|
-
|
|
182
|
-
### Default Behavior
|
|
183
|
-
|
|
184
|
-
```ruby
|
|
185
|
-
user = User.find(1)
|
|
186
|
-
user.to_param # => "1"
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
### Custom Path Parameters
|
|
190
|
-
|
|
191
|
-
Use a stable, unique field instead of ID:
|
|
192
|
-
|
|
193
|
-
```ruby
|
|
194
|
-
class User < ResourceRecord
|
|
195
|
-
private
|
|
196
|
-
|
|
197
|
-
def path_parameter(param_name)
|
|
198
|
-
:username # Must be unique
|
|
199
|
-
end
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
user = User.create(username: "john_doe")
|
|
203
|
-
user.to_param # => "john_doe"
|
|
204
|
-
# URLs: /users/john_doe
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### Dynamic Path Parameters (SEO-friendly)
|
|
208
|
-
|
|
209
|
-
Include ID prefix for uniqueness with human-readable suffix:
|
|
210
|
-
|
|
211
|
-
```ruby
|
|
212
|
-
class Article < ResourceRecord
|
|
213
|
-
private
|
|
214
|
-
|
|
215
|
-
def dynamic_path_parameter(param_name)
|
|
216
|
-
:title
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
article = Article.create(id: 1, title: "My Great Article")
|
|
221
|
-
article.to_param # => "1-my-great-article"
|
|
222
|
-
# URLs: /articles/1-my-great-article
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### Path Parameter Lookup
|
|
226
|
-
|
|
227
|
-
```ruby
|
|
228
|
-
# Scope for finding by path parameter
|
|
229
|
-
User.from_path_param("john_doe")
|
|
230
|
-
Article.from_path_param("1-my-great-article") # Extracts ID
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## Association Route Discovery
|
|
234
|
-
|
|
235
|
-
```ruby
|
|
236
|
-
class User < ResourceRecord
|
|
237
|
-
has_many :posts
|
|
238
|
-
has_many :comments
|
|
239
|
-
accepts_nested_attributes_for :posts
|
|
240
|
-
end
|
|
241
|
-
|
|
242
|
-
# Get has_many association names
|
|
243
|
-
User.has_many_association_routes
|
|
244
|
-
# => ["posts", "comments"]
|
|
245
|
-
|
|
246
|
-
# Get nested attributes config
|
|
247
|
-
User.all_nested_attributes_options
|
|
248
|
-
# => {posts: {allow_destroy: false, update_only: false, macro: :has_many, class: Post}}
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
## Performance Tips
|
|
252
|
-
|
|
253
|
-
### Field Introspection
|
|
254
|
-
|
|
255
|
-
```ruby
|
|
256
|
-
# Cached in production, fresh in development
|
|
257
|
-
User.resource_field_names # First call queries, subsequent cached
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Association Queries
|
|
261
|
-
|
|
262
|
-
```ruby
|
|
263
|
-
# Efficient: Direct belongs_to
|
|
264
|
-
Comment.associated_with(post) # Simple WHERE
|
|
265
|
-
|
|
266
|
-
# Less efficient: Reverse has_many (logs warning)
|
|
267
|
-
Post.associated_with(comment) # JOIN required
|
|
268
|
-
|
|
269
|
-
# Optimal: Custom scope when direct isn't possible
|
|
270
|
-
scope :associated_with_user, ->(user) { where(user_id: user.id) }
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### SGID Operations
|
|
274
|
-
|
|
275
|
-
```ruby
|
|
276
|
-
# Efficient: Batch assignment
|
|
277
|
-
user.post_sgids = sgid_array # Single operation
|
|
278
|
-
|
|
279
|
-
# Inefficient: Individual adds
|
|
280
|
-
sgid_array.each { |sgid| user.add_post_sgid(sgid) }
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## Related Skills
|
|
284
|
-
|
|
285
|
-
- `plutonium-model` - Model overview and structure
|
|
286
|
-
- `plutonium-create-resource` - Scaffold generator
|
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: plutonium-resource
|
|
3
|
-
description: Overview of Plutonium resources - what they are and how the pieces fit together
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Plutonium Resources
|
|
7
|
-
|
|
8
|
-
A **resource** in Plutonium is the combination of four layers that work together to provide full CRUD functionality with minimal code.
|
|
9
|
-
|
|
10
|
-
## The Four Layers
|
|
11
|
-
|
|
12
|
-
| Layer | File | Purpose |
|
|
13
|
-
|-------|------|---------|
|
|
14
|
-
| **Model** | `app/models/post.rb` | Data, validations, associations, business rules |
|
|
15
|
-
| **Definition** | `app/definitions/post_definition.rb` | UI configuration - how fields render, actions, filters |
|
|
16
|
-
| **Policy** | `app/policies/post_policy.rb` | Authorization - who can do what |
|
|
17
|
-
| **Controller** | `app/controllers/posts_controller.rb` | Request handling - usually empty, inherits CRUD |
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
┌─────────────────────────────────────────────────────────────────┐
|
|
21
|
-
│ Resource │
|
|
22
|
-
├─────────────────────────────────────────────────────────────────┤
|
|
23
|
-
│ Model │ Definition │ Policy │ Controller │
|
|
24
|
-
│ (WHAT it is) │ (HOW it looks) │ (WHO can act) │ (HOW it │
|
|
25
|
-
│ │ │ │ responds) │
|
|
26
|
-
├─────────────────────────────────────────────────────────────────┤
|
|
27
|
-
│ - attributes │ - field types │ - permissions │ - CRUD │
|
|
28
|
-
│ - associations │ - inputs/forms │ - scoping │ - redirects│
|
|
29
|
-
│ - validations │ - displays │ - attributes │ - params │
|
|
30
|
-
│ - scopes │ - actions │ │ │
|
|
31
|
-
│ - callbacks │ - filters │ │ │
|
|
32
|
-
└─────────────────────────────────────────────────────────────────┘
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Creating Resources
|
|
36
|
-
|
|
37
|
-
### New Resources (from scratch)
|
|
38
|
-
|
|
39
|
-
Use the scaffold generator to create all four layers at once:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
rails g pu:res:scaffold Post title:string content:text:required published:boolean
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
This generates:
|
|
46
|
-
- `app/models/post.rb` - Model with validations
|
|
47
|
-
- `app/definitions/post_definition.rb` - Definition (empty, uses auto-detection)
|
|
48
|
-
- `app/policies/post_policy.rb` - Policy with sensible defaults
|
|
49
|
-
- `app/controllers/posts_controller.rb` - Controller (empty, inherits CRUD)
|
|
50
|
-
- Migration file
|
|
51
|
-
|
|
52
|
-
See `plutonium-create-resource` skill for full generator options.
|
|
53
|
-
|
|
54
|
-
### From Existing Models
|
|
55
|
-
|
|
56
|
-
For existing Rails projects, you can convert models to Plutonium resources:
|
|
57
|
-
|
|
58
|
-
1. **Include the module** in your model:
|
|
59
|
-
|
|
60
|
-
```ruby
|
|
61
|
-
class Post < ApplicationRecord
|
|
62
|
-
include Plutonium::Resource::Record
|
|
63
|
-
# Your existing code...
|
|
64
|
-
end
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Or inherit from a base class that includes it:
|
|
68
|
-
|
|
69
|
-
```ruby
|
|
70
|
-
class Post < ResourceRecord
|
|
71
|
-
# Your existing code...
|
|
72
|
-
end
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
2. **Generate the supporting files** (definition, policy, controller):
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
rails g pu:res:scaffold Post --no-migration
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
This creates definition, policy, and controller without touching your existing model.
|
|
82
|
-
|
|
83
|
-
3. **Connect to a portal**:
|
|
84
|
-
|
|
85
|
-
```bash
|
|
86
|
-
rails g pu:res:conn Post --dest=admin_portal
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Connecting to Portals
|
|
90
|
-
|
|
91
|
-
Resources must be connected to a portal to be accessible:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
rails g pu:res:conn Post --dest=admin_portal
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
This:
|
|
98
|
-
- Registers the resource in portal routes
|
|
99
|
-
- Creates portal-specific controller
|
|
100
|
-
- Creates portal-specific policy with attribute permissions
|
|
101
|
-
|
|
102
|
-
See `plutonium-connect-resource` skill for details.
|
|
103
|
-
|
|
104
|
-
## Layer Responsibilities
|
|
105
|
-
|
|
106
|
-
### Model (Data Layer)
|
|
107
|
-
|
|
108
|
-
```ruby
|
|
109
|
-
class Post < ResourceRecord
|
|
110
|
-
belongs_to :author, class_name: "User"
|
|
111
|
-
has_many :comments
|
|
112
|
-
|
|
113
|
-
validates :title, presence: true
|
|
114
|
-
|
|
115
|
-
scope :published, -> { where(published: true) }
|
|
116
|
-
end
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
The model handles:
|
|
120
|
-
- Database schema and associations
|
|
121
|
-
- Data validation
|
|
122
|
-
- Business logic scopes
|
|
123
|
-
- Callbacks
|
|
124
|
-
|
|
125
|
-
**Skills:** `plutonium-model`, `plutonium-model-features`
|
|
126
|
-
|
|
127
|
-
### Definition (UI Layer)
|
|
128
|
-
|
|
129
|
-
```ruby
|
|
130
|
-
class PostDefinition < ResourceDefinition
|
|
131
|
-
# Override auto-detected field types
|
|
132
|
-
input :content, as: :markdown
|
|
133
|
-
|
|
134
|
-
# Add filters and scopes
|
|
135
|
-
filter :published, with: Plutonium::Query::Filters::Boolean
|
|
136
|
-
scope :published
|
|
137
|
-
|
|
138
|
-
# Add actions
|
|
139
|
-
action :publish, interaction: PublishPostInteraction
|
|
140
|
-
end
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
The definition handles:
|
|
144
|
-
- Field type overrides (auto-detection handles most cases)
|
|
145
|
-
- Form input customization
|
|
146
|
-
- Display formatting
|
|
147
|
-
- Search, filters, scopes, sorting
|
|
148
|
-
- Actions (interactive operations)
|
|
149
|
-
|
|
150
|
-
**Skills:** `plutonium-definition`, `plutonium-definition-fields`, `plutonium-definition-actions`, `plutonium-definition-query`
|
|
151
|
-
|
|
152
|
-
### Policy (Authorization Layer)
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class PostPolicy < ResourcePolicy
|
|
156
|
-
# Who can perform actions
|
|
157
|
-
def create?
|
|
158
|
-
user.present?
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
def read?
|
|
162
|
-
true
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def publish?
|
|
166
|
-
user.admin? || record.author == user
|
|
167
|
-
end
|
|
168
|
-
|
|
169
|
-
# What records are visible
|
|
170
|
-
relation_scope do |relation|
|
|
171
|
-
return relation if user.admin?
|
|
172
|
-
relation.where(author: user)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
# What attributes are readable/writable
|
|
176
|
-
def permitted_attributes_for_read
|
|
177
|
-
%i[title content published author created_at]
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def permitted_attributes_for_create
|
|
181
|
-
%i[title content]
|
|
182
|
-
end
|
|
183
|
-
end
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
The policy handles:
|
|
187
|
-
- Action authorization (create?, update?, destroy?, custom actions)
|
|
188
|
-
- Resource scoping (what records user can see)
|
|
189
|
-
- Attribute permissions (read/write access per field)
|
|
190
|
-
|
|
191
|
-
**Skill:** `plutonium-policy`
|
|
192
|
-
|
|
193
|
-
### Controller (Request Layer)
|
|
194
|
-
|
|
195
|
-
```ruby
|
|
196
|
-
class PostsController < ::ResourceController
|
|
197
|
-
# Empty - all CRUD actions inherited automatically
|
|
198
|
-
end
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
Controllers are usually empty because they inherit full CRUD functionality. Customize only when needed:
|
|
202
|
-
|
|
203
|
-
```ruby
|
|
204
|
-
class PostsController < ::ResourceController
|
|
205
|
-
private
|
|
206
|
-
|
|
207
|
-
def preferred_action_after_submit
|
|
208
|
-
"index" # Redirect to list instead of show
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
The controller handles:
|
|
214
|
-
- Request/response cycle
|
|
215
|
-
- Redirect logic
|
|
216
|
-
- Custom parameter processing
|
|
217
|
-
- Non-standard authorization flows
|
|
218
|
-
|
|
219
|
-
**Skill:** `plutonium-controller`
|
|
220
|
-
|
|
221
|
-
## Auto-Detection
|
|
222
|
-
|
|
223
|
-
Plutonium automatically detects from your model:
|
|
224
|
-
- All database columns with appropriate field types
|
|
225
|
-
- Associations (belongs_to, has_one, has_many)
|
|
226
|
-
- Attachments (Active Storage)
|
|
227
|
-
- Enums
|
|
228
|
-
|
|
229
|
-
**You only need to declare when overriding defaults.**
|
|
230
|
-
|
|
231
|
-
## Portal-Specific Customization
|
|
232
|
-
|
|
233
|
-
Each portal can have its own definition that overrides the base:
|
|
234
|
-
|
|
235
|
-
```ruby
|
|
236
|
-
# Base definition
|
|
237
|
-
class PostDefinition < ResourceDefinition
|
|
238
|
-
scope :published
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
# Admin portal sees more
|
|
242
|
-
class AdminPortal::PostDefinition < ::PostDefinition
|
|
243
|
-
scope :draft
|
|
244
|
-
scope :pending_review
|
|
245
|
-
action :feature, interaction: FeaturePostInteraction
|
|
246
|
-
end
|
|
247
|
-
|
|
248
|
-
# Public portal is restricted
|
|
249
|
-
class PublicPortal::PostDefinition < ::PostDefinition
|
|
250
|
-
# Only published scope, no actions
|
|
251
|
-
end
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
## Workflow Summary
|
|
255
|
-
|
|
256
|
-
1. **Generate** - `rails g pu:res:scaffold Model attributes... --dest=main_app`
|
|
257
|
-
2. **Connect** - `rails g pu:res:conn Model --dest=portal_name`
|
|
258
|
-
3. **Customize** - Edit definition/policy as needed (model rarely needs changes)
|
|
259
|
-
4. **Override per portal** - Create portal-specific definitions when needed
|
|
260
|
-
|
|
261
|
-
## Related Skills
|
|
262
|
-
|
|
263
|
-
- `plutonium-model` - Model structure and organization
|
|
264
|
-
- `plutonium-model-features` - has_cents, associations, scopes, routes
|
|
265
|
-
- `plutonium-definition` - Definition overview and structure
|
|
266
|
-
- `plutonium-definition-fields` - Fields, inputs, displays, columns
|
|
267
|
-
- `plutonium-definition-actions` - Actions and interactions
|
|
268
|
-
- `plutonium-interaction` - Writing interaction classes
|
|
269
|
-
- `plutonium-definition-query` - Search, filters, scopes, sorting
|
|
270
|
-
- `plutonium-policy` - Authorization and permissions
|
|
271
|
-
- `plutonium-controller` - Controller customization
|
|
272
|
-
- `plutonium-views` - Custom pages, displays, tables using Phlex
|
|
273
|
-
- `plutonium-forms` - Custom form templates and field builders
|
|
274
|
-
- `plutonium-assets` - TailwindCSS and component theming
|
|
275
|
-
- `plutonium-package` - Feature and portal packages
|
|
276
|
-
- `plutonium-portal` - Portal configuration and entity scoping
|
|
277
|
-
- `plutonium-nested-resources` - Parent/child routes and scoping
|
|
278
|
-
- `plutonium-installation` - Setting up Plutonium
|
|
279
|
-
- `plutonium-rodauth` - Authentication setup
|
|
280
|
-
- `plutonium-create-resource` - Scaffold generator details
|
|
281
|
-
- `plutonium-connect-resource` - Portal connection details
|