plutonium 0.37.0 → 0.39.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-controller/SKILL.md +38 -2
- data/.claude/skills/plutonium-definition-actions/SKILL.md +13 -0
- data/.claude/skills/plutonium-definition-fields/SKILL.md +33 -0
- data/.claude/skills/plutonium-nested-resources/SKILL.md +85 -23
- data/.claude/skills/plutonium-policy/SKILL.md +93 -6
- data/CHANGELOG.md +42 -0
- data/CLAUDE.md +8 -10
- data/CONTRIBUTING.md +6 -8
- data/Rakefile +16 -1
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +9371 -11492
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +55 -55
- data/app/assets/plutonium.min.js.map +4 -4
- data/docs/guides/custom-actions.md +14 -0
- data/docs/guides/index.md +5 -0
- data/docs/guides/nested-resources.md +139 -32
- data/docs/guides/troubleshooting.md +82 -0
- data/docs/og-image.html +84 -0
- data/docs/public/og-image.png +0 -0
- data/docs/reference/controller/index.md +6 -2
- data/docs/reference/definition/actions.md +14 -0
- data/docs/reference/definition/fields.md +33 -0
- data/docs/reference/model/index.md +1 -1
- data/docs/reference/policy/index.md +77 -6
- data/gemfiles/rails_7.gemfile.lock +5 -5
- data/gemfiles/rails_8.0.gemfile.lock +5 -5
- data/gemfiles/rails_8.1.gemfile.lock +5 -5
- data/lib/generators/pu/rodauth/install_generator.rb +7 -11
- data/lib/generators/pu/rodauth/templates/app/rodauth/rodauth_plugin.rb.tt +3 -5
- data/lib/plutonium/auth/sequel_adapter.rb +76 -0
- data/lib/plutonium/core/controller.rb +143 -19
- data/lib/plutonium/core/controllers/association_resolver.rb +86 -0
- data/lib/plutonium/helpers/display_helper.rb +12 -0
- data/lib/plutonium/query/filters/association.rb +25 -3
- data/lib/plutonium/resource/controller.rb +91 -9
- data/lib/plutonium/resource/controllers/authorizable.rb +17 -4
- data/lib/plutonium/resource/controllers/crud_actions.rb +7 -5
- data/lib/plutonium/resource/controllers/interactive_actions.rb +9 -0
- data/lib/plutonium/resource/controllers/presentable.rb +15 -11
- data/lib/plutonium/resource/policy.rb +85 -2
- data/lib/plutonium/resource/record/routes.rb +31 -1
- data/lib/plutonium/routing/mapper_extensions.rb +49 -10
- data/lib/plutonium/routing/route_set_extensions.rb +3 -0
- data/lib/plutonium/ui/action_button.rb +72 -11
- data/lib/plutonium/ui/actions_dropdown.rb +3 -25
- data/lib/plutonium/ui/breadcrumbs.rb +2 -2
- data/lib/plutonium/ui/component/methods.rb +10 -3
- data/lib/plutonium/ui/display/resource.rb +5 -2
- data/lib/plutonium/ui/form/base.rb +1 -1
- data/lib/plutonium/ui/form/components/key_value_store.rb +17 -5
- data/lib/plutonium/ui/form/interaction.rb +5 -5
- data/lib/plutonium/ui/form/query.rb +1 -1
- data/lib/plutonium/ui/form/resource.rb +1 -1
- data/lib/plutonium/ui/layout/base.rb +1 -1
- data/lib/plutonium/ui/layout/basic_layout.rb +2 -2
- data/lib/plutonium/ui/layout/resource_layout.rb +2 -2
- data/lib/plutonium/ui/layout/rodauth_layout.rb +2 -2
- data/lib/plutonium/ui/page/index.rb +1 -1
- data/lib/plutonium/ui/page/interactive_action.rb +1 -1
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +3 -25
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +1 -1
- data/package.json +6 -5
- data/plutonium.gemspec +2 -2
- data/src/js/controllers/key_value_store_controller.js +6 -0
- data/src/js/controllers/resource_drop_down_controller.js +3 -3
- data/yarn.lock +1465 -693
- metadata +10 -7
- data/app/javascript/controllers/key_value_store_controller.js +0 -119
|
@@ -39,6 +39,20 @@ class PostDefinition < ResourceDefinition
|
|
|
39
39
|
end
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
::: warning Always Name Custom Routes
|
|
43
|
+
When adding custom routes for actions, always use the `as:` option:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
resources :posts do
|
|
47
|
+
collection do
|
|
48
|
+
get :reports, as: :reports # Named route required!
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
|
|
54
|
+
:::
|
|
55
|
+
|
|
42
56
|
**Note:** For custom operations with business logic, use Interactive Actions with an Interaction class.
|
|
43
57
|
|
|
44
58
|
## Interactive Actions with Interactions
|
data/docs/guides/index.md
CHANGED
|
@@ -27,6 +27,10 @@ These guides show you how to accomplish specific tasks, with complete examples.
|
|
|
27
27
|
|
|
28
28
|
- [Theming](./theming) - Customize colors, styles, and branding
|
|
29
29
|
|
|
30
|
+
### Help
|
|
31
|
+
|
|
32
|
+
- [Troubleshooting](./troubleshooting) - Common issues and solutions
|
|
33
|
+
|
|
30
34
|
## Finding What You Need
|
|
31
35
|
|
|
32
36
|
| I want to... | Guide |
|
|
@@ -39,6 +43,7 @@ These guides show you how to accomplish specific tasks, with complete examples.
|
|
|
39
43
|
| Separate data by company | [Multi-tenancy](./multi-tenancy) |
|
|
40
44
|
| Add search to a list | [Search and Filtering](./search-filtering) |
|
|
41
45
|
| Change the color scheme | [Theming](./theming) |
|
|
46
|
+
| Fix a confusing error | [Troubleshooting](./troubleshooting) |
|
|
42
47
|
|
|
43
48
|
## Looking for Reference Docs?
|
|
44
49
|
|
|
@@ -4,7 +4,7 @@ This guide covers setting up parent/child resource relationships.
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
Nested resources create URLs like `/posts/1/
|
|
7
|
+
Nested resources create URLs like `/posts/1/nested_comments` where comments belong to a specific post. Plutonium automatically handles:
|
|
8
8
|
|
|
9
9
|
- Scoping queries to the parent
|
|
10
10
|
- Assigning parent to new records
|
|
@@ -12,6 +12,8 @@ Nested resources create URLs like `/posts/1/comments` where comments belong to a
|
|
|
12
12
|
- URL generation with parent context
|
|
13
13
|
- Breadcrumb navigation
|
|
14
14
|
|
|
15
|
+
Plutonium supports both `has_many` (plural routes) and `has_one` (singular routes) associations.
|
|
16
|
+
|
|
15
17
|
## Setting Up Nested Resources
|
|
16
18
|
|
|
17
19
|
### 1. Define the Association
|
|
@@ -20,12 +22,17 @@ Nested resources create URLs like `/posts/1/comments` where comments belong to a
|
|
|
20
22
|
# Parent model
|
|
21
23
|
class Post < ResourceRecord
|
|
22
24
|
has_many :comments, dependent: :destroy
|
|
25
|
+
has_one :post_metadata, dependent: :destroy
|
|
23
26
|
end
|
|
24
27
|
|
|
25
|
-
# Child
|
|
28
|
+
# Child models
|
|
26
29
|
class Comment < ResourceRecord
|
|
27
30
|
belongs_to :post
|
|
28
31
|
end
|
|
32
|
+
|
|
33
|
+
class PostMetadata < ResourceRecord
|
|
34
|
+
belongs_to :post
|
|
35
|
+
end
|
|
29
36
|
```
|
|
30
37
|
|
|
31
38
|
### 2. Register Both Resources
|
|
@@ -35,15 +42,25 @@ end
|
|
|
35
42
|
AdminPortal::Engine.routes.draw do
|
|
36
43
|
register_resource ::Post
|
|
37
44
|
register_resource ::Comment
|
|
45
|
+
register_resource ::PostMetadata
|
|
38
46
|
end
|
|
39
47
|
```
|
|
40
48
|
|
|
41
|
-
Plutonium automatically creates nested routes based on the `belongs_to` association:
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- `GET /posts/:post_id/
|
|
49
|
+
Plutonium automatically creates nested routes with a `nested_` prefix based on the `belongs_to` association:
|
|
50
|
+
|
|
51
|
+
**has_many routes (plural):**
|
|
52
|
+
- `GET /posts/:post_id/nested_comments`
|
|
53
|
+
- `GET /posts/:post_id/nested_comments/new`
|
|
54
|
+
- `GET /posts/:post_id/nested_comments/:id`
|
|
45
55
|
- etc.
|
|
46
56
|
|
|
57
|
+
**has_one routes (singular):**
|
|
58
|
+
- `GET /posts/:post_id/nested_post_metadata`
|
|
59
|
+
- `GET /posts/:post_id/nested_post_metadata/new`
|
|
60
|
+
- `GET /posts/:post_id/nested_post_metadata/edit`
|
|
61
|
+
|
|
62
|
+
The `nested_` prefix prevents route conflicts when the same resource is registered both as a top-level and nested resource.
|
|
63
|
+
|
|
47
64
|
### 3. Enable Association Panel
|
|
48
65
|
|
|
49
66
|
Show comments on the post detail page:
|
|
@@ -112,30 +129,46 @@ parent_input_param # => :post
|
|
|
112
129
|
Use `resource_url_for` with the `parent:` option:
|
|
113
130
|
|
|
114
131
|
```ruby
|
|
115
|
-
# Child collection
|
|
132
|
+
# Child collection (has_many)
|
|
116
133
|
resource_url_for(Comment, parent: @post)
|
|
117
|
-
# => /posts/123/
|
|
134
|
+
# => /posts/123/nested_comments
|
|
118
135
|
|
|
119
136
|
# Child record
|
|
120
137
|
resource_url_for(@comment, parent: @post)
|
|
121
|
-
# => /posts/123/
|
|
138
|
+
# => /posts/123/nested_comments/456
|
|
122
139
|
|
|
123
140
|
# New child form
|
|
124
141
|
resource_url_for(Comment, action: :new, parent: @post)
|
|
125
|
-
# => /posts/123/
|
|
142
|
+
# => /posts/123/nested_comments/new
|
|
126
143
|
|
|
127
144
|
# Edit child
|
|
128
145
|
resource_url_for(@comment, action: :edit, parent: @post)
|
|
129
|
-
# => /posts/123/
|
|
146
|
+
# => /posts/123/nested_comments/456/edit
|
|
147
|
+
|
|
148
|
+
# Singular resource (has_one)
|
|
149
|
+
resource_url_for(@post_metadata, parent: @post)
|
|
150
|
+
# => /posts/123/nested_post_metadata
|
|
151
|
+
|
|
152
|
+
resource_url_for(PostMetadata, action: :new, parent: @post)
|
|
153
|
+
# => /posts/123/nested_post_metadata/new
|
|
130
154
|
```
|
|
131
155
|
|
|
132
156
|
Within a nested context, `parent:` defaults to `current_parent`:
|
|
133
157
|
|
|
134
158
|
```ruby
|
|
135
|
-
# In CommentsController under /posts/:post_id/
|
|
159
|
+
# In CommentsController under /posts/:post_id/nested_comments
|
|
136
160
|
resource_url_for(@comment) # parent: current_parent is automatic
|
|
137
161
|
```
|
|
138
162
|
|
|
163
|
+
### Cross-Package URL Generation
|
|
164
|
+
|
|
165
|
+
Generate URLs for resources in a different package:
|
|
166
|
+
|
|
167
|
+
```ruby
|
|
168
|
+
# From AdminPortal, generate URL to CustomerPortal resource
|
|
169
|
+
resource_url_for(@comment, parent: @post, package: CustomerPortal)
|
|
170
|
+
```
|
|
171
|
+
|
|
139
172
|
## Presentation Hooks
|
|
140
173
|
|
|
141
174
|
Control whether the parent field appears:
|
|
@@ -167,23 +200,47 @@ The parent is authorized for `:read?` before being returned:
|
|
|
167
200
|
authorize! parent, to: :read?
|
|
168
201
|
```
|
|
169
202
|
|
|
170
|
-
###
|
|
203
|
+
### Parent Scoping Context
|
|
171
204
|
|
|
172
|
-
|
|
205
|
+
For nested resources, policies receive `parent` and `parent_association` context. This is used for automatic query scoping:
|
|
173
206
|
|
|
174
207
|
```ruby
|
|
175
208
|
class CommentPolicy < ResourcePolicy
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
209
|
+
# Available context:
|
|
210
|
+
# - parent: the parent record (e.g., Post instance)
|
|
211
|
+
# - parent_association: the association name (e.g., :comments)
|
|
212
|
+
# - entity_scope: the scoped entity (for multi-tenancy)
|
|
180
213
|
|
|
181
|
-
|
|
182
|
-
|
|
214
|
+
relation_scope do |relation|
|
|
215
|
+
relation = super(relation) # Applies parent scoping automatically
|
|
216
|
+
relation
|
|
183
217
|
end
|
|
218
|
+
end
|
|
219
|
+
```
|
|
184
220
|
|
|
185
|
-
|
|
186
|
-
|
|
221
|
+
**Parent scoping takes precedence over entity scoping** - when a parent is present, the policy scopes via the parent association rather than the entity scope. This prevents double-scoping since the parent was already authorized and entity-scoped.
|
|
222
|
+
|
|
223
|
+
### has_many vs has_one Scoping
|
|
224
|
+
|
|
225
|
+
For **has_many** associations, scoping uses the association directly:
|
|
226
|
+
```ruby
|
|
227
|
+
parent.send(parent_association) # e.g., post.comments
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
For **has_one** associations, scoping uses a where clause:
|
|
231
|
+
```ruby
|
|
232
|
+
relation.where(foreign_key => parent.id) # e.g., where(post_id: post.id)
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Entity Scope Fallback
|
|
236
|
+
|
|
237
|
+
When no parent is present (top-level resource access), entity_scope is used:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
class CommentPolicy < ResourcePolicy
|
|
241
|
+
def create?
|
|
242
|
+
# entity_scope is available for multi-tenancy
|
|
243
|
+
entity_scope.present? && user.can_comment_on?(entity_scope)
|
|
187
244
|
end
|
|
188
245
|
end
|
|
189
246
|
```
|
|
@@ -195,7 +252,7 @@ Add role-based filtering on top of parent scoping:
|
|
|
195
252
|
```ruby
|
|
196
253
|
class CommentPolicy < ResourcePolicy
|
|
197
254
|
relation_scope do |relation|
|
|
198
|
-
relation = super(relation) # Applies
|
|
255
|
+
relation = super(relation) # Applies parent scoping first
|
|
199
256
|
|
|
200
257
|
if user.moderator?
|
|
201
258
|
relation
|
|
@@ -206,6 +263,33 @@ class CommentPolicy < ResourcePolicy
|
|
|
206
263
|
end
|
|
207
264
|
```
|
|
208
265
|
|
|
266
|
+
### default_relation_scope is Required
|
|
267
|
+
|
|
268
|
+
Plutonium verifies that `default_relation_scope` is called in every `relation_scope` to prevent multi-tenancy leaks:
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
# ❌ This will raise an error
|
|
272
|
+
relation_scope do |relation|
|
|
273
|
+
relation.where(approved: true) # Missing default_relation_scope!
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# ✅ Correct
|
|
277
|
+
relation_scope do |relation|
|
|
278
|
+
default_relation_scope(relation).where(approved: true)
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
When overriding an inherited scope but still wanting parent scoping:
|
|
283
|
+
|
|
284
|
+
```ruby
|
|
285
|
+
class AdminCommentPolicy < CommentPolicy
|
|
286
|
+
relation_scope do |relation|
|
|
287
|
+
# Replace inherited scope but keep parent scoping
|
|
288
|
+
default_relation_scope(relation)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
```
|
|
292
|
+
|
|
209
293
|
## Association Panels
|
|
210
294
|
|
|
211
295
|
Associations listed in `permitted_associations` appear on the parent's show page:
|
|
@@ -257,15 +341,34 @@ class PostPolicy < ResourcePolicy
|
|
|
257
341
|
end
|
|
258
342
|
```
|
|
259
343
|
|
|
344
|
+
## has_one Associations
|
|
345
|
+
|
|
346
|
+
Plutonium supports `has_one` associations with singular routes:
|
|
347
|
+
|
|
348
|
+
```ruby
|
|
349
|
+
class Post < ResourceRecord
|
|
350
|
+
has_one :post_metadata, dependent: :destroy
|
|
351
|
+
end
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
Routes generated:
|
|
355
|
+
- `GET /posts/:post_id/nested_post_metadata` - Show metadata
|
|
356
|
+
- `GET /posts/:post_id/nested_post_metadata/new` - New metadata form
|
|
357
|
+
- `GET /posts/:post_id/nested_post_metadata/edit` - Edit metadata form
|
|
358
|
+
- `PATCH /posts/:post_id/nested_post_metadata` - Update metadata
|
|
359
|
+
- `DELETE /posts/:post_id/nested_post_metadata` - Delete metadata
|
|
360
|
+
|
|
361
|
+
Note: No `:id` parameter in singular routes - only one record can exist per parent.
|
|
362
|
+
|
|
260
363
|
## Nesting Depth
|
|
261
364
|
|
|
262
365
|
Plutonium supports **one level of nesting**:
|
|
263
366
|
|
|
264
|
-
- `/posts/:post_id/
|
|
265
|
-
- `/comments/:comment_id/
|
|
367
|
+
- `/posts/:post_id/nested_comments` (parent → child)
|
|
368
|
+
- `/comments/:comment_id/nested_replies` (parent → child)
|
|
266
369
|
|
|
267
370
|
Not supported:
|
|
268
|
-
- `/posts/:post_id/
|
|
371
|
+
- `/posts/:post_id/nested_comments/:comment_id/nested_replies` (grandparent → parent → child)
|
|
269
372
|
|
|
270
373
|
### Working with Deep Hierarchies
|
|
271
374
|
|
|
@@ -285,19 +388,23 @@ Add member/collection routes:
|
|
|
285
388
|
```ruby
|
|
286
389
|
register_resource ::Comment do
|
|
287
390
|
member do
|
|
288
|
-
post :approve
|
|
289
|
-
post :flag
|
|
391
|
+
post :approve, as: :approve
|
|
392
|
+
post :flag, as: :flag
|
|
290
393
|
end
|
|
291
394
|
collection do
|
|
292
|
-
get :pending
|
|
395
|
+
get :pending, as: :pending
|
|
293
396
|
end
|
|
294
397
|
end
|
|
295
398
|
```
|
|
296
399
|
|
|
400
|
+
::: warning Always Name Custom Routes
|
|
401
|
+
Always use the `as:` option when defining custom routes. This ensures `resource_url_for` can generate correct URLs. Without named routes, URL generation will fail for nested resources.
|
|
402
|
+
:::
|
|
403
|
+
|
|
297
404
|
Generates nested routes:
|
|
298
|
-
- `POST /posts/:post_id/
|
|
299
|
-
- `POST /posts/:post_id/
|
|
300
|
-
- `GET /posts/:post_id/
|
|
405
|
+
- `POST /posts/:post_id/nested_comments/:id/approve`
|
|
406
|
+
- `POST /posts/:post_id/nested_comments/:id/flag`
|
|
407
|
+
- `GET /posts/:post_id/nested_comments/pending`
|
|
301
408
|
|
|
302
409
|
## Breadcrumbs
|
|
303
410
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Common issues and their solutions when working with Plutonium.
|
|
4
|
+
|
|
5
|
+
## Resource Class Detection
|
|
6
|
+
|
|
7
|
+
### "Failed to determine the resource class" Error
|
|
8
|
+
|
|
9
|
+
**Error messages:**
|
|
10
|
+
```
|
|
11
|
+
NameError: Failed to determine the resource class for MyPortal::PostMetadataController.
|
|
12
|
+
Rails singularized "PostMetadata" to "PostMetadatum", but "PostMetadata" exists.
|
|
13
|
+
Add an inflection rule to config/initializers/inflections.rb.
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
or:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
NameError: Failed to determine the resource class.
|
|
20
|
+
Please call `controller_for(MyResource)` in MyPortal::MyResourceController.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**Cause:** Plutonium infers the resource class from the controller name by singularizing it. For resources with names that don't follow standard Rails pluralization (like `PostMetadata`), Rails may singularize incorrectly (`PostMetadata` → `PostMetadatum`). Plutonium detects this and provides a helpful error message.
|
|
24
|
+
|
|
25
|
+
**Solution:** Add a custom inflection rule in `config/initializers/inflections.rb`:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
29
|
+
# Preserve "Metadata" when singularizing (e.g., PostMetadata stays PostMetadata)
|
|
30
|
+
inflect.singular(/(M)etadata$/i, '\1etadata')
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
This ensures Rails correctly handles the singularization throughout your application, including:
|
|
35
|
+
- Controller to model resolution
|
|
36
|
+
- Route generation
|
|
37
|
+
- Association lookups
|
|
38
|
+
|
|
39
|
+
**Alternative:** If you can't modify inflections, explicitly set the resource class in your controller:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
class MyPortal::PostMetadataController < MyPortal::ResourceController
|
|
43
|
+
controller_for Blogging::PostMetadata
|
|
44
|
+
end
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Common Words Needing Inflection Rules
|
|
48
|
+
|
|
49
|
+
| Word | Without Rule | With Rule | Inflection |
|
|
50
|
+
|------|--------------|-----------|------------|
|
|
51
|
+
| Metadata | `PostMetadatum` | `PostMetadata` | `inflect.singular(/(M)etadata$/i, '\1etadata')` |
|
|
52
|
+
| Media | `PostMedium` | `PostMedia` | `inflect.singular(/(M)edia$/i, '\1edia')` |
|
|
53
|
+
| Data | `PostDatum` | `PostData` | `inflect.singular(/(D)ata$/i, '\1ata')` |
|
|
54
|
+
| Criteria | `SearchCriterium` | `SearchCriteria` | `inflect.singular(/(C)riteria$/i, '\1riteria')` |
|
|
55
|
+
|
|
56
|
+
## URL Generation
|
|
57
|
+
|
|
58
|
+
### Wrong URLs for Nested Resources
|
|
59
|
+
|
|
60
|
+
**Symptom:** URLs for nested resources include unexpected IDs or route to the wrong path.
|
|
61
|
+
|
|
62
|
+
**Cause:** Rails "param recall" fills in missing parameters from the current request when generating URLs. This can cause issues when both top-level and nested routes exist for the same resource.
|
|
63
|
+
|
|
64
|
+
**Solution:** Plutonium handles this automatically by using named route helpers for nested resources. Ensure you're using `resource_url_for` instead of `url_for`:
|
|
65
|
+
|
|
66
|
+
```ruby
|
|
67
|
+
# Good - uses Plutonium's smart URL generation
|
|
68
|
+
resource_url_for(@comment, parent: @post)
|
|
69
|
+
|
|
70
|
+
# May have issues with param recall
|
|
71
|
+
url_for(controller: 'comments', action: 'show', id: @comment.id)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Need More Help?
|
|
75
|
+
|
|
76
|
+
If you encounter an issue not covered here, please [open an issue](https://github.com/radioactive-labs/plutonium-core/issues) on GitHub.
|
|
77
|
+
|
|
78
|
+
## Related
|
|
79
|
+
|
|
80
|
+
- [Nested Resources Guide](./nested-resources)
|
|
81
|
+
- [Adding Resources Guide](./adding-resources)
|
|
82
|
+
- [Rails Inflections Documentation](https://api.rubyonrails.org/classes/ActiveSupport/Inflector/Inflections.html)
|
data/docs/og-image.html
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<style>
|
|
6
|
+
* {
|
|
7
|
+
margin: 0;
|
|
8
|
+
padding: 0;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
width: 1200px;
|
|
14
|
+
height: 630px;
|
|
15
|
+
background: #1e2330;
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.container {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: row;
|
|
25
|
+
align-items: center;
|
|
26
|
+
gap: 60px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.logo {
|
|
30
|
+
width: 260px;
|
|
31
|
+
height: 260px;
|
|
32
|
+
object-fit: contain;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.content {
|
|
36
|
+
display: flex;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: 16px;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.title {
|
|
42
|
+
font-size: 88px;
|
|
43
|
+
font-weight: 700;
|
|
44
|
+
color: #ffffff;
|
|
45
|
+
letter-spacing: -1px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.tagline {
|
|
49
|
+
font-size: 40px;
|
|
50
|
+
color: #b8bcc8;
|
|
51
|
+
font-weight: 400;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.features {
|
|
55
|
+
display: flex;
|
|
56
|
+
gap: 24px;
|
|
57
|
+
margin-top: 12px;
|
|
58
|
+
font-size: 26px;
|
|
59
|
+
color: #e07c5a;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.features span:not(:last-child)::after {
|
|
64
|
+
content: '·';
|
|
65
|
+
margin-left: 24px;
|
|
66
|
+
color: #e07c5a;
|
|
67
|
+
}
|
|
68
|
+
</style>
|
|
69
|
+
</head>
|
|
70
|
+
<body>
|
|
71
|
+
<div class="container">
|
|
72
|
+
<img src="public/plutonium.png" alt="Plutonium" class="logo">
|
|
73
|
+
<div class="content">
|
|
74
|
+
<div class="title">Plutonium</div>
|
|
75
|
+
<div class="tagline">Build Rails Apps in Minutes, Not Days</div>
|
|
76
|
+
<div class="features">
|
|
77
|
+
<span>Convention-driven</span>
|
|
78
|
+
<span>AI-ready</span>
|
|
79
|
+
<span>Fully customizable</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</body>
|
|
84
|
+
</html>
|
data/docs/public/og-image.png
CHANGED
|
Binary file
|
|
@@ -219,11 +219,15 @@ end
|
|
|
219
219
|
# In portal routes or config/routes.rb
|
|
220
220
|
register_resource Post do
|
|
221
221
|
member do
|
|
222
|
-
post :publish
|
|
222
|
+
post :publish, as: :publish # Always use as: option!
|
|
223
223
|
end
|
|
224
224
|
end
|
|
225
225
|
```
|
|
226
226
|
|
|
227
|
+
::: warning Always Name Custom Routes
|
|
228
|
+
Always use the `as:` option when defining custom routes. This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
|
|
229
|
+
:::
|
|
230
|
+
|
|
227
231
|
## Authorization
|
|
228
232
|
|
|
229
233
|
### Automatic Authorization
|
|
@@ -258,7 +262,7 @@ end
|
|
|
258
262
|
Parent records are automatically resolved:
|
|
259
263
|
|
|
260
264
|
```ruby
|
|
261
|
-
# Route: /users/:user_id/
|
|
265
|
+
# Route: /users/:user_id/nested_posts/:id
|
|
262
266
|
class PostsController < ::ResourceController
|
|
263
267
|
# current_parent returns the User
|
|
264
268
|
# resource_record! returns the Post scoped to that User
|
|
@@ -39,6 +39,20 @@ class PostDefinition < Plutonium::Resource::Definition
|
|
|
39
39
|
end
|
|
40
40
|
```
|
|
41
41
|
|
|
42
|
+
::: warning Always Name Custom Routes
|
|
43
|
+
When adding custom routes for actions, always use the `as:` option:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
resources :posts do
|
|
47
|
+
collection do
|
|
48
|
+
get :reports, as: :reports # Named route required!
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
|
|
54
|
+
:::
|
|
55
|
+
|
|
42
56
|
**Note:** For custom operations with business logic, use **Interactive Actions** with an Interaction class.
|
|
43
57
|
|
|
44
58
|
## Interactive Actions
|
|
@@ -232,6 +232,39 @@ column :status, align: :center # Center
|
|
|
232
232
|
column :amount, align: :end # Right
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
## Value Formatting
|
|
236
|
+
|
|
237
|
+
Use `formatter` for simple value transformations without a full block:
|
|
238
|
+
|
|
239
|
+
```ruby
|
|
240
|
+
# Truncate long text
|
|
241
|
+
column :description, formatter: ->(value) { value&.truncate(30) }
|
|
242
|
+
|
|
243
|
+
# Format numbers
|
|
244
|
+
column :price, formatter: ->(value) { "$%.2f" % value if value }
|
|
245
|
+
|
|
246
|
+
# Transform values
|
|
247
|
+
column :status, formatter: ->(value) { value&.humanize&.upcase }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The `formatter` option:
|
|
251
|
+
- Receives the field value as its argument
|
|
252
|
+
- Returns the transformed value for display
|
|
253
|
+
- Works with `column` and `display` declarations
|
|
254
|
+
- Is simpler than block syntax when you only need to transform the value
|
|
255
|
+
|
|
256
|
+
**formatter vs block:** Use `formatter` when you only need the value. Use a block when you need access to the full record:
|
|
257
|
+
|
|
258
|
+
```ruby
|
|
259
|
+
# formatter - receives just the value
|
|
260
|
+
column :name, formatter: ->(value) { value&.titleize }
|
|
261
|
+
|
|
262
|
+
# block - receives the full record
|
|
263
|
+
column :full_name do |record|
|
|
264
|
+
"#{record.first_name} #{record.last_name}"
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
235
268
|
## Nested Inputs
|
|
236
269
|
|
|
237
270
|
Render inline forms for associated records. Requires `accepts_nested_attributes_for` on the model.
|
|
@@ -67,7 +67,7 @@ class Comment < ResourceRecord
|
|
|
67
67
|
end
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
When both `Post` and `Comment` are registered in a portal, Plutonium automatically creates nested routes (`/posts/:post_id/
|
|
70
|
+
When both `Post` and `Comment` are registered in a portal, Plutonium automatically creates nested routes (`/posts/:post_id/nested_comments`). Queries are automatically scoped to the parent via the association.
|
|
71
71
|
|
|
72
72
|
See the [Nested Resources Guide](/guides/nested-resources) for details.
|
|
73
73
|
|