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,302 @@
1
+ ---
2
+ name: controller
3
+ description: Plutonium resource controllers - CRUD actions, customization, and integration
4
+ ---
5
+
6
+ # Plutonium Controllers
7
+
8
+ 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.
9
+
10
+ ## Base Classes
11
+
12
+ ```ruby
13
+ # app/controllers/resource_controller.rb (generated during install)
14
+ class ResourceController < ApplicationController
15
+ include Plutonium::Resource::Controller
16
+ end
17
+
18
+ # app/controllers/posts_controller.rb (generated per resource)
19
+ class PostsController < ::ResourceController
20
+ # Empty - all CRUD actions inherited
21
+ end
22
+ ```
23
+
24
+ ## What You Get for Free
25
+
26
+ Every resource controller automatically provides:
27
+
28
+ | Action | Route | Purpose |
29
+ |--------|-------|---------|
30
+ | `index` | GET /posts | List with pagination, search, filters, sorting |
31
+ | `show` | GET /posts/:id | Display single record |
32
+ | `new` | GET /posts/new | New record form |
33
+ | `create` | POST /posts | Create record |
34
+ | `edit` | GET /posts/:id/edit | Edit record form |
35
+ | `update` | PATCH /posts/:id | Update record |
36
+ | `destroy` | DELETE /posts/:id | Delete record |
37
+
38
+ Plus interactive action routes for custom operations defined in definitions.
39
+
40
+ ## When to Customize
41
+
42
+ **Use Definitions for:**
43
+ - Field configuration (inputs, displays, columns)
44
+ - Search, filters, scopes, sorting
45
+ - Actions (interactive operations)
46
+ - Form customization
47
+
48
+ **Customize Controller for:**
49
+ - Custom redirect logic
50
+ - Special parameter processing
51
+ - Non-standard authorization flows
52
+ - External integrations
53
+ - Response format changes
54
+
55
+ ## Override Hooks
56
+
57
+ All customization is done by overriding private methods:
58
+
59
+ ### Redirect Hooks
60
+
61
+ ```ruby
62
+ class PostsController < ::ResourceController
63
+ private
64
+
65
+ # Where to go after create/update: "show" (default), "edit", "new", "index"
66
+ def preferred_action_after_submit
67
+ "edit"
68
+ end
69
+
70
+ # Custom URL after create/update (overrides preferred_action_after_submit)
71
+ def redirect_url_after_submit
72
+ posts_path
73
+ end
74
+
75
+ # Custom URL after destroy
76
+ def redirect_url_after_destroy
77
+ posts_path
78
+ end
79
+ end
80
+ ```
81
+
82
+ ### Parameter Hooks
83
+
84
+ ```ruby
85
+ class PostsController < ::ResourceController
86
+ private
87
+
88
+ # Modify params before create/update
89
+ def resource_params
90
+ params = super
91
+ params[:tags] = params[:tags].split(",") if params[:tags].is_a?(String)
92
+ params
93
+ end
94
+ end
95
+ ```
96
+
97
+ ### Query Hooks
98
+
99
+ ```ruby
100
+ class PostsController < ::ResourceController
101
+ private
102
+
103
+ # Customize the index query
104
+ def filtered_resource_collection
105
+ base = current_authorized_scope
106
+ base = base.featured if params[:featured]
107
+ current_query_object.apply(base, raw_resource_query_params)
108
+ end
109
+ end
110
+ ```
111
+
112
+ ### Presentation Hooks
113
+
114
+ Control whether parent/entity fields appear in forms and displays:
115
+
116
+ ```ruby
117
+ class PostsController < ::ResourceController
118
+ private
119
+
120
+ # Show parent field in displays (default: false)
121
+ def present_parent?
122
+ true
123
+ end
124
+
125
+ # Include parent field in forms (default: same as present_parent?)
126
+ def submit_parent?
127
+ true
128
+ end
129
+
130
+ # Show scoped entity in displays (default: false)
131
+ def present_scoped_entity?
132
+ true
133
+ end
134
+
135
+ # Include scoped entity in forms (default: same as present_scoped_entity?)
136
+ def submit_scoped_entity?
137
+ true
138
+ end
139
+ end
140
+ ```
141
+
142
+ ## Custom Actions
143
+
144
+ ```ruby
145
+ class PostsController < ::ResourceController
146
+ def publish
147
+ authorize_current!(resource_record!, to: :publish?)
148
+ resource_record!.update!(published: true)
149
+ redirect_to resource_url_for(resource_record!), notice: "Published!"
150
+ end
151
+ end
152
+ ```
153
+
154
+ Note: For most custom operations, use Interactive Actions in definitions instead.
155
+
156
+ ## Key Methods
157
+
158
+ ### Resource Access
159
+
160
+ ```ruby
161
+ resource_class # The model class (e.g., Post)
162
+ resource_record! # Current record (raises if not found)
163
+ resource_record? # Current record (nil if not found)
164
+ resource_params # Permitted params for create/update
165
+ current_parent # Parent record for nested routes
166
+ ```
167
+
168
+ ### Authorization
169
+
170
+ ```ruby
171
+ authorize_current!(record, to: :action?) # Check permission
172
+ current_policy # Policy for current resource
173
+ permitted_attributes # Allowed attributes for action
174
+ current_authorized_scope # Scoped records user can access
175
+ ```
176
+
177
+ ### Definition Access
178
+
179
+ ```ruby
180
+ current_definition # Definition for current resource
181
+ ```
182
+
183
+ ### UI Building
184
+
185
+ ```ruby
186
+ build_form # Build form component
187
+ build_detail # Build show/detail component
188
+ build_collection # Build table component
189
+ ```
190
+
191
+ ### URL Generation
192
+
193
+ ```ruby
194
+ resource_url_for(@post) # URL for record
195
+ resource_url_for(@post, action: :edit) # Edit URL
196
+ resource_url_for(Post) # Index URL
197
+ ```
198
+
199
+ ## Nested Resources
200
+
201
+ Parent records are automatically resolved:
202
+
203
+ ```ruby
204
+ # Route: /users/:user_id/posts/:id
205
+ class PostsController < ::ResourceController
206
+ # current_parent returns the User
207
+ # resource_record! returns the Post scoped to that User
208
+ end
209
+ ```
210
+
211
+ Parent fields are automatically excluded from forms/displays. Override with presentation hooks (see above).
212
+
213
+ ## Entity Scoping (Multi-tenancy)
214
+
215
+ When a portal is scoped to an entity:
216
+
217
+ ```ruby
218
+ # packages/admin_portal/lib/engine.rb
219
+ module AdminPortal
220
+ class Engine < Rails::Engine
221
+ include Plutonium::Portal::Engine
222
+
223
+ config.after_initialize do
224
+ scope_to_entity Organization, strategy: :path
225
+ end
226
+ end
227
+ end
228
+ ```
229
+
230
+ Controllers automatically:
231
+ - Scope all queries to the entity
232
+ - Exclude entity field from forms
233
+ - Provide `current_scoped_entity` method
234
+
235
+ ## Authorization Verification
236
+
237
+ Controllers verify authorization was performed:
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
+ To skip verification for custom actions:
246
+
247
+ ```ruby
248
+ class PostsController < ::ResourceController
249
+ skip_verify_authorize_current only: [:custom_action]
250
+
251
+ def custom_action
252
+ # Handle authorization manually
253
+ end
254
+ end
255
+ ```
256
+
257
+ ## Response Formats
258
+
259
+ Controllers respond to multiple formats:
260
+
261
+ ```ruby
262
+ def show
263
+ # Responds to:
264
+ # - HTML (default)
265
+ # - JSON (via RABL templates)
266
+ # - Turbo Stream (for Hotwire)
267
+ end
268
+ ```
269
+
270
+ ## Portal-Specific Controllers
271
+
272
+ Each portal can have its own controller override:
273
+
274
+ ```ruby
275
+ # packages/admin_portal/app/controllers/admin_portal/posts_controller.rb
276
+ module AdminPortal
277
+ class PostsController < ResourceController
278
+ private
279
+
280
+ def preferred_action_after_submit
281
+ "index" # Admin prefers list view
282
+ end
283
+ end
284
+ end
285
+ ```
286
+
287
+ ## Best Practices
288
+
289
+ 1. **Keep controllers thin** - Use definitions for UI, policies for auth, interactions for logic
290
+ 2. **Don't override CRUD actions** - Customize via hooks (`resource_params`, `redirect_url_after_submit`)
291
+ 3. **Use interactive actions** - For custom operations, define in definition with interaction
292
+ 4. **Let authorization work** - Don't skip verification without good reason
293
+ 5. **Trust the framework** - Most customization belongs in definitions or policies
294
+
295
+ ## Related Skills
296
+
297
+ - `resource` - How controllers fit in the resource architecture
298
+ - `policy` - Authorization (used by controllers)
299
+ - `definition-actions` - Interactive actions (preferred over custom controller actions)
300
+ - `views` - Custom page, form, display, and table classes
301
+ - `nested-resources` - Parent/child routes and scoping
302
+ - `model` - Resource models
@@ -0,0 +1,240 @@
1
+ ---
2
+ name: create-resource
3
+ description: Generate Plutonium resources with models, migrations, controllers, policies, and definitions
4
+ ---
5
+
6
+ # Create Resource Skill
7
+
8
+ Use the `pu:res:scaffold` generator to create complete resources in Plutonium applications.
9
+
10
+ ## Command Syntax
11
+
12
+ ```bash
13
+ rails g pu:res:scaffold MODEL_NAME \
14
+ field1:type \
15
+ field2:type \
16
+ --dest=DESTINATION
17
+ ```
18
+
19
+ **IMPORTANT**: Always specify `--dest` to avoid interactive prompts:
20
+ - `--dest=main_app` for resources in the main application
21
+ - `--dest=package_name` for resources in a feature package
22
+
23
+ **IMPORTANT**: Quote fields containing `?` or `{}` to prevent shell expansion:
24
+ ```bash
25
+ 'field:type?' # Nullable - must quote
26
+ 'field:decimal{10,2}' # Options - must quote
27
+ 'field:decimal?{10,2}' # Both - must quote
28
+ ```
29
+
30
+ ## From Existing Models
31
+
32
+ For existing Rails projects with models you want to convert to Plutonium resources:
33
+
34
+ ### Option 1: Model already includes Plutonium::Resource::Record
35
+
36
+ ```bash
37
+ rails g pu:res:scaffold Post --no-migration --dest=main_app
38
+ ```
39
+
40
+ This generates only the definition, policy, and controller - leaving your model unchanged.
41
+
42
+ ### Option 2: Let the generator update the model
43
+
44
+ ```bash
45
+ rails g pu:res:scaffold Post --dest=main_app
46
+ ```
47
+
48
+ Run without attributes to auto-import fields from `model.content_columns`. This regenerates the model file, so review changes carefully.
49
+
50
+ ### Don't forget to include the module
51
+
52
+ Your model must include `Plutonium::Resource::Record` (directly or via inheritance):
53
+
54
+ ```ruby
55
+ class Post < ApplicationRecord
56
+ include Plutonium::Resource::Record
57
+ end
58
+
59
+ # Or inherit from a base class
60
+ class Post < ResourceRecord
61
+ end
62
+ ```
63
+
64
+ ## Field Type Syntax
65
+
66
+ Format: `name:type:index_type`
67
+
68
+ ### Basic Types
69
+
70
+ | Syntax | Result |
71
+ |--------|--------|
72
+ | `name:string` | Required string |
73
+ | `'name:string?'` | Nullable string |
74
+ | `age:integer` | Required integer |
75
+ | `'age:integer?'` | Nullable integer |
76
+ | `active:boolean` | Required boolean |
77
+ | `'active:boolean?'` | Nullable boolean |
78
+ | `content:text` | Required text |
79
+ | `'content:text?'` | Nullable text |
80
+ | `birth_date:date` | Required date |
81
+ | `'anniversary:date?'` | Nullable date |
82
+ | `starts_at:datetime` | Required datetime |
83
+ | `'ends_at:datetime?'` | Nullable datetime |
84
+ | `alarm_time:time` | Required time |
85
+ | `'reminder_time:time?'` | Nullable time |
86
+
87
+ ### Decimal with Precision
88
+
89
+ The `{precision,scale}` syntax **only works for decimal types**:
90
+
91
+ ```bash
92
+ 'latitude:decimal{11,8}' # precision: 11, scale: 8
93
+ 'amount:decimal{10,2}' # precision: 10, scale: 2
94
+ 'latitude:decimal?{11,8}' # nullable with precision
95
+ ```
96
+
97
+ **Note**: For default values on other types (boolean, integer, etc.), edit the migration manually.
98
+
99
+ ### References/Associations
100
+
101
+ ```bash
102
+ company:belongs_to # Required foreign key
103
+ 'parent:belongs_to?' # Nullable (null: true + optional: true)
104
+ user:references # Same as belongs_to
105
+ blogging/post:belongs_to # Cross-package reference
106
+ ```
107
+
108
+ Nullable references generate:
109
+ - Migration: `null: true`
110
+ - Model: `belongs_to :parent, optional: true`
111
+
112
+ ### Index Types (third segment)
113
+
114
+ ```bash
115
+ email:string:index # Regular index
116
+ email:string:uniq # Unique index
117
+ ```
118
+
119
+ ### Special Types
120
+
121
+ ```bash
122
+ password_digest # has_secure_password
123
+ auth_token:token # has_secure_token (auto unique index)
124
+ content:rich_text # has_rich_text
125
+ avatar:attachment # has_one_attached
126
+ photos:attachments # has_many_attached
127
+ price_cents:integer # has_cents (money field)
128
+ ```
129
+
130
+ Token fields automatically get a unique index in the migration.
131
+
132
+ ## Generator Options
133
+
134
+ - `--dest=DESTINATION` - Target destination (**always required** to avoid prompts)
135
+ - `main_app` for main application resources
136
+ - `package_name` for feature package resources
137
+ - `--no-model` - Skip model generation (keeps existing model)
138
+ - `--no-migration` - Skip migration generation (use with `--no-model` for existing models)
139
+
140
+ ## What Gets Generated
141
+
142
+ For **main_app** resources:
143
+ 1. **Model** - `app/models/model_name.rb`
144
+ 2. **Migration** - `db/migrate/xxx_create_model_names.rb`
145
+ 3. **Controller** - `app/controllers/model_names_controller.rb`
146
+ 4. **Policy** - `app/policies/model_name_policy.rb`
147
+ 5. **Definition** - `app/definitions/model_name_definition.rb`
148
+
149
+ For **packaged** resources:
150
+ 1. **Model** - `app/models/package_name/model_name.rb`
151
+ 2. **Migration** - `db/migrate/xxx_create_package_name_model_names.rb`
152
+ 3. **Controller** - `packages/package_name/app/controllers/package_name/model_names_controller.rb`
153
+ 4. **Policy** - `packages/package_name/app/policies/package_name/model_name_policy.rb`
154
+ 5. **Definition** - `packages/package_name/app/definitions/package_name/model_name_definition.rb`
155
+
156
+ ## Migration Customizations
157
+
158
+ The generator creates basic migrations. **Always review and customize** the migration before running:
159
+
160
+ ### Inline Indexes (preferred)
161
+
162
+ ```ruby
163
+ create_table :model_names do |t|
164
+ t.belongs_to :parent, null: false, foreign_key: true
165
+ t.string :name, null: false
166
+
167
+ t.timestamps
168
+
169
+ t.index :name
170
+ t.index [:parent_id, :name], unique: true
171
+ end
172
+ ```
173
+
174
+ ### Cascade Delete
175
+
176
+ ```ruby
177
+ t.belongs_to :parent, null: false, foreign_key: {on_delete: :cascade}
178
+ ```
179
+
180
+ ### Default Values
181
+
182
+ ```ruby
183
+ t.boolean :is_active, default: true
184
+ t.integer :status, default: 0
185
+ t.integer :count, null: true, default: 0
186
+ ```
187
+
188
+ ## Examples
189
+
190
+ ### Main App Resource
191
+
192
+ ```bash
193
+ rails g pu:res:scaffold Post \
194
+ user:belongs_to \
195
+ title:string \
196
+ 'content:text?' \
197
+ 'published_at:datetime?' \
198
+ --dest=main_app
199
+ ```
200
+
201
+ ### Resource with Precision and Indexes
202
+
203
+ ```bash
204
+ rails g pu:res:scaffold Property \
205
+ company:belongs_to \
206
+ code:string:uniq \
207
+ 'latitude:decimal{11,8}' \
208
+ 'longitude:decimal?{11,8}' \
209
+ 'value:decimal?{15,2}' \
210
+ 'notes:text?' \
211
+ --dest=main_app
212
+ ```
213
+
214
+ ### Optional Association
215
+
216
+ ```bash
217
+ rails g pu:res:scaffold Comment \
218
+ user:belongs_to \
219
+ 'parent:belongs_to?' \
220
+ body:text \
221
+ --dest=blogging
222
+ ```
223
+
224
+ ### Cross-Package Reference
225
+
226
+ ```bash
227
+ rails g pu:res:scaffold Comment \
228
+ user:belongs_to \
229
+ blogging/post:belongs_to \
230
+ body:text \
231
+ --dest=comments
232
+ ```
233
+
234
+ ## After Generation
235
+
236
+ 1. **Review and customize the migration** (add cascade delete, defaults, composite indexes)
237
+ 2. Run `rails db:migrate`
238
+ 3. Connect resource to portal: `rails g pu:res:conn Post --dest=admin_portal`
239
+ 4. Customize policy permissions as needed
240
+ 5. Add definition customizations for UI behavior