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
data/docs/modules/form.md DELETED
@@ -1,495 +0,0 @@
1
- ---
2
- title: Form Module
3
- ---
4
-
5
- # Form Module
6
-
7
- The Form module is Plutonium's comprehensive system for building powerful, modern, and secure forms. It extends the `Phlexi::Form` library to provide a suite of enhanced input components, automatic field inference, secure-by-default associations, and seamless integration with your resources. This module is designed to make creating rich, accessible, and interactive forms a breeze.
8
-
9
- ::: tip
10
- The Form module is located in `lib/plutonium/ui/form/`.
11
- :::
12
-
13
- ## Key Features
14
-
15
- - **Rich Input Components**: Out-of-the-box support for markdown editors, date pickers, file uploads with previews, and international phone inputs.
16
- - **Secure by Default**: All associations use Signed Global IDs (SGIDs) to prevent parameter tampering, with automatic authorization checks.
17
- - **Intelligent Type Inference**: Automatically selects the best input component based on Active Record column types, saving you from boilerplate.
18
- - **Deep Resource Integration**: Generate forms automatically from your resource definitions, including support for conditional fields.
19
- - **Modern Frontend**: A complete theme system built with Tailwind CSS, Stimulus for interactivity, and first-class dark mode support.
20
- - **Complex Form Structures**: Easily manage associations with nested forms supporting dynamic "add" and "remove" functionality.
21
-
22
- ## Core Form Classes
23
-
24
- Plutonium provides several base form classes, each tailored for a specific purpose.
25
-
26
- ### `Form::Base`
27
-
28
- This is the foundation for all forms in Plutonium. It extends `Phlexi::Form::Base` and includes the core form builder with all the custom input components. You can inherit from this class to create custom, one-off forms.
29
-
30
- ::: details Builder Implementation
31
- The `Builder` class within `Form::Base` is where all the custom input tag methods are defined. It aliases standard Rails form helpers like `belongs_to_tag` to Plutonium's secure and enhanced versions.
32
-
33
- ```ruby
34
- class Plutonium::UI::Form::Base < Phlexi::Form::Base
35
- # ...
36
- class Builder < Builder
37
- include Plutonium::UI::Form::Options::InferredTypes
38
-
39
- # Enhanced input components
40
- def easymde_tag(**); end
41
- alias_method :markdown_tag, :easymde_tag
42
-
43
- def flatpickr_tag(**); end
44
- def int_tel_input_tag(**); end
45
- alias_method :phone_tag, :int_tel_input_tag
46
-
47
- def uppy_tag(**); end
48
- alias_method :file_tag, :uppy_tag
49
- alias_method :attachment_tag, :uppy_tag
50
-
51
- def slim_select_tag(**); end
52
- def secure_association_tag(**); end
53
- def secure_polymorphic_association_tag(**); end
54
-
55
- # Override default association methods
56
- alias_method :belongs_to_tag, :secure_association_tag
57
- alias_method :has_many_tag, :secure_association_tag
58
- alias_method :has_one_tag, :secure_association_tag
59
- alias_method :polymorphic_belongs_to_tag, :secure_polymorphic_association_tag
60
- end
61
- end
62
- ```
63
- :::
64
-
65
- ### `Form::Resource`
66
-
67
- This is a specialized form that intelligently renders inputs based on a resource definition. It's the primary way you'll create `new` and `edit` forms for your models. It automatically handles field rendering, nested resources, and conditional logic defined in your resource class.
68
-
69
- ```ruby
70
- class Plutonium::UI::Form::Resource < Base
71
- include Plutonium::UI::Form::Concerns::RendersNestedResourceFields
72
-
73
- def initialize(object, resource_fields:, resource_definition:, **options)
74
- # ...
75
- end
76
-
77
- def form_template
78
- render_fields # Renders inputs from resource_definition
79
- render_actions # Renders submit/cancel buttons
80
- end
81
- end
82
- ```
83
-
84
- ### `Form::Query`
85
-
86
- This form is built for search and filtering. It integrates with Plutonium's Query Objects to create search inputs, dynamic filter controls, and hidden fields for sorting and pagination, all submitted via GET requests to preserve filterable URLs.
87
-
88
- ```ruby
89
- class Plutonium::UI::Form::Query < Base
90
- def initialize(object, query_object:, page_size:, **options)
91
- # ... configured as a GET form with Turbo integration
92
- end
93
-
94
- def form_template
95
- render_search_fields
96
- render_filter_fields
97
- render_sort_fields
98
- render_scope_fields
99
- end
100
- end
101
- ```
102
-
103
- ### `Form::Interaction`
104
-
105
- This specialized form is designed for handling user interactions and actions. It automatically configures itself based on an interaction object, setting up the appropriate fields and form behavior for interactive actions.
106
-
107
- ```ruby
108
- class Plutonium::UI::Form::Interaction < Resource
109
- def initialize(interaction, **options)
110
- # Automatically configures fields from interaction attributes
111
- options[:resource_fields] = interaction.attribute_names.map(&:to_sym) - %i[resource resources]
112
- options[:resource_definition] = interaction
113
- # ...
114
- end
115
-
116
- # Form posts to the same page for interaction handling
117
- def form_action
118
- nil
119
- end
120
- end
121
- ```
122
-
123
- ## Enhanced Input Components
124
-
125
- Plutonium replaces standard form inputs with enhanced versions that provide a modern user experience.
126
-
127
- ### Input Block Syntax
128
-
129
- Input blocks provide additional flexibility for custom logic while ensuring proper form integration. **Important**: Input blocks can only use existing form builder methods (like `date_tag`, `text_tag`, etc.) because the form system requires inputs to be registered internally.
130
-
131
- ::: code-group
132
- ```ruby [Input Block Example]
133
- # Valid: Using form builder methods with custom logic
134
- input :birth_date do |f|
135
- case object.age_category
136
- when 'adult'
137
- f.date_tag(min: 18.years.ago.to_date)
138
- when 'minor'
139
- f.date_tag(max: 18.years.ago.to_date)
140
- else
141
- f.date_tag
142
- end
143
- end
144
- ```
145
-
146
- ```ruby [Custom Component Classes]
147
- # New: Pass component classes directly to as: option
148
- # Works for both input and display
149
- input :color_picker, as: ColorPickerComponent
150
- input :custom_slider, as: RangeSliderComponent
151
-
152
- display :status_badge, as: StatusBadgeComponent
153
- display :progress_chart, as: ProgressChartComponent
154
- ```
155
- :::
156
-
157
- ### Markdown Editor (Easymde)
158
-
159
- For rich text content, Plutonium integrates a client-side markdown editor based on [EasyMDE](https://github.com/Ionaru/easy-markdown-editor). It's automatically used for `rich_text` fields (like ActionText) and provides a live preview.
160
-
161
- ::: code-group
162
- ```ruby [Automatic Usage]
163
- # Automatically used for ActionText rich_text fields
164
- render field(:content).easymde_tag
165
-
166
- # Or explicitly with an alias
167
- render field(:description).markdown_tag
168
- ```
169
- :::
170
-
171
- ::: details Component Internals
172
- The component renders a `textarea` with a `data-controller="easymde"` attribute. It also includes logic to correctly handle ActionText objects by calling `to_plain_text` on the value.
173
-
174
- ```ruby
175
- class Plutonium::UI::Form::Components::Easymde < Phlexi::Form::Components::Base
176
- def view_template
177
- textarea(**attributes, data_controller: "easymde") do
178
- normalize_value(field.value)
179
- end
180
- end
181
- # ...
182
- end
183
- ```
184
- :::
185
-
186
- ### Date/Time Picker (Flatpickr)
187
-
188
- A beautiful and lightweight date/time picker from [Flatpickr](https://flatpickr.js.org/). It's automatically enabled for `date`, `time`, and `datetime` fields.
189
-
190
- ::: code-group
191
- ```ruby [Automatic Usage]
192
- # Automatically used based on field type
193
- render field(:published_at).flatpickr_tag # datetime field
194
- render field(:event_date).flatpickr_tag # date field
195
- render field(:meeting_time).flatpickr_tag # time field
196
- ```
197
- :::
198
-
199
- ::: details Component Internals
200
- The component simply adds a `data-controller="flatpickr"` attribute to a standard input. The corresponding Stimulus controller then inspects the input's `type` attribute (`date`, `time`, or `datetime-local`) to initialize Flatpickr with the correct options (e.g., with or without the time picker).
201
-
202
- ```ruby
203
- class Plutonium::UI::Form::Components::Flatpickr < Phlexi::Form::Components::Input
204
- private
205
-
206
- def build_input_attributes
207
- super
208
- attributes[:data_controller] = tokens(attributes[:data_controller], :flatpickr)
209
- end
210
- end
211
- ```
212
- :::
213
-
214
- ### International Phone Input
215
-
216
- For phone numbers, a user-friendly input with a country-code dropdown is provided by [intl-tel-input](https://github.com/jackocnr/intl-tel-input).
217
-
218
- ::: code-group
219
- ```ruby [Usage]
220
- # Automatically used for fields of type :tel
221
- render field(:phone).int_tel_input_tag
222
-
223
- # Or using its alias
224
- render field(:mobile).phone_tag
225
- ```
226
- :::
227
-
228
- ::: details Component Internals
229
- This component wraps the input in a `div` with a `data-controller="intl-tel-input"` and adds a `data_intl_tel_input_target` to the input itself, allowing the Stimulus controller to initialize the library.
230
-
231
- ```ruby
232
- class Plutonium::UI::Form::Components::IntlTelInput < Phlexi::Form::Components::Input
233
- def view_template
234
- div(data_controller: "intl-tel-input") do
235
- super # Renders the input with proper data targets
236
- end
237
- end
238
-
239
- private
240
-
241
- def build_input_attributes
242
- super
243
- attributes[:data_intl_tel_input_target] = tokens(attributes[:data_intl_tel_input_target], :input)
244
- end
245
- end
246
- ```
247
- :::
248
-
249
- ### File Upload (Uppy)
250
-
251
- File uploads are handled by [Uppy](https://uppy.io/), a sleek, modern uploader. It supports drag & drop, progress indicators, direct-to-cloud uploads, and interactive previews for existing attachments.
252
-
253
- ::: code-group
254
- ```ruby [Basic Usage]
255
- # Automatically used for file and Active Storage attachments
256
- render field(:avatar).uppy_tag
257
- render field(:documents).file_tag # alias
258
- render field(:gallery).attachment_tag # alias
259
- ```
260
-
261
- ```ruby [With Options]
262
- render field(:documents).uppy_tag(
263
- multiple: true,
264
- direct_upload: true, # For S3, etc.
265
- max_file_size: 10.megabytes,
266
- allowed_file_types: ['.pdf', '.doc']
267
- )
268
- ```
269
- :::
270
-
271
- ::: details Component Internals
272
- The Uppy component is quite sophisticated. It renders an interactive preview grid for existing attachments (each with its own `attachment-preview` Stimulus controller for deletion) and a file input managed by an `attachment-input` Stimulus controller that initializes Uppy.
273
-
274
- ```ruby
275
- class Plutonium::UI::Form::Components::Uppy < Phlexi::Form::Components::Input
276
- # Automatic features:
277
- # - Interactive preview of existing attachments
278
- # - Delete buttons for removing attachments
279
- # - Support for direct cloud uploads
280
- # - File type and size validation via Uppy options
281
- # ...
282
-
283
- def view_template
284
- div(class: "flex flex-col-reverse gap-2") do
285
- render_existing_attachments
286
- render_upload_interface
287
- end
288
- end
289
- end
290
- ```
291
- :::
292
-
293
- ### Secure Association Inputs
294
-
295
- Plutonium overrides all standard Rails association helpers (`belongs_to`, `has_many`, etc.) to use a secure, enhanced version that integrates with [SlimSelect](https://slimselectjs.com/) for a better UI.
296
-
297
- ::: code-group
298
- ```ruby [Association Types]
299
- # Automatically used for all standard association types
300
- render field(:author).belongs_to_tag
301
- render field(:tags).has_many_tag
302
- render field(:profile).has_one_tag
303
- render field(:commentable).polymorphic_belongs_to_tag
304
- ```
305
-
306
- ```ruby [With Options]
307
- render field(:category).belongs_to_tag(
308
- choices: Category.published.pluck(:name, :id),
309
- add_action: new_category_path, # Adds a "+" button to add new records
310
- skip_authorization: false # Enforces authorization policies
311
- )
312
- ```
313
- :::
314
-
315
- ::: details Security & Implementation
316
- The `SecureAssociation` component is the cornerstone of Plutonium's form security.
317
- - **SGID Encoding**: It uses `to_signed_global_id` as the value method, so raw database IDs are never exposed to the client.
318
- - **Authorization**: It uses `authorized_resource_scope` to ensure that the choices presented to the user are only the ones they are permitted to see.
319
- - **Add Action**: It can render an "add new" button that automatically includes a `return_to` parameter for a smooth UX.
320
-
321
- ```ruby
322
- class Plutonium::UI::Form::Components::SecureAssociation
323
- def choices
324
- collection = if @skip_authorization
325
- # ...
326
- else
327
- # Only show records user is authorized to see
328
- authorized_resource_scope(association_reflection.klass,
329
- relation: choices_from_association(association_reflection.klass))
330
- end
331
- # ...
332
- end
333
- end
334
- ```
335
- :::
336
-
337
- ## Type Inference System
338
-
339
- Plutonium is smart about choosing the right input for a given field, minimizing boilerplate in your forms.
340
-
341
- ### Automatic Component Selection
342
-
343
- The `InferredTypes` module overrides the default type inference to map common types to Plutonium's enhanced components.
344
-
345
- ```ruby
346
- module Plutonium::UI::Form::Options::InferredTypes
347
- private
348
-
349
- def infer_field_component
350
- case inferred_field_type
351
- when :rich_text
352
- return :markdown # Use Easymde for ActionText fields
353
- end
354
-
355
- inferred = super
356
- case inferred
357
- when :select
358
- :slim_select # Enhance selects with SlimSelect
359
- when :date, :time, :datetime
360
- :flatpickr # Use Flatpickr for date/time fields
361
- else
362
- inferred
363
- end
364
- end
365
- end
366
- ```
367
-
368
- This means you often don't need to specify the input type at all.
369
-
370
- ```ruby
371
- # These are automatically inferred:
372
- render field(:title) # -> input (string)
373
- render field(:content) # -> easymde (rich_text)
374
- render field(:published_at) # -> flatpickr (datetime)
375
- render field(:phone) # -> int_tel_input (tel)
376
- render field(:author) # -> secure_association (belongs_to)
377
- render field(:avatar) # -> uppy (Active Storage attachment)
378
- render field(:category) # -> slim_select (enum/select)
379
- ```
380
-
381
- ## Nested Resources
382
-
383
- Plutonium has first-class support for `accepts_nested_attributes_for`, allowing you to build complex forms with nested records. This is handled by the `RendersNestedResourceFields` concern in `Form::Resource`.
384
-
385
- ### Defining Nested Inputs
386
-
387
- You define nested inputs in your resource definition file. Plutonium will automatically detect the configuration from your Rails model's `accepts_nested_attributes_for` declaration—including options like `allow_destroy`, `update_only`, and `limit`—and use them to render the appropriate form controls.
388
-
389
- You can declare a nested input with a simple block or by referencing another definition class.
390
-
391
- ::: code-group
392
- ```ruby [Block Definition]
393
- # app/models/post.rb
394
- class Post < ApplicationRecord
395
- has_many :comments
396
- accepts_nested_attributes_for :comments, allow_destroy: true, limit: 5
397
- end
398
-
399
- # app/definitions/post_definition.rb
400
- class PostDefinition < Plutonium::Resource::Definition
401
- # This automatically inherits allow_destroy: true and limit: 5 from the model
402
- nested_input :comments do |n|
403
- n.input :content, as: :textarea
404
- n.input :author_name, as: :string
405
- end
406
- end
407
- ```
408
-
409
- ```ruby [Reference Definition]
410
- # app/models/post.rb
411
- class Post < ApplicationRecord
412
- has_many :tags
413
- accepts_nested_attributes_for :tags, update_only: true
414
- end
415
-
416
- # app/definitions/post_definition.rb
417
- class PostDefinition < Plutonium::Resource::Definition
418
- # This inherits update_only: true from the model
419
- nested_input :tags,
420
- using: TagDefinition,
421
- fields: %i[name color]
422
- end
423
- ```
424
- :::
425
-
426
- ### Overriding Configuration
427
-
428
- While Plutonium automatically uses your Rails configuration, you can easily override it by passing options directly to the `nested_input` method. Explicit options always take precedence.
429
-
430
- ```ruby
431
- class PostDefinition < Plutonium::Resource::Definition
432
- # Explicit options override the model's configuration
433
- nested_input :comments,
434
- allow_destroy: false, # Overrides model's allow_destroy: true
435
- limit: 10, # Overrides model's limit: 5
436
- description: "Add up to 10 comments for this post." do |n|
437
- n.input :content
438
- end
439
- end
440
- ```
441
-
442
- ### Automatic Rendering
443
-
444
- The `Form::Resource` class automatically renders the nested form based on your definition:
445
- - For `has_many` associations, it provides "Add" and "Remove" buttons, respecting the `limit`.
446
- - For `has_one` and `belongs_to` associations, it renders inline fields for a single record.
447
- - If `allow_destroy: true`, a "Delete" checkbox is rendered for persisted records.
448
- - If `update_only: true`, the "Add" button is hidden.
449
-
450
- ::: details Nested Rendering Internals
451
- The `render_nested_resource_field` method orchestrates the rendering of the nested form, including the header, existing records, the "add" button, and the template for new records. This is all managed by the `nested-resource-form-fields` Stimulus controller.
452
- :::
453
-
454
- ## Theming
455
-
456
- Forms are styled using a comprehensive theme system that leverages Tailwind CSS utility classes. The theme is defined in `lib/plutonium/ui/form/theme.rb`.
457
-
458
- ::: details Form Theme Configuration
459
- The `Plutonium::UI::Form::Theme.theme` method returns a hash where keys represent form elements (like `input`, `label`, `error`) and values are the corresponding CSS classes. It includes styles for layout, inputs in different states (valid, invalid), and all custom components.
460
-
461
- ```ruby
462
- class Plutonium::UI::Form::Theme < Phlexi::Form::Theme
463
- def self.theme
464
- super.merge({
465
- # Layout
466
- base: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3 p-6 space-y-6",
467
- fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4",
468
- actions_wrapper: "flex justify-end space-x-2",
469
-
470
- # Input styling
471
- input: "w-full p-2 border rounded-md shadow-sm dark:bg-gray-700 focus:ring-2",
472
- valid_input: "bg-green-50 border-green-500 ...",
473
- invalid_input: "bg-red-50 border-red-500 ...",
474
-
475
- # Enhanced component themes (aliases to base styles)
476
- flatpickr: :input,
477
- int_tel_input: :input,
478
- uppy: :file,
479
- association: :select,
480
- # ...
481
- })
482
- end
483
- end
484
- ```
485
- :::
486
-
487
- ## JavaScript & Stimulus
488
-
489
- Interactivity is powered by a set of dedicated Stimulus controllers. Plutonium automatically loads these controllers and the required third-party libraries.
490
-
491
- - **`form`**: The main controller for handling pre-submit refreshes (for conditional fields).
492
- - **`nested-resource-form-fields`**: Manages adding and removing nested form fields dynamically.
493
- - **`slim-select`**: Initializes the SlimSelect library on select fields.
494
- - **`easymde`**, **`flatpickr`**, **`intl-tel-input`**: Controllers for their respective input components.
495
- - **`attachment-input`** & **`attachment-preview`**: Work together to manage the Uppy file upload experience.