plutonium 0.33.1 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/# Plutonium: The pre-alpha demo.md +4 -2
- data/.claude/skills/assets/SKILL.md +416 -0
- data/.claude/skills/connect-resource/SKILL.md +112 -0
- data/.claude/skills/controller/SKILL.md +302 -0
- data/.claude/skills/create-resource/SKILL.md +240 -0
- data/.claude/skills/definition/SKILL.md +218 -0
- data/.claude/skills/definition-actions/SKILL.md +386 -0
- data/.claude/skills/definition-fields/SKILL.md +474 -0
- data/.claude/skills/definition-query/SKILL.md +334 -0
- data/.claude/skills/forms/SKILL.md +439 -0
- data/.claude/skills/installation/SKILL.md +300 -0
- data/.claude/skills/interaction/SKILL.md +382 -0
- data/.claude/skills/model/SKILL.md +267 -0
- data/.claude/skills/model-features/SKILL.md +286 -0
- data/.claude/skills/nested-resources/SKILL.md +274 -0
- data/.claude/skills/package/SKILL.md +191 -0
- data/.claude/skills/policy/SKILL.md +352 -0
- data/.claude/skills/portal/SKILL.md +400 -0
- data/.claude/skills/resource/SKILL.md +281 -0
- data/.claude/skills/rodauth/SKILL.md +452 -0
- data/.claude/skills/views/SKILL.md +563 -0
- data/Appraisals +46 -4
- data/CHANGELOG.md +32 -1
- data/app/assets/plutonium.css +2 -2
- data/config/brakeman.ignore +239 -0
- data/config/initializers/action_policy.rb +1 -1
- data/docs/.vitepress/config.ts +132 -47
- data/docs/concepts/architecture.md +226 -0
- data/docs/concepts/auto-detection.md +254 -0
- data/docs/concepts/index.md +61 -0
- data/docs/concepts/packages-portals.md +304 -0
- data/docs/concepts/resources.md +224 -0
- data/docs/cookbook/blog.md +412 -0
- data/docs/cookbook/index.md +289 -0
- data/docs/cookbook/saas.md +481 -0
- data/docs/getting-started/index.md +56 -0
- data/docs/getting-started/installation.md +146 -0
- data/docs/getting-started/tutorial/01-setup.md +118 -0
- data/docs/getting-started/tutorial/02-first-resource.md +180 -0
- data/docs/getting-started/tutorial/03-authentication.md +246 -0
- data/docs/getting-started/tutorial/04-authorization.md +170 -0
- data/docs/getting-started/tutorial/05-custom-actions.md +202 -0
- data/docs/getting-started/tutorial/06-nested-resources.md +147 -0
- data/docs/getting-started/tutorial/07-customizing-ui.md +254 -0
- data/docs/getting-started/tutorial/index.md +64 -0
- data/docs/guides/adding-resources.md +420 -0
- data/docs/guides/authentication.md +551 -0
- data/docs/guides/authorization.md +468 -0
- data/docs/guides/creating-packages.md +380 -0
- data/docs/guides/custom-actions.md +523 -0
- data/docs/guides/index.md +45 -0
- data/docs/guides/multi-tenancy.md +302 -0
- data/docs/guides/nested-resources.md +411 -0
- data/docs/guides/search-filtering.md +266 -0
- data/docs/guides/theming.md +321 -0
- data/docs/index.md +67 -26
- data/docs/public/CLAUDE.md +64 -21
- data/docs/reference/assets/index.md +496 -0
- data/docs/reference/controller/index.md +363 -0
- data/docs/reference/definition/actions.md +400 -0
- data/docs/reference/definition/fields.md +350 -0
- data/docs/reference/definition/index.md +252 -0
- data/docs/reference/definition/query.md +342 -0
- data/docs/reference/generators/index.md +469 -0
- data/docs/reference/index.md +49 -0
- data/docs/reference/interaction/index.md +445 -0
- data/docs/reference/model/features.md +248 -0
- data/docs/reference/model/index.md +219 -0
- data/docs/reference/policy/index.md +385 -0
- data/docs/reference/portal/index.md +382 -0
- data/docs/reference/views/forms.md +396 -0
- data/docs/reference/views/index.md +479 -0
- data/gemfiles/rails_7.gemfile +9 -2
- data/gemfiles/rails_7.gemfile.lock +146 -111
- data/gemfiles/rails_8.0.gemfile +20 -0
- data/gemfiles/rails_8.0.gemfile.lock +417 -0
- data/gemfiles/rails_8.1.gemfile +20 -0
- data/gemfiles/rails_8.1.gemfile.lock +419 -0
- data/lib/generators/pu/gem/dotenv/templates/.env +2 -0
- data/lib/generators/pu/gem/dotenv/templates/config/initializers/001_ensure_required_env.rb +3 -1
- data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +13 -16
- data/lib/generators/pu/pkg/portal/USAGE +65 -0
- data/lib/generators/pu/pkg/portal/portal_generator.rb +22 -9
- data/lib/generators/pu/res/conn/USAGE +71 -0
- data/lib/generators/pu/res/model/USAGE +106 -110
- data/lib/generators/pu/res/model/templates/model.rb.tt +6 -2
- data/lib/generators/pu/res/scaffold/USAGE +85 -0
- data/lib/generators/pu/rodauth/install_generator.rb +2 -6
- data/lib/generators/pu/rodauth/templates/config/initializers/url_options.rb +17 -0
- data/lib/generators/pu/skills/sync/USAGE +14 -0
- data/lib/generators/pu/skills/sync/sync_generator.rb +66 -0
- data/lib/plutonium/action_policy/sti_policy_lookup.rb +1 -1
- data/lib/plutonium/core/controller.rb +2 -2
- data/lib/plutonium/interaction/base.rb +1 -0
- data/lib/plutonium/package/engine.rb +2 -2
- data/lib/plutonium/query/adhoc_block.rb +6 -2
- data/lib/plutonium/query/model_scope.rb +1 -1
- data/lib/plutonium/railtie.rb +4 -0
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +1 -1
- data/lib/plutonium/resource/query_object.rb +38 -8
- data/lib/plutonium/ui/table/components/scopes_bar.rb +39 -34
- data/lib/plutonium/version.rb +1 -1
- data/lib/tasks/release.rake +19 -4
- data/package.json +1 -1
- metadata +76 -39
- data/brakeman.ignore +0 -28
- data/docs/api-examples.md +0 -49
- data/docs/guide/claude-code-guide.md +0 -74
- data/docs/guide/deep-dive/authorization.md +0 -189
- data/docs/guide/deep-dive/multitenancy.md +0 -256
- data/docs/guide/deep-dive/resources.md +0 -390
- data/docs/guide/getting-started/01-installation.md +0 -165
- data/docs/guide/index.md +0 -28
- data/docs/guide/introduction/01-what-is-plutonium.md +0 -211
- data/docs/guide/introduction/02-core-concepts.md +0 -440
- data/docs/guide/tutorial/01-project-setup.md +0 -75
- data/docs/guide/tutorial/02-creating-a-feature-package.md +0 -45
- data/docs/guide/tutorial/03-defining-resources.md +0 -90
- data/docs/guide/tutorial/04-creating-a-portal.md +0 -101
- data/docs/guide/tutorial/05-customizing-the-ui.md +0 -128
- data/docs/guide/tutorial/06-adding-custom-actions.md +0 -101
- data/docs/guide/tutorial/07-implementing-authorization.md +0 -90
- data/docs/markdown-examples.md +0 -85
- data/docs/modules/action.md +0 -244
- data/docs/modules/authentication.md +0 -236
- data/docs/modules/configuration.md +0 -599
- data/docs/modules/controller.md +0 -443
- data/docs/modules/core.md +0 -316
- data/docs/modules/definition.md +0 -1308
- data/docs/modules/display.md +0 -759
- data/docs/modules/form.md +0 -495
- data/docs/modules/generator.md +0 -400
- data/docs/modules/index.md +0 -167
- data/docs/modules/interaction.md +0 -642
- data/docs/modules/package.md +0 -151
- data/docs/modules/policy.md +0 -176
- data/docs/modules/portal.md +0 -710
- data/docs/modules/query.md +0 -297
- data/docs/modules/resource_record.md +0 -618
- data/docs/modules/routing.md +0 -690
- data/docs/modules/table.md +0 -301
- data/docs/modules/ui.md +0 -631
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: forms
|
|
3
|
+
description: Plutonium forms - custom templates, Phlex form components, field builders, and theming
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plutonium Forms
|
|
7
|
+
|
|
8
|
+
Plutonium forms are built on [Phlexi::Form](https://github.com/radioactive-labs/phlexi-form), providing a Ruby-first approach to form building with Phlex components.
|
|
9
|
+
|
|
10
|
+
## Form Class Hierarchy
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
Phlexi::Form::Base
|
|
14
|
+
└── Plutonium::UI::Form::Base # Base with Plutonium components
|
|
15
|
+
├── Plutonium::UI::Form::Resource # Resource CRUD forms
|
|
16
|
+
│ └── Plutonium::UI::Form::Interaction # Interactive action forms
|
|
17
|
+
└── Plutonium::UI::Form::Query # Search/filter forms
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Customizing Resource Forms
|
|
21
|
+
|
|
22
|
+
### Override in Definition
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
class PostDefinition < ResourceDefinition
|
|
26
|
+
class Form < Form
|
|
27
|
+
def form_template
|
|
28
|
+
# Your custom form layout
|
|
29
|
+
render_fields
|
|
30
|
+
render_actions
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Form Template Methods
|
|
37
|
+
|
|
38
|
+
| Method | Description |
|
|
39
|
+
|--------|-------------|
|
|
40
|
+
| `form_template` | Main template method to override |
|
|
41
|
+
| `render_fields` | Render all permitted fields |
|
|
42
|
+
| `render_resource_field(name)` | Render a single field by name |
|
|
43
|
+
| `render_actions` | Render submit buttons |
|
|
44
|
+
| `fields_wrapper { }` | Wrapper div for field grid |
|
|
45
|
+
| `actions_wrapper { }` | Wrapper div for buttons |
|
|
46
|
+
|
|
47
|
+
### Form Attributes
|
|
48
|
+
|
|
49
|
+
| Attribute | Description |
|
|
50
|
+
|-----------|-------------|
|
|
51
|
+
| `object` / `record` | The form object being edited |
|
|
52
|
+
| `resource_fields` | Array of permitted field names |
|
|
53
|
+
| `resource_definition` | The definition instance |
|
|
54
|
+
|
|
55
|
+
## Custom Form Layout
|
|
56
|
+
|
|
57
|
+
### Sectioned Form
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
class PostDefinition < ResourceDefinition
|
|
61
|
+
class Form < Form
|
|
62
|
+
def form_template
|
|
63
|
+
section("Basic Information") {
|
|
64
|
+
render_resource_field :title
|
|
65
|
+
render_resource_field :slug
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
section("Content") {
|
|
69
|
+
render_resource_field :content
|
|
70
|
+
render_resource_field :excerpt
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
section("Publishing") {
|
|
74
|
+
render_resource_field :published_at
|
|
75
|
+
render_resource_field :category
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
render_actions
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def section(title, &)
|
|
84
|
+
div(class: "mb-8") {
|
|
85
|
+
h3(class: "text-lg font-semibold mb-4 text-gray-900 dark:text-white") { title }
|
|
86
|
+
fields_wrapper(&)
|
|
87
|
+
}
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Two-Column Layout
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
class Form < Form
|
|
97
|
+
def form_template
|
|
98
|
+
div(class: "grid grid-cols-1 lg:grid-cols-3 gap-6") {
|
|
99
|
+
# Main content - 2 columns
|
|
100
|
+
div(class: "lg:col-span-2") {
|
|
101
|
+
fields_wrapper {
|
|
102
|
+
render_resource_field :title
|
|
103
|
+
render_resource_field :content
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Sidebar - 1 column
|
|
108
|
+
div(class: "space-y-4") {
|
|
109
|
+
Panel {
|
|
110
|
+
h4(class: "font-medium mb-2") { "Settings" }
|
|
111
|
+
render_resource_field :status
|
|
112
|
+
render_resource_field :visibility
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
render_actions
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Field Builder
|
|
123
|
+
|
|
124
|
+
When using `render_resource_field`, Plutonium uses the field builder. For custom rendering, use the `field` method directly.
|
|
125
|
+
|
|
126
|
+
### Basic Field Usage
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
def form_template
|
|
130
|
+
# Using field builder directly
|
|
131
|
+
render field(:title).wrapped {|f| f.input_tag }
|
|
132
|
+
render field(:content).wrapped {|f| f.easymde_tag }
|
|
133
|
+
render field(:published).wrapped {|f| f.checkbox_tag }
|
|
134
|
+
|
|
135
|
+
render_actions
|
|
136
|
+
end
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Field Builder Methods
|
|
140
|
+
|
|
141
|
+
The field builder (`f`) provides these tag methods:
|
|
142
|
+
|
|
143
|
+
| Method | Input Type |
|
|
144
|
+
|--------|------------|
|
|
145
|
+
| `f.input_tag` | Text input (auto-detects type) |
|
|
146
|
+
| `f.string_tag` | Text input |
|
|
147
|
+
| `f.text_tag` | Textarea |
|
|
148
|
+
| `f.number_tag` | Number input |
|
|
149
|
+
| `f.email_tag` | Email input |
|
|
150
|
+
| `f.password_tag` | Password input |
|
|
151
|
+
| `f.url_tag` | URL input |
|
|
152
|
+
| `f.tel_tag` | Telephone input |
|
|
153
|
+
| `f.hidden_tag` | Hidden input |
|
|
154
|
+
| `f.checkbox_tag` | Checkbox |
|
|
155
|
+
| `f.select_tag` | Select dropdown |
|
|
156
|
+
| `f.radio_button_tag` | Radio buttons |
|
|
157
|
+
|
|
158
|
+
### Plutonium-Enhanced Tags
|
|
159
|
+
|
|
160
|
+
| Method | Description |
|
|
161
|
+
|--------|-------------|
|
|
162
|
+
| `f.easymde_tag` / `f.markdown_tag` | Markdown editor (EasyMDE) |
|
|
163
|
+
| `f.slim_select_tag` | Enhanced select (Slim Select) |
|
|
164
|
+
| `f.flatpickr_tag` | Date/time picker (Flatpickr) |
|
|
165
|
+
| `f.phone_tag` / `f.int_tel_input_tag` | International phone input |
|
|
166
|
+
| `f.uppy_tag` / `f.file_tag` | File upload (Uppy) |
|
|
167
|
+
| `f.secure_association_tag` | Association with authorization |
|
|
168
|
+
| `f.belongs_to_tag` | Belongs-to association |
|
|
169
|
+
| `f.has_many_tag` | Has-many association |
|
|
170
|
+
| `f.has_one_tag` | Has-one association |
|
|
171
|
+
| `f.key_value_store_tag` | Key-value pairs |
|
|
172
|
+
|
|
173
|
+
### Field with Options
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Select with choices
|
|
177
|
+
render field(:status).wrapped { |f|
|
|
178
|
+
f.select_tag(choices: %w[draft published archived])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# Date picker with options
|
|
182
|
+
render field(:published_at).wrapped { |f|
|
|
183
|
+
f.flatpickr_tag(min_date: Date.today, enable_time: true)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
# File upload with restrictions
|
|
187
|
+
render field(:avatar).wrapped { |f|
|
|
188
|
+
f.uppy_tag(
|
|
189
|
+
allowed_file_types: %w[.jpg .png .gif],
|
|
190
|
+
max_file_size: 5.megabytes
|
|
191
|
+
)
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Wrapped vs Unwrapped
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
# Wrapped - includes label, hint, errors
|
|
199
|
+
render field(:title).wrapped { |f| f.input_tag }
|
|
200
|
+
|
|
201
|
+
# Unwrapped - just the input element
|
|
202
|
+
render field(:title).input_tag
|
|
203
|
+
|
|
204
|
+
# Custom wrapper options
|
|
205
|
+
render field(:title).wrapped(class: "col-span-full") { |f|
|
|
206
|
+
f.input_tag
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Input Configuration in Definitions
|
|
211
|
+
|
|
212
|
+
Define inputs in the definition, render them in the form:
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
class PostDefinition < ResourceDefinition
|
|
216
|
+
# Configure inputs
|
|
217
|
+
input :title, hint: "Be descriptive", placeholder: "Enter title"
|
|
218
|
+
input :content, as: :markdown
|
|
219
|
+
input :status, as: :select, choices: %w[draft published]
|
|
220
|
+
input :published_at, as: :flatpickr
|
|
221
|
+
|
|
222
|
+
# Custom input with block
|
|
223
|
+
input :category do |f|
|
|
224
|
+
choices = Category.active.pluck(:name, :id)
|
|
225
|
+
f.select_tag(choices: choices)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
class Form < Form
|
|
229
|
+
def form_template
|
|
230
|
+
# render_resource_field uses the input configuration
|
|
231
|
+
render_resource_field :title
|
|
232
|
+
render_resource_field :content
|
|
233
|
+
render_resource_field :status
|
|
234
|
+
render_resource_field :published_at
|
|
235
|
+
render_resource_field :category
|
|
236
|
+
|
|
237
|
+
render_actions
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Dynamic Forms (pre_submit)
|
|
244
|
+
|
|
245
|
+
Fields with `pre_submit: true` trigger form re-rendering on change:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
class PostDefinition < ResourceDefinition
|
|
249
|
+
input :post_type, as: :select,
|
|
250
|
+
choices: %w[article video podcast],
|
|
251
|
+
pre_submit: true
|
|
252
|
+
|
|
253
|
+
input :video_url,
|
|
254
|
+
condition: -> { object.post_type == "video" }
|
|
255
|
+
|
|
256
|
+
input :podcast_url,
|
|
257
|
+
condition: -> { object.post_type == "podcast" }
|
|
258
|
+
end
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
When `post_type` changes, the form re-renders via Turbo and shows/hides conditional fields.
|
|
262
|
+
|
|
263
|
+
## Nested Forms
|
|
264
|
+
|
|
265
|
+
For `has_many` / `has_one` associations with `accepts_nested_attributes_for`:
|
|
266
|
+
|
|
267
|
+
### Model Setup
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
class Post < ResourceRecord
|
|
271
|
+
has_many :comments
|
|
272
|
+
accepts_nested_attributes_for :comments, allow_destroy: true
|
|
273
|
+
end
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Definition Setup
|
|
277
|
+
|
|
278
|
+
```ruby
|
|
279
|
+
class PostDefinition < ResourceDefinition
|
|
280
|
+
nested_input :comments do |n|
|
|
281
|
+
n.input :author_name
|
|
282
|
+
n.input :body, as: :text
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
# Or reference another definition
|
|
286
|
+
nested_input :comments, using: CommentDefinition, fields: %i[author_name body]
|
|
287
|
+
end
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Custom Nested Rendering
|
|
291
|
+
|
|
292
|
+
```ruby
|
|
293
|
+
class Form < Form
|
|
294
|
+
def form_template
|
|
295
|
+
render_resource_field :title
|
|
296
|
+
render_resource_field :content
|
|
297
|
+
|
|
298
|
+
# Nested fields are automatically handled
|
|
299
|
+
render_resource_field :comments
|
|
300
|
+
|
|
301
|
+
render_actions
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Interaction Forms
|
|
307
|
+
|
|
308
|
+
Forms for interactive actions use `Plutonium::UI::Form::Interaction`:
|
|
309
|
+
|
|
310
|
+
```ruby
|
|
311
|
+
class PublishPostInteraction < ResourceInteraction
|
|
312
|
+
attribute :publish_date, :date
|
|
313
|
+
attribute :notify_subscribers, :boolean, default: true
|
|
314
|
+
|
|
315
|
+
# Custom form
|
|
316
|
+
class Form < Form
|
|
317
|
+
def form_template
|
|
318
|
+
div(class: "space-y-4") {
|
|
319
|
+
render_resource_field :publish_date
|
|
320
|
+
render_resource_field :notify_subscribers
|
|
321
|
+
}
|
|
322
|
+
render_actions
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Form Actions
|
|
329
|
+
|
|
330
|
+
### Default Actions
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
def render_actions
|
|
334
|
+
actions_wrapper {
|
|
335
|
+
# "Create and add another" / "Update and continue editing" button
|
|
336
|
+
# Primary submit button
|
|
337
|
+
render submit_button
|
|
338
|
+
}
|
|
339
|
+
end
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Custom Actions
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
def render_actions
|
|
346
|
+
actions_wrapper {
|
|
347
|
+
# Cancel link
|
|
348
|
+
a(href: resource_url_for(resource_class), class: "btn btn-secondary") {
|
|
349
|
+
"Cancel"
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
# Save as draft
|
|
353
|
+
button(type: :submit, name: "draft", value: "1", class: "btn") {
|
|
354
|
+
"Save Draft"
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
# Primary submit
|
|
358
|
+
render submit_button
|
|
359
|
+
}
|
|
360
|
+
end
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
## Form Context
|
|
364
|
+
|
|
365
|
+
Inside form templates, you have access to:
|
|
366
|
+
|
|
367
|
+
```ruby
|
|
368
|
+
class Form < Form
|
|
369
|
+
def form_template
|
|
370
|
+
# Form object
|
|
371
|
+
object # The record
|
|
372
|
+
record # Alias for object
|
|
373
|
+
object.new_record? # Check if creating
|
|
374
|
+
|
|
375
|
+
# Request context
|
|
376
|
+
current_user
|
|
377
|
+
current_parent
|
|
378
|
+
current_scoped_entity
|
|
379
|
+
request
|
|
380
|
+
params
|
|
381
|
+
|
|
382
|
+
# Definition
|
|
383
|
+
resource_definition
|
|
384
|
+
resource_fields # Permitted fields
|
|
385
|
+
|
|
386
|
+
# URL helpers
|
|
387
|
+
resource_url_for(object)
|
|
388
|
+
resource_url_for(Post, action: :new)
|
|
389
|
+
|
|
390
|
+
# Rails helpers
|
|
391
|
+
helpers.link_to(...)
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
## Theming
|
|
397
|
+
|
|
398
|
+
Forms use a theme system for consistent styling:
|
|
399
|
+
|
|
400
|
+
```ruby
|
|
401
|
+
class PostDefinition < ResourceDefinition
|
|
402
|
+
class Form < Form
|
|
403
|
+
class Theme < Plutonium::UI::Form::Theme
|
|
404
|
+
def self.theme
|
|
405
|
+
super.merge({
|
|
406
|
+
fields_wrapper: "grid grid-cols-2 gap-6",
|
|
407
|
+
actions_wrapper: "flex justify-between mt-8",
|
|
408
|
+
input: "w-full p-3 border-2 rounded-lg",
|
|
409
|
+
label: "block mb-1 font-bold text-gray-700"
|
|
410
|
+
})
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Theme Keys
|
|
418
|
+
|
|
419
|
+
| Key | Description |
|
|
420
|
+
|-----|-------------|
|
|
421
|
+
| `base` | Form container |
|
|
422
|
+
| `fields_wrapper` | Grid wrapper for fields |
|
|
423
|
+
| `actions_wrapper` | Wrapper for buttons |
|
|
424
|
+
| `wrapper` | Individual field wrapper |
|
|
425
|
+
| `label` | Label styling |
|
|
426
|
+
| `input` | Input styling |
|
|
427
|
+
| `hint` | Hint text styling |
|
|
428
|
+
| `error` | Error message styling |
|
|
429
|
+
| `button` | Submit button styling |
|
|
430
|
+
| `checkbox` | Checkbox styling |
|
|
431
|
+
| `select` | Select styling |
|
|
432
|
+
|
|
433
|
+
## Related Skills
|
|
434
|
+
|
|
435
|
+
- `definition-fields` - Input configuration (as:, hint:, condition:)
|
|
436
|
+
- `views` - Custom page classes
|
|
437
|
+
- `assets` - TailwindCSS and component theming
|
|
438
|
+
- `interaction` - Interactive action forms
|
|
439
|
+
- `nested-resources` - Parent/child forms
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: installation
|
|
3
|
+
description: Installing Plutonium in a Rails application - setup, generators, and configuration
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plutonium Installation
|
|
7
|
+
|
|
8
|
+
## New Rails App (Recommended)
|
|
9
|
+
|
|
10
|
+
Use the Rails template for a fully configured setup:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
rails new myapp -a propshaft -j esbuild -c tailwind \
|
|
14
|
+
-m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This sets up Rails with Propshaft, esbuild, TailwindCSS, and Plutonium in one command.
|
|
18
|
+
|
|
19
|
+
## Existing Rails App
|
|
20
|
+
|
|
21
|
+
### Option 1: Rails Template
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bin/rails app:template \
|
|
25
|
+
LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Option 2: Manual Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Add to Gemfile
|
|
32
|
+
gem "plutonium"
|
|
33
|
+
|
|
34
|
+
# Install
|
|
35
|
+
bundle install
|
|
36
|
+
rails generate pu:core:install
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## What Gets Generated
|
|
40
|
+
|
|
41
|
+
After `pu:core:install`:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
app/
|
|
45
|
+
├── controllers/
|
|
46
|
+
│ ├── plutonium_controller.rb # Base controller
|
|
47
|
+
│ └── resource_controller.rb # Resource CRUD base
|
|
48
|
+
├── definitions/
|
|
49
|
+
│ └── resource_definition.rb # Definition base class
|
|
50
|
+
├── interactions/
|
|
51
|
+
│ └── resource_interaction.rb # Interaction base class
|
|
52
|
+
├── models/
|
|
53
|
+
│ └── resource_record.rb # Abstract model base
|
|
54
|
+
├── policies/
|
|
55
|
+
│ └── resource_policy.rb # Policy base class
|
|
56
|
+
└── views/
|
|
57
|
+
└── layouts/
|
|
58
|
+
└── resource.html.erb # Base layout
|
|
59
|
+
|
|
60
|
+
config/
|
|
61
|
+
├── initializers/
|
|
62
|
+
│ └── plutonium.rb # Configuration
|
|
63
|
+
└── packages.rb # Package loader
|
|
64
|
+
|
|
65
|
+
packages/
|
|
66
|
+
└── .keep
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Base Classes
|
|
70
|
+
|
|
71
|
+
### ResourceController
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
class ResourceController < PlutoniumController
|
|
75
|
+
include Plutonium::Resource::Controller
|
|
76
|
+
# Provides: index, show, new, create, edit, update, destroy
|
|
77
|
+
# Plus: interactive actions, authorization, query handling
|
|
78
|
+
end
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### ResourcePolicy
|
|
82
|
+
|
|
83
|
+
```ruby
|
|
84
|
+
class ResourcePolicy < Plutonium::Resource::Policy
|
|
85
|
+
def create?
|
|
86
|
+
true # Override with your logic
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def read?
|
|
90
|
+
true
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### ResourceDefinition
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
class ResourceDefinition < Plutonium::Resource::Definition
|
|
99
|
+
# Add app-wide definition defaults here
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### ResourceRecord
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
class ResourceRecord < ApplicationRecord
|
|
107
|
+
self.abstract_class = true
|
|
108
|
+
# Models inherit from this for Plutonium features
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Authentication Setup
|
|
113
|
+
|
|
114
|
+
### Install Rodauth
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
rails generate pu:rodauth:install
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Create Account Types
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
# Basic user account
|
|
124
|
+
rails generate pu:rodauth:account user
|
|
125
|
+
|
|
126
|
+
# Admin with 2FA, lockout, audit logging
|
|
127
|
+
rails generate pu:rodauth:admin
|
|
128
|
+
|
|
129
|
+
# Customer with entity association
|
|
130
|
+
rails generate pu:rodauth:customer customer
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Account Options
|
|
134
|
+
|
|
135
|
+
| Option | Description |
|
|
136
|
+
|--------|-------------|
|
|
137
|
+
| `--defaults` | Enable common features (login, logout, remember, reset_password) |
|
|
138
|
+
| `--kitchen_sink` | Enable all available features |
|
|
139
|
+
| `--no-allow_signup` | Disable public signup |
|
|
140
|
+
| `--entity=Organization` | Create associated entity model |
|
|
141
|
+
|
|
142
|
+
### Connect Auth to Controllers
|
|
143
|
+
|
|
144
|
+
```ruby
|
|
145
|
+
# app/controllers/resource_controller.rb
|
|
146
|
+
class ResourceController < PlutoniumController
|
|
147
|
+
include Plutonium::Resource::Controller
|
|
148
|
+
include Plutonium::Auth::Rodauth(:user) # Add this
|
|
149
|
+
end
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Creating Your First Resource
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
rails generate pu:res:scaffold Post user:belongs_to title:string content:text
|
|
156
|
+
rails db:migrate
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Creating a Portal
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
rails generate pu:pkg:portal admin
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Select authentication when prompted:
|
|
166
|
+
- **Rodauth account** - Use existing auth
|
|
167
|
+
- **Public access** - No authentication
|
|
168
|
+
- **Bring your own** - Custom implementation
|
|
169
|
+
|
|
170
|
+
### Mount the Portal
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
# config/routes.rb
|
|
174
|
+
Rails.application.routes.draw do
|
|
175
|
+
mount AdminPortal::Engine, at: "/admin"
|
|
176
|
+
end
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Connect Resources to Portal
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
rails generate pu:res:conn Post --dest=admin_portal
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Configuration
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
# config/initializers/plutonium.rb
|
|
189
|
+
Plutonium.configure do |config|
|
|
190
|
+
config.load_defaults 1.0
|
|
191
|
+
|
|
192
|
+
# Custom assets (optional)
|
|
193
|
+
# config.assets.stylesheet = "custom_stylesheet"
|
|
194
|
+
# config.assets.script = "custom_script"
|
|
195
|
+
# config.assets.logo = "custom_logo.png"
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Package System
|
|
200
|
+
|
|
201
|
+
Packages are loaded from `config/packages.rb`:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
Dir.glob(File.expand_path("../packages/**/lib/engine.rb", __dir__)) { |package| load package }
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Create packages in `packages/` directory:
|
|
208
|
+
- **Feature packages** - Business logic (`rails g pu:pkg:package blogging`)
|
|
209
|
+
- **Portal packages** - Web interfaces (`rails g pu:pkg:portal admin`)
|
|
210
|
+
|
|
211
|
+
## Post-Installation Checklist
|
|
212
|
+
|
|
213
|
+
1. **Install core**
|
|
214
|
+
```bash
|
|
215
|
+
rails generate pu:core:install
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
2. **Setup authentication** (if needed)
|
|
219
|
+
```bash
|
|
220
|
+
rails generate pu:rodauth:install
|
|
221
|
+
rails generate pu:rodauth:account user
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
3. **Create a portal**
|
|
225
|
+
```bash
|
|
226
|
+
rails generate pu:pkg:portal admin
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
4. **Create resources**
|
|
230
|
+
```bash
|
|
231
|
+
rails generate pu:res:scaffold Post title:string content:text
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
5. **Connect resources to portal**
|
|
235
|
+
```bash
|
|
236
|
+
rails generate pu:res:conn Post --dest=admin_portal
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
6. **Run migrations**
|
|
240
|
+
```bash
|
|
241
|
+
rails db:migrate
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
7. **Mount portal** (add to `config/routes.rb`)
|
|
245
|
+
```ruby
|
|
246
|
+
mount AdminPortal::Engine, at: "/admin"
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
8. **Start server**
|
|
250
|
+
```bash
|
|
251
|
+
rails server
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## Converting Existing Models
|
|
255
|
+
|
|
256
|
+
For models that already exist in your app:
|
|
257
|
+
|
|
258
|
+
1. Include the module:
|
|
259
|
+
```ruby
|
|
260
|
+
class Post < ApplicationRecord
|
|
261
|
+
include Plutonium::Resource::Record
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
2. Generate supporting files (skips model/migration):
|
|
266
|
+
```bash
|
|
267
|
+
rails g pu:res:scaffold Post
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
3. Connect to portal:
|
|
271
|
+
```bash
|
|
272
|
+
rails g pu:res:conn Post --dest=admin_portal
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Generator Reference
|
|
276
|
+
|
|
277
|
+
| Generator | Purpose |
|
|
278
|
+
|-----------|---------|
|
|
279
|
+
| `pu:core:install` | Initial Plutonium setup |
|
|
280
|
+
| `pu:rodauth:install` | Setup Rodauth authentication |
|
|
281
|
+
| `pu:rodauth:account NAME` | Create user account type |
|
|
282
|
+
| `pu:rodauth:admin` | Create admin account with 2FA |
|
|
283
|
+
| `pu:rodauth:customer NAME` | Create customer with entity |
|
|
284
|
+
| `pu:pkg:package NAME` | Create feature package |
|
|
285
|
+
| `pu:pkg:portal NAME` | Create portal package |
|
|
286
|
+
| `pu:res:scaffold NAME` | Create resource (model, policy, definition, controller) |
|
|
287
|
+
| `pu:res:conn NAME` | Connect resource to portal |
|
|
288
|
+
| `pu:eject:layout` | Eject layout files for customization |
|
|
289
|
+
| `pu:skills:sync` | Sync Claude Code skills to project |
|
|
290
|
+
|
|
291
|
+
## Related Skills
|
|
292
|
+
|
|
293
|
+
- `resource` - Resource architecture overview
|
|
294
|
+
- `rodauth` - Authentication setup and configuration
|
|
295
|
+
- `package` - Feature and portal packages
|
|
296
|
+
- `portal` - Portal configuration
|
|
297
|
+
- `views` - Custom pages, layouts, and Phlex components
|
|
298
|
+
- `assets` - TailwindCSS and custom styling
|
|
299
|
+
- `create-resource` - Resource scaffold options
|
|
300
|
+
- `connect-resource` - Portal connection
|