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.
Files changed (143) hide show
  1. checksums.yaml +4 -4
  2. data/# Plutonium: The pre-alpha demo.md +4 -2
  3. data/.claude/skills/assets/SKILL.md +416 -0
  4. data/.claude/skills/connect-resource/SKILL.md +112 -0
  5. data/.claude/skills/controller/SKILL.md +302 -0
  6. data/.claude/skills/create-resource/SKILL.md +240 -0
  7. data/.claude/skills/definition/SKILL.md +218 -0
  8. data/.claude/skills/definition-actions/SKILL.md +386 -0
  9. data/.claude/skills/definition-fields/SKILL.md +474 -0
  10. data/.claude/skills/definition-query/SKILL.md +334 -0
  11. data/.claude/skills/forms/SKILL.md +439 -0
  12. data/.claude/skills/installation/SKILL.md +300 -0
  13. data/.claude/skills/interaction/SKILL.md +382 -0
  14. data/.claude/skills/model/SKILL.md +267 -0
  15. data/.claude/skills/model-features/SKILL.md +286 -0
  16. data/.claude/skills/nested-resources/SKILL.md +274 -0
  17. data/.claude/skills/package/SKILL.md +191 -0
  18. data/.claude/skills/policy/SKILL.md +352 -0
  19. data/.claude/skills/portal/SKILL.md +400 -0
  20. data/.claude/skills/resource/SKILL.md +281 -0
  21. data/.claude/skills/rodauth/SKILL.md +452 -0
  22. data/.claude/skills/views/SKILL.md +563 -0
  23. data/Appraisals +46 -4
  24. data/CHANGELOG.md +32 -1
  25. data/app/assets/plutonium.css +2 -2
  26. data/config/brakeman.ignore +239 -0
  27. data/config/initializers/action_policy.rb +1 -1
  28. data/docs/.vitepress/config.ts +132 -47
  29. data/docs/concepts/architecture.md +226 -0
  30. data/docs/concepts/auto-detection.md +254 -0
  31. data/docs/concepts/index.md +61 -0
  32. data/docs/concepts/packages-portals.md +304 -0
  33. data/docs/concepts/resources.md +224 -0
  34. data/docs/cookbook/blog.md +412 -0
  35. data/docs/cookbook/index.md +289 -0
  36. data/docs/cookbook/saas.md +481 -0
  37. data/docs/getting-started/index.md +56 -0
  38. data/docs/getting-started/installation.md +146 -0
  39. data/docs/getting-started/tutorial/01-setup.md +118 -0
  40. data/docs/getting-started/tutorial/02-first-resource.md +180 -0
  41. data/docs/getting-started/tutorial/03-authentication.md +246 -0
  42. data/docs/getting-started/tutorial/04-authorization.md +170 -0
  43. data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
  44. data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
  45. data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
  46. data/docs/getting-started/tutorial/index.md +64 -0
  47. data/docs/guides/adding-resources.md +420 -0
  48. data/docs/guides/authentication.md +551 -0
  49. data/docs/guides/authorization.md +468 -0
  50. data/docs/guides/creating-packages.md +380 -0
  51. data/docs/guides/custom-actions.md +523 -0
  52. data/docs/guides/index.md +45 -0
  53. data/docs/guides/multi-tenancy.md +302 -0
  54. data/docs/guides/nested-resources.md +411 -0
  55. data/docs/guides/search-filtering.md +266 -0
  56. data/docs/guides/theming.md +321 -0
  57. data/docs/index.md +67 -26
  58. data/docs/public/CLAUDE.md +64 -21
  59. data/docs/reference/assets/index.md +496 -0
  60. data/docs/reference/controller/index.md +363 -0
  61. data/docs/reference/definition/actions.md +400 -0
  62. data/docs/reference/definition/fields.md +350 -0
  63. data/docs/reference/definition/index.md +252 -0
  64. data/docs/reference/definition/query.md +342 -0
  65. data/docs/reference/generators/index.md +469 -0
  66. data/docs/reference/index.md +49 -0
  67. data/docs/reference/interaction/index.md +445 -0
  68. data/docs/reference/model/features.md +248 -0
  69. data/docs/reference/model/index.md +219 -0
  70. data/docs/reference/policy/index.md +385 -0
  71. data/docs/reference/portal/index.md +382 -0
  72. data/docs/reference/views/forms.md +396 -0
  73. data/docs/reference/views/index.md +479 -0
  74. data/gemfiles/rails_7.gemfile +9 -2
  75. data/gemfiles/rails_7.gemfile.lock +146 -111
  76. data/gemfiles/rails_8.0.gemfile +20 -0
  77. data/gemfiles/rails_8.0.gemfile.lock +417 -0
  78. data/gemfiles/rails_8.1.gemfile +20 -0
  79. data/gemfiles/rails_8.1.gemfile.lock +419 -0
  80. data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
  81. data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
  82. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
  83. data/lib/generators/pu/pkg/portal/USAGE +65 -0
  84. data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
  85. data/lib/generators/pu/res/conn/USAGE +71 -0
  86. data/lib/generators/pu/res/model/USAGE +106 -110
  87. data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
  88. data/lib/generators/pu/res/scaffold/USAGE +85 -0
  89. data/lib/generators/pu/rodauth/install_generator.rb +2 -6
  90. data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
  91. data/lib/generators/pu/skills/sync/USAGE +14 -0
  92. data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
  93. data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
  94. data/lib/plutonium/core/controller.rb +2 -2
  95. data/lib/plutonium/interaction/base.rb +1 -0
  96. data/lib/plutonium/package/engine.rb +2 -2
  97. data/lib/plutonium/query/adhoc_block.rb +6 -2
  98. data/lib/plutonium/query/model_scope.rb +1 -1
  99. data/lib/plutonium/railtie.rb +4 -0
  100. data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
  101. data/lib/plutonium/resource/query_object.rb +38 -8
  102. data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
  103. data/lib/plutonium/version.rb +1 -1
  104. data/lib/tasks/release.rake +19 -4
  105. data/package.json +1 -1
  106. metadata +76 -39
  107. data/brakeman.ignore +0 -28
  108. data/docs/api-examples.md +0 -49
  109. data/docs/guide/claude-code-guide.md +0 -74
  110. data/docs/guide/deep-dive/authorization.md +0 -189
  111. data/docs/guide/deep-dive/multitenancy.md +0 -256
  112. data/docs/guide/deep-dive/resources.md +0 -390
  113. data/docs/guide/getting-started/01-installation.md +0 -165
  114. data/docs/guide/index.md +0 -28
  115. data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
  116. data/docs/guide/introduction/02-core-concepts.md +0 -440
  117. data/docs/guide/tutorial/01-project-setup.md +0 -75
  118. data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
  119. data/docs/guide/tutorial/03-defining-resources.md +0 -90
  120. data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
  121. data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
  122. data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
  123. data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
  124. data/docs/markdown-examples.md +0 -85
  125. data/docs/modules/action.md +0 -244
  126. data/docs/modules/authentication.md +0 -236
  127. data/docs/modules/configuration.md +0 -599
  128. data/docs/modules/controller.md +0 -443
  129. data/docs/modules/core.md +0 -316
  130. data/docs/modules/definition.md +0 -1308
  131. data/docs/modules/display.md +0 -759
  132. data/docs/modules/form.md +0 -495
  133. data/docs/modules/generator.md +0 -400
  134. data/docs/modules/index.md +0 -167
  135. data/docs/modules/interaction.md +0 -642
  136. data/docs/modules/package.md +0 -151
  137. data/docs/modules/policy.md +0 -176
  138. data/docs/modules/portal.md +0 -710
  139. data/docs/modules/query.md +0 -297
  140. data/docs/modules/resource_record.md +0 -618
  141. data/docs/modules/routing.md +0 -690
  142. data/docs/modules/table.md +0 -301
  143. data/docs/modules/ui.md +0 -631
@@ -0,0 +1,400 @@
1
+ ---
2
+ name: portal
3
+ description: Plutonium portals - web interfaces with authentication, entity scoping, and routes
4
+ ---
5
+
6
+ # Plutonium Portals
7
+
8
+ Portals are Rails engines that provide web interfaces for specific user types.
9
+
10
+ ## Creating a Portal
11
+
12
+ ```bash
13
+ rails g pu:pkg:portal dashboard
14
+ ```
15
+
16
+ ### Generator Options
17
+
18
+ | Option | Description |
19
+ |--------|-------------|
20
+ | `--auth=NAME` | Rodauth account to authenticate with (e.g., `--auth=user`) |
21
+ | `--public` | Grant public access (no authentication) |
22
+ | `--byo` | Bring your own authentication |
23
+
24
+ ```bash
25
+ # Non-interactive examples
26
+ rails g pu:pkg:portal admin --auth=admin
27
+ rails g pu:pkg:portal api --public
28
+ rails g pu:pkg:portal custom --byo
29
+ ```
30
+
31
+ Without flags, the generator prompts interactively:
32
+ - **Rodauth account** - Use existing Rodauth authentication
33
+ - **Public access** - No authentication required
34
+ - **Bring your own** - Implement custom `current_user`
35
+
36
+ ## Portal Engine
37
+
38
+ ```ruby
39
+ # packages/dashboard_portal/lib/engine.rb
40
+ module DashboardPortal
41
+ class Engine < Rails::Engine
42
+ include Plutonium::Portal::Engine
43
+
44
+ config.after_initialize do
45
+ # Optional: multi-tenancy
46
+ scope_to_entity Organization, strategy: :path
47
+ end
48
+ end
49
+ end
50
+ ```
51
+
52
+ ## Authentication
53
+
54
+ ### Rodauth Integration
55
+
56
+ ```ruby
57
+ # packages/dashboard_portal/app/controllers/dashboard_portal/concerns/controller.rb
58
+ module DashboardPortal
59
+ module Concerns
60
+ module Controller
61
+ extend ActiveSupport::Concern
62
+ include Plutonium::Portal::Controller
63
+ include Plutonium::Auth::Rodauth(:user) # Use :user account
64
+ end
65
+ end
66
+ end
67
+ ```
68
+
69
+ ### Public Access
70
+
71
+ ```ruby
72
+ module DashboardPortal
73
+ module Concerns
74
+ module Controller
75
+ extend ActiveSupport::Concern
76
+ include Plutonium::Portal::Controller
77
+ include Plutonium::Auth::Public
78
+ end
79
+ end
80
+ end
81
+ ```
82
+
83
+ ### Custom Authentication
84
+
85
+ ```ruby
86
+ module DashboardPortal
87
+ module Concerns
88
+ module Controller
89
+ extend ActiveSupport::Concern
90
+ include Plutonium::Portal::Controller
91
+ include Plutonium::Auth::Public
92
+
93
+ def current_user
94
+ @current_user ||= User.find_by(api_key: request.headers["X-API-Key"])
95
+ end
96
+ end
97
+ end
98
+ end
99
+ ```
100
+
101
+ ## Entity Scoping (Multi-tenancy)
102
+
103
+ Automatically scope all data to a parent entity.
104
+
105
+ ### Path Strategy
106
+
107
+ Entity ID in URL path:
108
+
109
+ ```ruby
110
+ module AdminPortal
111
+ class Engine < Rails::Engine
112
+ include Plutonium::Portal::Engine
113
+
114
+ config.after_initialize do
115
+ scope_to_entity Organization, strategy: :path
116
+ end
117
+ end
118
+ end
119
+ ```
120
+
121
+ Routes become: `/organizations/:organization_id/posts`
122
+
123
+ ### Custom Strategy
124
+
125
+ Implement your own lookup method:
126
+
127
+ ```ruby
128
+ module AdminPortal
129
+ class Engine < Rails::Engine
130
+ include Plutonium::Portal::Engine
131
+
132
+ config.after_initialize do
133
+ scope_to_entity Organization, strategy: :current_organization
134
+ end
135
+ end
136
+ end
137
+
138
+ # In controller concern
139
+ module AdminPortal
140
+ module Concerns
141
+ module Controller
142
+ extend ActiveSupport::Concern
143
+ include Plutonium::Portal::Controller
144
+
145
+ private
146
+
147
+ # Method name must match strategy
148
+ def current_organization
149
+ @current_organization ||= Organization.find_by!(subdomain: request.subdomain)
150
+ end
151
+ end
152
+ end
153
+ end
154
+ ```
155
+
156
+ ### Accessing the Scoped Entity
157
+
158
+ ```ruby
159
+ current_scoped_entity # The current Organization/Account/etc.
160
+ scoped_to_entity? # true if scoping is active
161
+ ```
162
+
163
+ ### Model Requirements
164
+
165
+ Models must have an association path to the scoped entity:
166
+
167
+ ```ruby
168
+ # Direct association (preferred)
169
+ class Post < ResourceRecord
170
+ belongs_to :organization
171
+ end
172
+
173
+ # Through association
174
+ class Comment < ResourceRecord
175
+ belongs_to :post
176
+ has_one :organization, through: :post
177
+ end
178
+
179
+ # Complex (define custom scope)
180
+ class AuditLog < ResourceRecord
181
+ scope :associated_with_organization, ->(org) {
182
+ joins(:user).where(users: { organization_id: org.id })
183
+ }
184
+ end
185
+ ```
186
+
187
+ ## Routes
188
+
189
+ ### Portal Routes
190
+
191
+ ```ruby
192
+ # packages/dashboard_portal/config/routes.rb
193
+ DashboardPortal::Engine.routes.draw do
194
+ root to: "dashboard#index"
195
+
196
+ # Register resources
197
+ register_resource ::Post
198
+ register_resource Blogging::Comment
199
+
200
+ # Custom routes
201
+ get "settings", to: "settings#index"
202
+ end
203
+ ```
204
+
205
+ ### Custom Routes on Resources
206
+
207
+ Add member or collection routes with a block:
208
+
209
+ ```ruby
210
+ register_resource ::Post do
211
+ member do
212
+ get :preview
213
+ get :analytics
214
+ post :publish
215
+ end
216
+ collection do
217
+ get :archived
218
+ post :bulk_publish
219
+ end
220
+ end
221
+ ```
222
+
223
+ This generates:
224
+ - `GET /posts/:id/preview`
225
+ - `GET /posts/:id/analytics`
226
+ - `POST /posts/:id/publish`
227
+ - `GET /posts/archived`
228
+ - `POST /posts/bulk_publish`
229
+
230
+ ### Mounting in Main App
231
+
232
+ ```ruby
233
+ # config/routes.rb
234
+ Rails.application.routes.draw do
235
+ # With authentication constraint
236
+ constraints Rodauth::Rails.authenticate(:user) do
237
+ mount DashboardPortal::Engine, at: "/dashboard"
238
+ end
239
+
240
+ # Or without
241
+ mount PublicPortal::Engine, at: "/public"
242
+ end
243
+ ```
244
+
245
+ ## Controller Hierarchy
246
+
247
+ ```
248
+ ::PlutoniumController (app-wide base)
249
+
250
+ ::ResourceController (resource handling)
251
+
252
+ DashboardPortal::ResourceController (portal base)
253
+
254
+ DashboardPortal::PostsController (resource-specific)
255
+ ```
256
+
257
+ ### Portal Controllers
258
+
259
+ ```ruby
260
+ # packages/dashboard_portal/app/controllers/dashboard_portal/resource_controller.rb
261
+ module DashboardPortal
262
+ class ResourceController < ::ResourceController
263
+ include DashboardPortal::Concerns::Controller
264
+ end
265
+ end
266
+ ```
267
+
268
+ ### Dynamic Controllers
269
+
270
+ Controllers are auto-created if not defined. When accessing `DashboardPortal::PostsController`:
271
+
272
+ 1. If file exists, use it
273
+ 2. Otherwise, dynamically create inheriting from `::PostsController`
274
+ 3. Include `DashboardPortal::Concerns::Controller`
275
+
276
+ ## Portal-Specific Overrides
277
+
278
+ ### Override Definition
279
+
280
+ ```ruby
281
+ # packages/dashboard_portal/app/definitions/dashboard_portal/post_definition.rb
282
+ class DashboardPortal::PostDefinition < ::PostDefinition
283
+ # Hide certain actions from this portal
284
+ # Add portal-specific scopes
285
+ scope :my_posts, -> { where(user: current_user) }
286
+ end
287
+ ```
288
+
289
+ ### Override Policy
290
+
291
+ ```ruby
292
+ # packages/dashboard_portal/app/policies/dashboard_portal/post_policy.rb
293
+ class DashboardPortal::PostPolicy < ::PostPolicy
294
+ include DashboardPortal::ResourcePolicy
295
+
296
+ def destroy?
297
+ false # No deletion in user portal
298
+ end
299
+
300
+ def permitted_attributes_for_create
301
+ %i[title content] # Fewer fields than admin
302
+ end
303
+ end
304
+ ```
305
+
306
+ ### Override Controller
307
+
308
+ ```ruby
309
+ # packages/dashboard_portal/app/controllers/dashboard_portal/posts_controller.rb
310
+ module DashboardPortal
311
+ class PostsController < ResourceController
312
+ private
313
+
314
+ def preferred_action_after_submit
315
+ "index"
316
+ end
317
+ end
318
+ end
319
+ ```
320
+
321
+ ## Layout and Views
322
+
323
+ ### Portal Layout
324
+
325
+ ```erb
326
+ <!-- packages/dashboard_portal/app/views/layouts/dashboard_portal.html.erb -->
327
+ <!DOCTYPE html>
328
+ <html>
329
+ <head>
330
+ <title>Dashboard</title>
331
+ <%= csrf_meta_tags %>
332
+ <%= stylesheet_link_tag "application" %>
333
+ </head>
334
+ <body>
335
+ <nav><!-- Portal navigation --></nav>
336
+ <main><%= yield %></main>
337
+ </body>
338
+ </html>
339
+ ```
340
+
341
+ ### Dashboard Controller
342
+
343
+ ```ruby
344
+ # packages/dashboard_portal/app/controllers/dashboard_portal/dashboard_controller.rb
345
+ module DashboardPortal
346
+ class DashboardController < PlutoniumController
347
+ def index
348
+ # Dashboard home page
349
+ end
350
+ end
351
+ end
352
+ ```
353
+
354
+ ## Multiple Portals Example
355
+
356
+ ```ruby
357
+ # Admin portal - full access
358
+ module AdminPortal
359
+ class Engine < Rails::Engine
360
+ include Plutonium::Portal::Engine
361
+
362
+ config.after_initialize do
363
+ scope_to_entity Organization, strategy: :path
364
+ end
365
+ end
366
+ end
367
+
368
+ # User dashboard - limited access
369
+ module DashboardPortal
370
+ class Engine < Rails::Engine
371
+ include Plutonium::Portal::Engine
372
+
373
+ config.after_initialize do
374
+ scope_to_entity Organization, strategy: :path
375
+ end
376
+ end
377
+ end
378
+
379
+ # Public portal - read-only, no auth
380
+ module PublicPortal
381
+ class Engine < Rails::Engine
382
+ include Plutonium::Portal::Engine
383
+ end
384
+ end
385
+ ```
386
+
387
+ Each portal can:
388
+ - Have different authentication
389
+ - Show different fields
390
+ - Allow different actions
391
+ - Use different layouts
392
+
393
+ ## Related Skills
394
+
395
+ - `package` - Package overview (features vs portals)
396
+ - `rodauth` - Authentication setup and configuration
397
+ - `connect-resource` - Connecting resources to portals
398
+ - `policy` - Portal-specific policies
399
+ - `definition` - Portal-specific definitions
400
+ - `controller` - Portal-specific controllers
@@ -0,0 +1,281 @@
1
+ ---
2
+ name: 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 `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 --portal AdminPortal
95
+ ```
96
+
97
+ This:
98
+ - Registers the resource in the portal
99
+ - Creates a route
100
+ - Optionally creates portal-specific definition override
101
+
102
+ See `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:** `model`, `model-features`
126
+
127
+ ### Definition (UI Layer)
128
+
129
+ ```ruby
130
+ class PostDefinition < ResourceDefinition
131
+ # Override auto-detected field types
132
+ input :content, as: :rich_text
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:** `definition`, `definition-fields`, `definition-actions`, `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:** `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:** `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...`
257
+ 2. **Connect** - `rails g pu:res:conn Model --portal PortalName`
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
+ - `model` - Model structure and organization
264
+ - `model-features` - has_cents, associations, scopes, routes
265
+ - `definition` - Definition overview and structure
266
+ - `definition-fields` - Fields, inputs, displays, columns
267
+ - `definition-actions` - Actions and interactions
268
+ - `interaction` - Writing interaction classes
269
+ - `definition-query` - Search, filters, scopes, sorting
270
+ - `policy` - Authorization and permissions
271
+ - `controller` - Controller customization
272
+ - `views` - Custom pages, displays, tables using Phlex
273
+ - `forms` - Custom form templates and field builders
274
+ - `assets` - TailwindCSS and component theming
275
+ - `package` - Feature and portal packages
276
+ - `portal` - Portal configuration and entity scoping
277
+ - `nested-resources` - Parent/child routes and scoping
278
+ - `installation` - Setting up Plutonium
279
+ - `rodauth` - Authentication setup
280
+ - `create-resource` - Scaffold generator details
281
+ - `connect-resource` - Portal connection details