plutonium 0.33.1 → 0.34.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/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# Controller Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for resource controllers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Controllers handle HTTP requests and responses. Plutonium provides a controller module with CRUD actions built-in. You rarely need to customize controllers - definitions handle UI configuration and policies handle authorization.
|
|
8
|
+
|
|
9
|
+
## Base Class
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
# app/controllers/resource_controller.rb (generated during install)
|
|
13
|
+
class ResourceController < ApplicationController
|
|
14
|
+
include Plutonium::Resource::Controller
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# app/controllers/posts_controller.rb (generated per resource)
|
|
18
|
+
class PostsController < ::ResourceController
|
|
19
|
+
# Empty - all CRUD actions inherited
|
|
20
|
+
end
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
For portals:
|
|
24
|
+
|
|
25
|
+
```ruby
|
|
26
|
+
# packages/admin_portal/app/controllers/admin_portal/resource_controller.rb
|
|
27
|
+
module AdminPortal
|
|
28
|
+
class ResourceController < ::ResourceController
|
|
29
|
+
include AdminPortal::Concerns::Controller
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
|
|
34
|
+
module AdminPortal
|
|
35
|
+
class PostsController < ResourceController
|
|
36
|
+
# Portal-specific customizations
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Built-in Actions
|
|
42
|
+
|
|
43
|
+
| Action | HTTP Method | Path | Purpose |
|
|
44
|
+
|--------|-------------|------|---------|
|
|
45
|
+
| `index` | GET | `/posts` | List with pagination, search, filters, sorting |
|
|
46
|
+
| `show` | GET | `/posts/:id` | Show record |
|
|
47
|
+
| `new` | GET | `/posts/new` | New record form |
|
|
48
|
+
| `create` | POST | `/posts` | Create record |
|
|
49
|
+
| `edit` | GET | `/posts/:id/edit` | Edit record form |
|
|
50
|
+
| `update` | PATCH/PUT | `/posts/:id` | Update record |
|
|
51
|
+
| `destroy` | DELETE | `/posts/:id` | Delete record |
|
|
52
|
+
|
|
53
|
+
Plus interactive action routes for custom operations defined in definitions.
|
|
54
|
+
|
|
55
|
+
## Key Methods
|
|
56
|
+
|
|
57
|
+
### Resource Access
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
resource_class # The model class (e.g., Post)
|
|
61
|
+
resource_record! # Current record (raises RecordNotFound if not found)
|
|
62
|
+
resource_record? # Current record (nil if not found)
|
|
63
|
+
resource_params # Permitted params for create/update
|
|
64
|
+
current_parent # Parent record for nested routes
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Authorization
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
authorize_current!(record, to: :action?) # Check permission
|
|
71
|
+
current_policy # Policy for current resource
|
|
72
|
+
permitted_attributes # Allowed attributes for action
|
|
73
|
+
current_authorized_scope # Scoped records user can access
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Definition Access
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
current_definition # Definition for current resource
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### UI Building
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
build_form # Build form component
|
|
86
|
+
build_detail # Build show/detail component
|
|
87
|
+
build_collection # Build table component
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### URL Generation
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
resource_url_for(@post) # URL for record
|
|
94
|
+
resource_url_for(@post, action: :edit) # Edit URL
|
|
95
|
+
resource_url_for(Post) # Index URL
|
|
96
|
+
resource_url_for(Post, parent: @user) # Nested index URL
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Customization Hooks
|
|
100
|
+
|
|
101
|
+
All customization is done by overriding private methods.
|
|
102
|
+
|
|
103
|
+
### Redirect Hooks
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class PostsController < ::ResourceController
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
# Where to go after create/update: "show" (default), "edit", "new", "index"
|
|
110
|
+
def preferred_action_after_submit
|
|
111
|
+
"edit"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Custom URL after create/update (overrides preferred_action_after_submit)
|
|
115
|
+
def redirect_url_after_submit
|
|
116
|
+
resource_url_for(resource_class)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Custom URL after destroy
|
|
120
|
+
def redirect_url_after_destroy
|
|
121
|
+
resource_url_for(resource_class)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Parameter Hooks
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
class PostsController < ::ResourceController
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Modify params before create/update
|
|
133
|
+
def resource_params
|
|
134
|
+
params = super
|
|
135
|
+
params[:tags] = params[:tags].split(",") if params[:tags].is_a?(String)
|
|
136
|
+
params
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Query Hooks
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
class PostsController < ::ResourceController
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Customize the index query
|
|
148
|
+
def filtered_resource_collection
|
|
149
|
+
base = current_authorized_scope
|
|
150
|
+
base = base.featured if params[:featured]
|
|
151
|
+
current_query_object.apply(base, raw_resource_query_params)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Presentation Hooks
|
|
157
|
+
|
|
158
|
+
Control whether parent/entity fields appear in forms and displays:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
class PostsController < ::ResourceController
|
|
162
|
+
private
|
|
163
|
+
|
|
164
|
+
# Show parent field in displays (default: false)
|
|
165
|
+
def present_parent?
|
|
166
|
+
true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Include parent field in forms (default: same as present_parent?)
|
|
170
|
+
def submit_parent?
|
|
171
|
+
true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Show scoped entity in displays (default: false)
|
|
175
|
+
def present_scoped_entity?
|
|
176
|
+
true
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# Include scoped entity in forms (default: same as present_scoped_entity?)
|
|
180
|
+
def submit_scoped_entity?
|
|
181
|
+
true
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## Lifecycle Callbacks
|
|
187
|
+
|
|
188
|
+
Use standard Rails callbacks:
|
|
189
|
+
|
|
190
|
+
```ruby
|
|
191
|
+
class PostsController < ::ResourceController
|
|
192
|
+
before_action :check_quota, only: [:create]
|
|
193
|
+
|
|
194
|
+
private
|
|
195
|
+
|
|
196
|
+
def check_quota
|
|
197
|
+
if current_user.posts.count >= 100
|
|
198
|
+
redirect_to resource_url_for(resource_class), alert: "Post limit reached"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Custom Actions
|
|
205
|
+
|
|
206
|
+
For most custom operations, use Interactive Actions in definitions. When you need a custom controller action:
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
class PostsController < ::ResourceController
|
|
210
|
+
def publish
|
|
211
|
+
authorize_current!(resource_record!, to: :publish?)
|
|
212
|
+
resource_record!.update!(published: true)
|
|
213
|
+
redirect_to resource_url_for(resource_record!), notice: "Published!"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Routes for Custom Actions
|
|
219
|
+
|
|
220
|
+
```ruby
|
|
221
|
+
# In portal routes or config/routes.rb
|
|
222
|
+
register_resource Post do
|
|
223
|
+
member do
|
|
224
|
+
post :publish
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Authorization
|
|
230
|
+
|
|
231
|
+
### Automatic Authorization
|
|
232
|
+
|
|
233
|
+
Authorization is checked automatically for standard CRUD actions via `authorize_current!`.
|
|
234
|
+
|
|
235
|
+
### Authorization Verification
|
|
236
|
+
|
|
237
|
+
Controllers verify authorization was performed after every action:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
# These run after every action
|
|
241
|
+
verify_authorize_current # Ensures authorize_current! was called
|
|
242
|
+
verify_current_authorized_scope # Ensures scope was loaded (except new/create)
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Skip Authorization Verification
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
class PostsController < ::ResourceController
|
|
249
|
+
skip_verify_authorize_current only: [:preview]
|
|
250
|
+
skip_verify_current_authorized_scope only: [:preview]
|
|
251
|
+
|
|
252
|
+
def preview
|
|
253
|
+
# Handle authorization manually or skip it
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Nested Resources
|
|
259
|
+
|
|
260
|
+
Parent records are automatically resolved:
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
# Route: /users/:user_id/posts/:id
|
|
264
|
+
class PostsController < ::ResourceController
|
|
265
|
+
# current_parent returns the User
|
|
266
|
+
# resource_record! returns the Post scoped to that User
|
|
267
|
+
end
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Parent fields are automatically excluded from forms/displays. Override with presentation hooks.
|
|
271
|
+
|
|
272
|
+
### Parent-Related Methods
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
current_parent # The parent record (e.g., User)
|
|
276
|
+
parent_route_param # The route param key (e.g., :user_id)
|
|
277
|
+
parent_input_param # The association name (e.g., :user)
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Entity Scoping (Multi-tenancy)
|
|
281
|
+
|
|
282
|
+
When a portal is scoped to an entity:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
# packages/customer_portal/lib/engine.rb
|
|
286
|
+
module CustomerPortal
|
|
287
|
+
class Engine < Rails::Engine
|
|
288
|
+
include Plutonium::Portal::Engine
|
|
289
|
+
|
|
290
|
+
config.after_initialize do
|
|
291
|
+
scope_to_entity Organization
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Controllers automatically:
|
|
298
|
+
- Scope all queries to the entity
|
|
299
|
+
- Exclude entity field from forms
|
|
300
|
+
- Provide `current_scoped_entity` method
|
|
301
|
+
|
|
302
|
+
## Specifying Resource Class
|
|
303
|
+
|
|
304
|
+
The resource class is inferred from the controller name. Override if needed:
|
|
305
|
+
|
|
306
|
+
```ruby
|
|
307
|
+
class LegacyPostsController < ::ResourceController
|
|
308
|
+
controller_for Post
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Response Formats
|
|
313
|
+
|
|
314
|
+
Controllers respond to multiple formats:
|
|
315
|
+
|
|
316
|
+
- HTML (default)
|
|
317
|
+
- JSON (via RABL templates)
|
|
318
|
+
- Turbo Stream (for Hotwire)
|
|
319
|
+
|
|
320
|
+
## Error Handling
|
|
321
|
+
|
|
322
|
+
```ruby
|
|
323
|
+
class PostsController < ::ResourceController
|
|
324
|
+
rescue_from ActiveRecord::RecordNotFound do
|
|
325
|
+
redirect_to resource_url_for(resource_class), alert: "Post not found"
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
rescue_from ActionPolicy::Unauthorized do
|
|
329
|
+
redirect_to resource_url_for(resource_class), alert: "Not authorized"
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## Portal-Specific Controllers
|
|
335
|
+
|
|
336
|
+
Each portal can have its own controller override:
|
|
337
|
+
|
|
338
|
+
```ruby
|
|
339
|
+
# packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
|
|
340
|
+
module AdminPortal
|
|
341
|
+
class PostsController < ResourceController
|
|
342
|
+
private
|
|
343
|
+
|
|
344
|
+
def preferred_action_after_submit
|
|
345
|
+
"index" # Admin prefers list view
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Best Practices
|
|
352
|
+
|
|
353
|
+
1. **Keep controllers thin** - Use definitions for UI, policies for auth, interactions for logic
|
|
354
|
+
2. **Don't override CRUD actions** - Customize via hooks (`resource_params`, `redirect_url_after_submit`)
|
|
355
|
+
3. **Use interactive actions** - For custom operations, define in definition with interaction
|
|
356
|
+
4. **Let authorization work** - Don't skip verification without good reason
|
|
357
|
+
5. **Trust the framework** - Most customization belongs in definitions or policies
|
|
358
|
+
|
|
359
|
+
## Related
|
|
360
|
+
|
|
361
|
+
- [Definition Reference](/reference/definition/) - UI configuration
|
|
362
|
+
- [Policy Reference](/reference/policy/) - Authorization
|
|
363
|
+
- [Actions Reference](/reference/definition/actions) - Interactive actions
|