plutonium 0.50.0 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +572 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +163 -300
  5. data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
  6. data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
  7. data/.claude/skills/plutonium-tenancy/SKILL.md +655 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +6 -5
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +27 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1009 -1214
  14. data/app/assets/plutonium.js.map +3 -3
  15. data/app/assets/plutonium.min.js +52 -51
  16. data/app/assets/plutonium.min.js.map +3 -3
  17. data/docs/.vitepress/config.ts +37 -27
  18. data/docs/getting-started/index.md +22 -29
  19. data/docs/getting-started/installation.md +37 -80
  20. data/docs/getting-started/tutorial/index.md +4 -5
  21. data/docs/guides/adding-resources.md +66 -377
  22. data/docs/guides/authentication.md +94 -463
  23. data/docs/guides/authorization.md +124 -370
  24. data/docs/guides/creating-packages.md +94 -296
  25. data/docs/guides/custom-actions.md +121 -441
  26. data/docs/guides/index.md +22 -42
  27. data/docs/guides/multi-tenancy.md +116 -187
  28. data/docs/guides/nested-resources.md +103 -431
  29. data/docs/guides/search-filtering.md +123 -240
  30. data/docs/guides/testing.md +5 -4
  31. data/docs/guides/theming.md +157 -407
  32. data/docs/guides/troubleshooting.md +5 -3
  33. data/docs/guides/user-invites.md +106 -425
  34. data/docs/guides/user-profile.md +76 -243
  35. data/docs/index.md +1 -1
  36. data/docs/reference/app/generators.md +517 -0
  37. data/docs/reference/app/index.md +158 -0
  38. data/docs/reference/app/packages.md +146 -0
  39. data/docs/reference/app/portals.md +377 -0
  40. data/docs/reference/auth/accounts.md +230 -0
  41. data/docs/reference/auth/index.md +88 -0
  42. data/docs/reference/auth/profile.md +185 -0
  43. data/docs/reference/behavior/controllers.md +395 -0
  44. data/docs/reference/behavior/index.md +22 -0
  45. data/docs/reference/behavior/interactions.md +341 -0
  46. data/docs/reference/behavior/policies.md +417 -0
  47. data/docs/reference/index.md +56 -49
  48. data/docs/reference/resource/actions.md +423 -0
  49. data/docs/reference/resource/definition.md +508 -0
  50. data/docs/reference/resource/index.md +50 -0
  51. data/docs/reference/resource/model.md +348 -0
  52. data/docs/reference/resource/query.md +305 -0
  53. data/docs/reference/tenancy/entity-scoping.md +361 -0
  54. data/docs/reference/tenancy/index.md +36 -0
  55. data/docs/reference/tenancy/invites.md +393 -0
  56. data/docs/reference/tenancy/nested-resources.md +267 -0
  57. data/docs/reference/testing/index.md +287 -0
  58. data/docs/reference/ui/assets.md +400 -0
  59. data/docs/reference/ui/components.md +165 -0
  60. data/docs/reference/ui/displays.md +104 -0
  61. data/docs/reference/ui/forms.md +284 -0
  62. data/docs/reference/ui/index.md +30 -0
  63. data/docs/reference/ui/layouts.md +106 -0
  64. data/docs/reference/ui/pages.md +189 -0
  65. data/docs/reference/ui/tables.md +117 -0
  66. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  67. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  68. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  69. data/gemfiles/rails_7.gemfile.lock +1 -1
  70. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  71. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  72. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  73. data/lib/generators/pu/invites/install_generator.rb +1 -0
  74. data/lib/plutonium/definition/base.rb +1 -1
  75. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  76. data/lib/plutonium/helpers/turbo_helper.rb +11 -0
  77. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  78. data/lib/plutonium/resource/controller.rb +1 -0
  79. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  80. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  81. data/lib/plutonium/resource/policy.rb +7 -0
  82. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  83. data/lib/plutonium/ui/component/methods.rb +4 -0
  84. data/lib/plutonium/ui/form/base.rb +6 -2
  85. data/lib/plutonium/ui/form/components/json.rb +58 -0
  86. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  87. data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
  88. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  89. data/lib/plutonium/ui/form/resource.rb +0 -4
  90. data/lib/plutonium/ui/grid/resource.rb +1 -1
  91. data/lib/plutonium/ui/layout/base.rb +1 -0
  92. data/lib/plutonium/ui/page/base.rb +0 -7
  93. data/lib/plutonium/ui/page/index.rb +4 -4
  94. data/lib/plutonium/ui/table/resource.rb +1 -1
  95. data/lib/plutonium/version.rb +1 -1
  96. data/lib/plutonium.rb +8 -0
  97. data/lib/tasks/release.rake +15 -1
  98. data/package.json +10 -10
  99. data/src/css/slim_select.css +4 -0
  100. data/src/js/controllers/slim_select_controller.js +61 -0
  101. data/src/js/turbo/turbo_actions.js +33 -0
  102. data/yarn.lock +553 -543
  103. metadata +44 -33
  104. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  105. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  106. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  107. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  108. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  109. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  110. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  111. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  112. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  113. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  114. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  115. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  116. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  117. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  118. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  119. data/docs/reference/assets/index.md +0 -496
  120. data/docs/reference/controller/index.md +0 -412
  121. data/docs/reference/definition/actions.md +0 -462
  122. data/docs/reference/definition/fields.md +0 -383
  123. data/docs/reference/definition/index.md +0 -326
  124. data/docs/reference/definition/query.md +0 -351
  125. data/docs/reference/generators/index.md +0 -648
  126. data/docs/reference/interaction/index.md +0 -449
  127. data/docs/reference/model/features.md +0 -248
  128. data/docs/reference/model/index.md +0 -218
  129. data/docs/reference/policy/index.md +0 -456
  130. data/docs/reference/portal/index.md +0 -379
  131. data/docs/reference/views/forms.md +0 -411
  132. data/docs/reference/views/index.md +0 -544
@@ -1,396 +0,0 @@
1
- ---
2
- name: plutonium-controller
3
- description: Use BEFORE overriding a controller action, adding a hook, or changing redirect logic in a Plutonium controller. Also when customizing resource_params or presentation hooks.
4
- ---
5
-
6
- # Plutonium Controllers
7
-
8
- ## 🚨 Critical (read first)
9
- - **Use generators.** `pu:res:scaffold` and `pu:res:conn` create controllers — never hand-write them.
10
- - **Don't override CRUD actions.** Customize via hooks (`resource_params`, `redirect_url_after_submit`, `preferred_action_after_submit`, presentation hooks). Overriding `create`/`update` usually breaks authorization, params filtering, or both.
11
- - **Prefer definitions and interactions.** UI config belongs in definitions; business logic belongs in interactions. The controller is thin by design.
12
- - **Named custom routes.** When adding custom member/collection routes, always use `as:` so `resource_url_for` can build URLs — especially for nested resources.
13
- - **Related skills:** `plutonium-definition` (interactive actions instead of controller actions), `plutonium-policy` (authorization), `plutonium-nested-resources` (parent/child routing), `plutonium-views` (custom page classes).
14
-
15
- **Controllers are generated automatically** - never create them manually:
16
- - `rails g pu:res:scaffold` creates the base controller
17
- - `rails g pu:res:conn` creates portal-specific controllers
18
-
19
- Controllers in Plutonium provide full CRUD functionality out of the box. You rarely need to customize them - definitions handle most UI configuration and policies handle authorization.
20
-
21
- ## Base Classes
22
-
23
- ```ruby
24
- # app/controllers/resource_controller.rb (generated during install)
25
- class ResourceController < ApplicationController
26
- include Plutonium::Resource::Controller
27
- end
28
-
29
- # app/controllers/posts_controller.rb (generated per resource)
30
- class PostsController < ::ResourceController
31
- # Empty - all CRUD actions inherited
32
- end
33
- ```
34
-
35
- ## What You Get for Free
36
-
37
- Every resource controller automatically provides:
38
-
39
- | Action | Route | Purpose |
40
- |--------|-------|---------|
41
- | `index` | GET /posts | List with pagination, search, filters, sorting |
42
- | `show` | GET /posts/:id | Display single record |
43
- | `new` | GET /posts/new | New record form |
44
- | `create` | POST /posts | Create record |
45
- | `edit` | GET /posts/:id/edit | Edit record form |
46
- | `update` | PATCH /posts/:id | Update record |
47
- | `destroy` | DELETE /posts/:id | Delete record |
48
-
49
- Plus interactive action routes for custom operations defined in definitions.
50
-
51
- ## When to Customize
52
-
53
- **Use Definitions for:**
54
- - Field configuration (inputs, displays, columns)
55
- - Search, filters, scopes, sorting
56
- - Actions (interactive operations)
57
- - Form customization
58
-
59
- **Customize Controller for:**
60
- - Custom redirect logic
61
- - Special parameter processing
62
- - Non-standard authorization flows
63
- - External integrations
64
- - Response format changes
65
-
66
- ## Override Hooks
67
-
68
- All customization is done by overriding private methods:
69
-
70
- ### Redirect Hooks
71
-
72
- ```ruby
73
- class PostsController < ::ResourceController
74
- private
75
-
76
- # Where to go after create/update: "show" (default), "edit", "new", "index"
77
- def preferred_action_after_submit
78
- "edit"
79
- end
80
-
81
- # Custom URL after create/update (overrides preferred_action_after_submit)
82
- def redirect_url_after_submit
83
- posts_path
84
- end
85
-
86
- # Custom URL after destroy
87
- def redirect_url_after_destroy
88
- posts_path
89
- end
90
- end
91
- ```
92
-
93
- ### Parameter Hooks
94
-
95
- ```ruby
96
- class PostsController < ::ResourceController
97
- private
98
-
99
- # Modify params before create/update
100
- def resource_params
101
- params = super
102
- params[:tags] = params[:tags].split(",") if params[:tags].is_a?(String)
103
- params
104
- end
105
- end
106
- ```
107
-
108
- ### Query Hooks
109
-
110
- ```ruby
111
- class PostsController < ::ResourceController
112
- private
113
-
114
- # Customize the index query
115
- def filtered_resource_collection
116
- base = current_authorized_scope
117
- base = base.featured if params[:featured]
118
- current_query_object.apply(base, raw_resource_query_params)
119
- end
120
- end
121
- ```
122
-
123
- ### Presentation Hooks
124
-
125
- Control whether parent/entity fields appear in forms and displays:
126
-
127
- ```ruby
128
- class PostsController < ::ResourceController
129
- private
130
-
131
- # Show parent field in displays (default: false)
132
- def present_parent?
133
- true
134
- end
135
-
136
- # Include parent field in forms (default: same as present_parent?)
137
- def submit_parent?
138
- true
139
- end
140
-
141
- # Show scoped entity in displays (default: false)
142
- def present_scoped_entity?
143
- true
144
- end
145
-
146
- # Include scoped entity in forms (default: same as present_scoped_entity?)
147
- def submit_scoped_entity?
148
- true
149
- end
150
- end
151
- ```
152
-
153
- ## Custom Actions
154
-
155
- ```ruby
156
- class PostsController < ::ResourceController
157
- def publish
158
- authorize_current!(resource_record!, to: :publish?)
159
- resource_record!.update!(published: true)
160
- redirect_to resource_url_for(resource_record!), notice: "Published!"
161
- end
162
- end
163
- ```
164
-
165
- **Important:** When adding custom routes, always use the `as:` option to name them:
166
-
167
- ```ruby
168
- # config/routes.rb or portal routes
169
- resources :posts do
170
- member do
171
- post :publish, as: :publish # Named route required!
172
- end
173
- end
174
- ```
175
-
176
- This ensures `resource_url_for` can generate correct URLs, especially for nested resources.
177
-
178
- Note: For most custom operations, use Interactive Actions in definitions instead.
179
-
180
- ## Key Methods
181
-
182
- ### Resource Access
183
-
184
- ```ruby
185
- resource_class # The model class (e.g., Post)
186
- resource_record! # Current record (raises if not found)
187
- resource_record? # Current record (nil if not found)
188
- resource_params # Permitted params for create/update
189
- current_parent # Parent record for nested routes
190
- ```
191
-
192
- ### Authorization
193
-
194
- ```ruby
195
- authorize_current!(record, to: :action?) # Check permission
196
- current_policy # Policy for current resource
197
- permitted_attributes # Allowed attributes for action
198
- current_authorized_scope # Scoped records user can access
199
- ```
200
-
201
- ### Definition Access
202
-
203
- ```ruby
204
- current_definition # Definition for current resource
205
- ```
206
-
207
- ### UI Building
208
-
209
- ```ruby
210
- build_form # Build form component
211
- build_detail # Build show/detail component
212
- build_collection # Build table component
213
- ```
214
-
215
- ### URL Generation
216
-
217
- ```ruby
218
- resource_url_for(@post) # URL for record
219
- resource_url_for(@post, action: :edit) # Edit URL
220
- resource_url_for(Post) # Index URL
221
-
222
- # With parent (nested resources)
223
- resource_url_for(@comment, parent: @post) # Nested URL
224
- resource_url_for(Comment, action: :new, parent: @post)
225
-
226
- # Cross-package URLs
227
- resource_url_for(@post, package: AdminPortal)
228
- ```
229
-
230
- ## Nested Resources
231
-
232
- Parent records are automatically resolved from routes with the `nested_` prefix:
233
-
234
- ```ruby
235
- # Route: /users/:user_id/nested_posts/:id
236
- class PostsController < ::ResourceController
237
- # current_parent returns the User
238
- # current_nested_association returns :posts
239
- # resource_record! returns the Post scoped to that User
240
- end
241
- ```
242
-
243
- ### Key Methods for Nested Resources
244
-
245
- ```ruby
246
- current_parent # Parent record (e.g., User instance)
247
- current_nested_association # Association name (e.g., :posts)
248
- parent_route_param # URL param (e.g., :user_id)
249
- parent_input_param # Form param (e.g., :user)
250
- ```
251
-
252
- Parent fields are automatically excluded from forms/displays. Override with presentation hooks (see above).
253
-
254
- ### has_one Support
255
-
256
- For `has_one` associations, routes are singular:
257
- - `/users/:user_id/nested_profile` (no `:id` param)
258
- - Index redirects to show (or new if no record exists)
259
-
260
- ## Entity Scoping (Multi-tenancy)
261
-
262
- When a portal is scoped to an entity:
263
-
264
- ```ruby
265
- # packages/admin_portal/lib/engine.rb
266
- module AdminPortal
267
- class Engine < Rails::Engine
268
- include Plutonium::Portal::Engine
269
-
270
- config.after_initialize do
271
- scope_to_entity Organization, strategy: :path
272
- end
273
- end
274
- end
275
- ```
276
-
277
- Controllers automatically:
278
- - Scope all queries to the entity
279
- - Exclude entity field from forms (detected by association class)
280
- - Inject entity value on create/update
281
- - Provide `current_scoped_entity` method
282
-
283
- ### Association Detection
284
-
285
- Plutonium auto-detects which `belongs_to` association points to the scoped entity class. This works even when `param_key` differs from the association name:
286
-
287
- ```ruby
288
- # Portal config
289
- scope_to_entity Competition::Team, param_key: :team
290
-
291
- # Model (association name differs from param_key)
292
- class Match < ApplicationRecord
293
- belongs_to :competition_team # Plutonium finds this by class
294
- end
295
- ```
296
-
297
- ### Multiple Associations to Same Class
298
-
299
- If a model has multiple associations to the scoped entity class, Plutonium raises an error:
300
-
301
- ```
302
- Match has multiple associations to Competition::Team: home_team, away_team.
303
- Plutonium cannot auto-detect which one to use for entity scoping.
304
- Override `scoped_entity_association` in your controller to specify the association.
305
- ```
306
-
307
- Resolve by overriding `scoped_entity_association`:
308
-
309
- ```ruby
310
- class MatchesController < ::ResourceController
311
- private
312
-
313
- def scoped_entity_association
314
- :home_team # Return the association name as a symbol
315
- end
316
- end
317
- ```
318
-
319
- ## Authorization Verification
320
-
321
- Controllers verify authorization was performed:
322
-
323
- ```ruby
324
- # These run after every action
325
- verify_authorize_current # Ensures authorize_current! was called
326
- verify_current_authorized_scope # Ensures scope was loaded (except new/create)
327
- ```
328
-
329
- To skip verification for custom actions:
330
-
331
- ```ruby
332
- class PostsController < ::ResourceController
333
- skip_verify_authorize_current only: [:custom_action]
334
-
335
- def custom_action
336
- # Handle authorization manually
337
- end
338
- end
339
- ```
340
-
341
- ## Response Formats
342
-
343
- Controllers respond to multiple formats:
344
-
345
- ```ruby
346
- def show
347
- # Responds to:
348
- # - HTML (default)
349
- # - JSON (via RABL templates)
350
- # - Turbo Stream (for Hotwire)
351
- end
352
- ```
353
-
354
- ## Portal-Specific Controllers
355
-
356
- Portal controllers inherit from the feature package's controller if one exists (and include the portal's `Concerns::Controller`). If no feature package controller exists, they inherit from the portal's `ResourceController`.
357
-
358
- ```ruby
359
- # With feature package controller:
360
- class AdminPortal::PostsController < ::PostsController
361
- include AdminPortal::Concerns::Controller
362
- end
363
-
364
- # Without feature package controller:
365
- class AdminPortal::PostsController < AdminPortal::ResourceController
366
- end
367
- ```
368
-
369
- For non-resource portal pages (dashboard, settings), inherit from `PlutoniumController`:
370
-
371
- ```ruby
372
- module AdminPortal
373
- class DashboardController < PlutoniumController
374
- def index
375
- # Dashboard home
376
- end
377
- end
378
- end
379
- ```
380
-
381
- ## Best Practices
382
-
383
- 1. **Keep controllers thin** - Use definitions for UI, policies for auth, interactions for logic
384
- 2. **Don't override CRUD actions** - Customize via hooks (`resource_params`, `redirect_url_after_submit`)
385
- 3. **Use interactive actions** - For custom operations, define in definition with interaction
386
- 4. **Let authorization work** - Don't skip verification without good reason
387
- 5. **Trust the framework** - Most customization belongs in definitions or policies
388
-
389
- ## Related Skills
390
-
391
- - `plutonium` - How controllers fit in the resource architecture
392
- - `plutonium-policy` - Authorization (used by controllers)
393
- - `plutonium-definition` - Interactive actions (preferred over custom controller actions)
394
- - `plutonium-views` - Custom page, form, display, and table classes
395
- - `plutonium-nested-resources` - Parent/child routes and scoping
396
- - `plutonium-model` - Resource models
@@ -1,303 +0,0 @@
1
- ---
2
- name: plutonium-create-resource
3
- description: Use BEFORE running pu:res:scaffold or creating any new resource. Also when picking field types or generator options. Covers field syntax and scaffold options.
4
- ---
5
-
6
- # Create Resource Skill
7
-
8
- ## 🚨 Critical (read first)
9
- - **Always use `pu:res:scaffold`.** Never hand-write models, migrations, policies, definitions, or controllers. Plutonium's conventions rely on files it generated.
10
- - **Always pass `--dest`** (`--dest=main_app` or `--dest=package_name`) to skip the interactive destination prompt.
11
- - **Quote fields with `?` or `{}`** to prevent shell expansion: `'field:type?'`, `'field:decimal{10,2}'`, `'field:decimal?{10,2}'`.
12
- - **Run `pu:res:conn` next** to connect the resource to a portal — without it, the resource is invisible.
13
- - **Related skills:** `plutonium-model` (model structure), `plutonium-definition` (UI config), `plutonium-policy` (authorization), `plutonium-portal` (connecting to portals).
14
-
15
- Use the `pu:res:scaffold` generator to create complete resources in Plutonium applications.
16
-
17
- ## Quick checklist
18
-
19
- Creating a new resource:
20
-
21
- 1. Pick a destination: `--dest=main_app` or `--dest=package_name`.
22
- 2. Identify field types (see field type syntax below). Quote fields with `?` or `{}`.
23
- 3. Run `rails g pu:res:scaffold ResourceName field:type ... --dest=<dest>`.
24
- 4. Review the generated migration — add cascade deletes, composite indexes, defaults.
25
- 5. Run `rails db:migrate`.
26
- 6. Run `rails g pu:res:conn ResourceName --dest=<portal_name>` to connect to a portal.
27
- 7. Verify routes: `bin/rails routes | grep resource_name`.
28
- 8. Customize the policy (`permitted_attributes_for_read`, `permitted_attributes_for_create`) as needed.
29
- 9. Open the portal route in the browser.
30
-
31
- ## Command Syntax
32
-
33
- ```bash
34
- rails g pu:res:scaffold MODEL_NAME \
35
- field1:type \
36
- field2:type \
37
- --dest=DESTINATION
38
- ```
39
-
40
- **IMPORTANT**: Always specify `--dest` to avoid interactive prompts:
41
- - `--dest=main_app` for resources in the main application
42
- - `--dest=package_name` for resources in a feature package
43
-
44
- **IMPORTANT**: Quote fields containing `?` or `{}` to prevent shell expansion:
45
- ```bash
46
- 'field:type?' # Nullable - must quote
47
- 'field:decimal{10,2}' # Options - must quote
48
- 'field:decimal?{10,2}' # Both - must quote
49
- ```
50
-
51
- ## From Existing Models
52
-
53
- For existing Rails projects with models you want to convert to Plutonium resources:
54
-
55
- ### Option 1: Model already includes Plutonium::Resource::Record
56
-
57
- ```bash
58
- rails g pu:res:scaffold Post --no-migration --dest=main_app
59
- ```
60
-
61
- This generates only the definition, policy, and controller - leaving your model unchanged.
62
-
63
- ### Option 2: Let the generator update the model
64
-
65
- ```bash
66
- rails g pu:res:scaffold Post --dest=main_app
67
- ```
68
-
69
- Run without attributes to auto-import fields from `model.content_columns`. This regenerates the model file, so review changes carefully.
70
-
71
- ### Don't forget to include the module
72
-
73
- Your model must include `Plutonium::Resource::Record` (directly or via inheritance):
74
-
75
- ```ruby
76
- class Post < ApplicationRecord
77
- include Plutonium::Resource::Record
78
- end
79
-
80
- # Or inherit from a base class
81
- class Post < ResourceRecord
82
- end
83
- ```
84
-
85
- ## Field Type Syntax
86
-
87
- Format: `name:type:index_type`
88
-
89
- ### Basic Types
90
-
91
- | Syntax | Result |
92
- |--------|--------|
93
- | `name:string` | Required string |
94
- | `'name:string?'` | Nullable string |
95
- | `age:integer` | Required integer |
96
- | `'age:integer?'` | Nullable integer |
97
- | `active:boolean` | Required boolean |
98
- | `'active:boolean?'` | Nullable boolean |
99
- | `content:text` | Required text |
100
- | `'content:text?'` | Nullable text |
101
- | `birth_date:date` | Required date |
102
- | `'anniversary:date?'` | Nullable date |
103
- | `starts_at:datetime` | Required datetime |
104
- | `'ends_at:datetime?'` | Nullable datetime |
105
- | `alarm_time:time` | Required time |
106
- | `'reminder_time:time?'` | Nullable time |
107
- | `metadata:json` | JSON field |
108
- | `settings:jsonb` | JSONB (PostgreSQL) |
109
- | `external_id:uuid` | UUID field |
110
-
111
- ### PostgreSQL-Specific Types
112
-
113
- These types work in both PostgreSQL and SQLite (automatically mapped):
114
-
115
- | Type | PostgreSQL | SQLite |
116
- |------|------------|--------|
117
- | `jsonb` | `jsonb` | `json` |
118
- | `hstore` | `hstore` | `json` |
119
- | `uuid` | `uuid` | `string` |
120
- | `inet` | `inet` | `string` |
121
- | `cidr` | `cidr` | `string` |
122
- | `macaddr` | `macaddr` | `string` |
123
- | `ltree` | `ltree` | `string` |
124
-
125
- ### Default Values
126
-
127
- Use `{default:value}` syntax for default values:
128
-
129
- ```bash
130
- 'status:string{default:draft}' # String default
131
- 'active:boolean{default:true}' # Boolean default (true/false/yes/1)
132
- 'priority:integer{default:0}' # Integer default
133
- 'rating:float{default:4.5}' # Float default
134
- 'status:string?{default:pending}' # Nullable with default
135
- ```
136
-
137
- ### JSON/JSONB Default Values
138
-
139
- Supports nested braces for structured defaults:
140
-
141
- ```bash
142
- 'metadata:jsonb{default:{}}' # Empty hash
143
- 'tags:jsonb{default:[]}' # Empty array
144
- 'settings:jsonb{default:{"theme":"dark"}}' # Object with values
145
- 'config:jsonb?{default:{}}' # Nullable with empty hash default
146
- ```
147
-
148
- Default values are parsed as JSON first. If JSON parsing fails, the value is treated as a string (or coerced based on column type for integers, floats, and booleans).
149
-
150
- ### Decimal with Precision and Default
151
-
152
- ```bash
153
- 'amount:decimal{10,2}' # precision: 10, scale: 2
154
- 'price:decimal{10,2,default:0}' # with default value
155
- 'balance:decimal?{15,2,default:0}' # nullable with precision and default
156
- ```
157
-
158
- ### References/Associations
159
-
160
- ```bash
161
- company:belongs_to # Required foreign key
162
- 'parent:belongs_to?' # Nullable (null: true + optional: true)
163
- user:references # Same as belongs_to
164
- blogging/post:belongs_to # Cross-package reference
165
- 'author:belongs_to{class_name:User}' # Custom class_name (author_id -> User)
166
- 'reviewer:belongs_to?{class_name:User}' # Nullable with class_name
167
- ```
168
-
169
- Nullable references generate:
170
- - Migration: `null: true`
171
- - Model: `belongs_to :parent, optional: true`
172
-
173
- ### Index Types (third segment)
174
-
175
- ```bash
176
- email:string:index # Regular index
177
- email:string:uniq # Unique index
178
- ```
179
-
180
- ### Special Types
181
-
182
- ```bash
183
- password_digest # has_secure_password
184
- auth_token:token # has_secure_token (auto unique index)
185
- content:rich_text # has_rich_text
186
- avatar:attachment # has_one_attached
187
- photos:attachments # has_many_attached
188
- price_cents:integer # has_cents (money field)
189
- ```
190
-
191
- Token fields automatically get a unique index in the migration.
192
-
193
- ## Generator Options
194
-
195
- - `--dest=DESTINATION` - Target destination (**always required** to avoid prompts)
196
- - `main_app` for main application resources
197
- - `package_name` for feature package resources
198
- - `--no-model` - Skip model generation (keeps existing model)
199
- - `--no-migration` - Skip migration generation (use with `--no-model` for existing models)
200
-
201
- ## What Gets Generated
202
-
203
- For **main_app** resources:
204
- 1. **Model** - `app/models/model_name.rb`
205
- 2. **Migration** - `db/migrate/xxx_create_model_names.rb`
206
- 3. **Controller** - `app/controllers/model_names_controller.rb`
207
- 4. **Policy** - `app/policies/model_name_policy.rb`
208
- 5. **Definition** - `app/definitions/model_name_definition.rb`
209
-
210
- For **packaged** resources:
211
- 1. **Model** - `app/models/package_name/model_name.rb`
212
- 2. **Migration** - `db/migrate/xxx_create_package_name_model_names.rb`
213
- 3. **Controller** - `packages/package_name/app/controllers/package_name/model_names_controller.rb`
214
- 4. **Policy** - `packages/package_name/app/policies/package_name/model_name_policy.rb`
215
- 5. **Definition** - `packages/package_name/app/definitions/package_name/model_name_definition.rb`
216
-
217
- ## Migration Customizations
218
-
219
- The generator creates basic migrations. **Always review and customize** the migration before running:
220
-
221
- ### Inline Indexes (preferred)
222
-
223
- ```ruby
224
- create_table :model_names do |t|
225
- t.belongs_to :parent, null: false, foreign_key: true
226
- t.string :name, null: false
227
-
228
- t.timestamps
229
-
230
- t.index :name
231
- t.index [:parent_id, :name], unique: true
232
- end
233
- ```
234
-
235
- ### Cascade Delete
236
-
237
- ```ruby
238
- t.belongs_to :parent, null: false, foreign_key: {on_delete: :cascade}
239
- ```
240
-
241
- ### Default Values
242
-
243
- Default values can be set directly in the generator using `{default:value}` syntax (see Field Type Syntax above). For more complex defaults or expressions, edit the migration:
244
-
245
- ```ruby
246
- t.boolean :is_active, default: true
247
- t.integer :status, default: 0
248
- t.datetime :published_at, default: -> { "CURRENT_TIMESTAMP" }
249
- ```
250
-
251
- ## Examples
252
-
253
- ### Main App Resource
254
-
255
- ```bash
256
- rails g pu:res:scaffold Post \
257
- user:belongs_to \
258
- title:string \
259
- 'content:text?' \
260
- 'published_at:datetime?' \
261
- --dest=main_app
262
- ```
263
-
264
- ### Resource with Precision and Indexes
265
-
266
- ```bash
267
- rails g pu:res:scaffold Property \
268
- company:belongs_to \
269
- code:string:uniq \
270
- 'latitude:decimal{11,8}' \
271
- 'longitude:decimal?{11,8}' \
272
- 'value:decimal?{15,2}' \
273
- 'notes:text?' \
274
- --dest=main_app
275
- ```
276
-
277
- ### Optional Association
278
-
279
- ```bash
280
- rails g pu:res:scaffold Comment \
281
- user:belongs_to \
282
- 'parent:belongs_to?' \
283
- body:text \
284
- --dest=blogging
285
- ```
286
-
287
- ### Cross-Package Reference
288
-
289
- ```bash
290
- rails g pu:res:scaffold Comment \
291
- user:belongs_to \
292
- blogging/post:belongs_to \
293
- body:text \
294
- --dest=comments
295
- ```
296
-
297
- ## After Generation
298
-
299
- 1. **Review and customize the migration** (add cascade delete, defaults, composite indexes)
300
- 2. Run `rails db:migrate`
301
- 3. Connect resource to portal: `rails g pu:res:conn Post --dest=admin_portal`
302
- 4. Customize policy permissions as needed
303
- 5. Add definition customizations for UI behavior