plutonium 0.23.4 → 0.24.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/plutonium.css +2 -2
  3. data/config/initializers/rabl.rb +17 -0
  4. data/config/initializers/sqlite_json_alias.rb +1 -1
  5. data/docs/.vitepress/config.ts +60 -19
  6. data/docs/guide/cursor-rules.md +75 -0
  7. data/docs/guide/deep-dive/authorization.md +189 -0
  8. data/docs/guide/{getting-started → deep-dive}/resources.md +137 -0
  9. data/docs/guide/getting-started/{installation.md → 01-installation.md} +0 -105
  10. data/docs/guide/index.md +28 -0
  11. data/docs/guide/introduction/02-core-concepts.md +440 -0
  12. data/docs/guide/tutorial/01-project-setup.md +75 -0
  13. data/docs/guide/tutorial/02-creating-a-feature-package.md +45 -0
  14. data/docs/guide/tutorial/03-defining-resources.md +90 -0
  15. data/docs/guide/tutorial/04-creating-a-portal.md +101 -0
  16. data/docs/guide/tutorial/05-customizing-the-ui.md +128 -0
  17. data/docs/guide/tutorial/06-adding-custom-actions.md +101 -0
  18. data/docs/guide/tutorial/07-implementing-authorization.md +90 -0
  19. data/docs/index.md +24 -31
  20. data/docs/modules/action.md +190 -0
  21. data/docs/modules/authentication.md +236 -0
  22. data/docs/modules/configuration.md +599 -0
  23. data/docs/modules/controller.md +398 -0
  24. data/docs/modules/core.md +316 -0
  25. data/docs/modules/definition.md +876 -0
  26. data/docs/modules/display.md +759 -0
  27. data/docs/modules/form.md +465 -0
  28. data/docs/modules/generator.md +288 -0
  29. data/docs/modules/index.md +167 -0
  30. data/docs/modules/interaction.md +470 -0
  31. data/docs/modules/package.md +151 -0
  32. data/docs/modules/policy.md +176 -0
  33. data/docs/modules/portal.md +710 -0
  34. data/docs/modules/query.md +287 -0
  35. data/docs/modules/resource_record.md +618 -0
  36. data/docs/modules/routing.md +641 -0
  37. data/docs/modules/table.md +293 -0
  38. data/docs/modules/ui.md +631 -0
  39. data/docs/public/plutonium.mdc +667 -0
  40. data/lib/generators/pu/core/assets/assets_generator.rb +0 -5
  41. data/lib/plutonium/definition/nested_inputs.rb +0 -8
  42. data/lib/plutonium/resource/controller.rb +1 -1
  43. data/lib/plutonium/ui/display/resource.rb +7 -2
  44. data/lib/plutonium/ui/table/resource.rb +8 -3
  45. data/lib/plutonium/version.rb +1 -1
  46. metadata +36 -9
  47. data/docs/guide/getting-started/authorization.md +0 -296
  48. data/docs/guide/getting-started/core-concepts.md +0 -432
  49. data/docs/guide/getting-started/index.md +0 -21
  50. data/docs/guide/tutorial.md +0 -401
  51. /data/docs/guide/{what-is-plutonium.md → introduction/01-what-is-plutonium.md} +0 -0
@@ -0,0 +1,465 @@
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
+ ### Markdown Editor (Easymde)
128
+
129
+ 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.
130
+
131
+ ::: code-group
132
+ ```ruby [Automatic Usage]
133
+ # Automatically used for ActionText rich_text fields
134
+ render field(:content).easymde_tag
135
+
136
+ # Or explicitly with an alias
137
+ render field(:description).markdown_tag
138
+ ```
139
+ :::
140
+
141
+ ::: details Component Internals
142
+ 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.
143
+
144
+ ```ruby
145
+ class Plutonium::UI::Form::Components::Easymde < Phlexi::Form::Components::Base
146
+ def view_template
147
+ textarea(**attributes, data_controller: "easymde") do
148
+ normalize_value(field.value)
149
+ end
150
+ end
151
+ # ...
152
+ end
153
+ ```
154
+ :::
155
+
156
+ ### Date/Time Picker (Flatpickr)
157
+
158
+ A beautiful and lightweight date/time picker from [Flatpickr](https://flatpickr.js.org/). It's automatically enabled for `date`, `time`, and `datetime` fields.
159
+
160
+ ::: code-group
161
+ ```ruby [Automatic Usage]
162
+ # Automatically used based on field type
163
+ render field(:published_at).flatpickr_tag # datetime field
164
+ render field(:event_date).flatpickr_tag # date field
165
+ render field(:meeting_time).flatpickr_tag # time field
166
+ ```
167
+ :::
168
+
169
+ ::: details Component Internals
170
+ 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).
171
+
172
+ ```ruby
173
+ class Plutonium::UI::Form::Components::Flatpickr < Phlexi::Form::Components::Input
174
+ private
175
+
176
+ def build_input_attributes
177
+ super
178
+ attributes[:data_controller] = tokens(attributes[:data_controller], :flatpickr)
179
+ end
180
+ end
181
+ ```
182
+ :::
183
+
184
+ ### International Phone Input
185
+
186
+ 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).
187
+
188
+ ::: code-group
189
+ ```ruby [Usage]
190
+ # Automatically used for fields of type :tel
191
+ render field(:phone).int_tel_input_tag
192
+
193
+ # Or using its alias
194
+ render field(:mobile).phone_tag
195
+ ```
196
+ :::
197
+
198
+ ::: details Component Internals
199
+ 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.
200
+
201
+ ```ruby
202
+ class Plutonium::UI::Form::Components::IntlTelInput < Phlexi::Form::Components::Input
203
+ def view_template
204
+ div(data_controller: "intl-tel-input") do
205
+ super # Renders the input with proper data targets
206
+ end
207
+ end
208
+
209
+ private
210
+
211
+ def build_input_attributes
212
+ super
213
+ attributes[:data_intl_tel_input_target] = tokens(attributes[:data_intl_tel_input_target], :input)
214
+ end
215
+ end
216
+ ```
217
+ :::
218
+
219
+ ### File Upload (Uppy)
220
+
221
+ 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.
222
+
223
+ ::: code-group
224
+ ```ruby [Basic Usage]
225
+ # Automatically used for file and Active Storage attachments
226
+ render field(:avatar).uppy_tag
227
+ render field(:documents).file_tag # alias
228
+ render field(:gallery).attachment_tag # alias
229
+ ```
230
+
231
+ ```ruby [With Options]
232
+ render field(:documents).uppy_tag(
233
+ multiple: true,
234
+ direct_upload: true, # For S3, etc.
235
+ max_file_size: 10.megabytes,
236
+ allowed_file_types: ['.pdf', '.doc']
237
+ )
238
+ ```
239
+ :::
240
+
241
+ ::: details Component Internals
242
+ 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.
243
+
244
+ ```ruby
245
+ class Plutonium::UI::Form::Components::Uppy < Phlexi::Form::Components::Input
246
+ # Automatic features:
247
+ # - Interactive preview of existing attachments
248
+ # - Delete buttons for removing attachments
249
+ # - Support for direct cloud uploads
250
+ # - File type and size validation via Uppy options
251
+ # ...
252
+
253
+ def view_template
254
+ div(class: "flex flex-col-reverse gap-2") do
255
+ render_existing_attachments
256
+ render_upload_interface
257
+ end
258
+ end
259
+ end
260
+ ```
261
+ :::
262
+
263
+ ### Secure Association Inputs
264
+
265
+ 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.
266
+
267
+ ::: code-group
268
+ ```ruby [Association Types]
269
+ # Automatically used for all standard association types
270
+ render field(:author).belongs_to_tag
271
+ render field(:tags).has_many_tag
272
+ render field(:profile).has_one_tag
273
+ render field(:commentable).polymorphic_belongs_to_tag
274
+ ```
275
+
276
+ ```ruby [With Options]
277
+ render field(:category).belongs_to_tag(
278
+ choices: Category.published.pluck(:name, :id),
279
+ add_action: new_category_path, # Adds a "+" button to add new records
280
+ skip_authorization: false # Enforces authorization policies
281
+ )
282
+ ```
283
+ :::
284
+
285
+ ::: details Security & Implementation
286
+ The `SecureAssociation` component is the cornerstone of Plutonium's form security.
287
+ - **SGID Encoding**: It uses `to_signed_global_id` as the value method, so raw database IDs are never exposed to the client.
288
+ - **Authorization**: It uses `authorized_resource_scope` to ensure that the choices presented to the user are only the ones they are permitted to see.
289
+ - **Add Action**: It can render an "add new" button that automatically includes a `return_to` parameter for a smooth UX.
290
+
291
+ ```ruby
292
+ class Plutonium::UI::Form::Components::SecureAssociation
293
+ def choices
294
+ collection = if @skip_authorization
295
+ # ...
296
+ else
297
+ # Only show records user is authorized to see
298
+ authorized_resource_scope(association_reflection.klass,
299
+ relation: choices_from_association(association_reflection.klass))
300
+ end
301
+ # ...
302
+ end
303
+ end
304
+ ```
305
+ :::
306
+
307
+ ## Type Inference System
308
+
309
+ Plutonium is smart about choosing the right input for a given field, minimizing boilerplate in your forms.
310
+
311
+ ### Automatic Component Selection
312
+
313
+ The `InferredTypes` module overrides the default type inference to map common types to Plutonium's enhanced components.
314
+
315
+ ```ruby
316
+ module Plutonium::UI::Form::Options::InferredTypes
317
+ private
318
+
319
+ def infer_field_component
320
+ case inferred_field_type
321
+ when :rich_text
322
+ return :markdown # Use Easymde for ActionText fields
323
+ end
324
+
325
+ inferred = super
326
+ case inferred
327
+ when :select
328
+ :slim_select # Enhance selects with SlimSelect
329
+ when :date, :time, :datetime
330
+ :flatpickr # Use Flatpickr for date/time fields
331
+ else
332
+ inferred
333
+ end
334
+ end
335
+ end
336
+ ```
337
+
338
+ This means you often don't need to specify the input type at all.
339
+
340
+ ```ruby
341
+ # These are automatically inferred:
342
+ render field(:title) # -> input (string)
343
+ render field(:content) # -> easymde (rich_text)
344
+ render field(:published_at) # -> flatpickr (datetime)
345
+ render field(:phone) # -> int_tel_input (tel)
346
+ render field(:author) # -> secure_association (belongs_to)
347
+ render field(:avatar) # -> uppy (Active Storage attachment)
348
+ render field(:category) # -> slim_select (enum/select)
349
+ ```
350
+
351
+ ## Nested Resources
352
+
353
+ 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`.
354
+
355
+ ### Defining Nested Inputs
356
+
357
+ 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.
358
+
359
+ You can declare a nested input with a simple block or by referencing another definition class.
360
+
361
+ ::: code-group
362
+ ```ruby [Block Definition]
363
+ # app/models/post.rb
364
+ class Post < ApplicationRecord
365
+ has_many :comments
366
+ accepts_nested_attributes_for :comments, allow_destroy: true, limit: 5
367
+ end
368
+
369
+ # app/definitions/post_definition.rb
370
+ class PostDefinition < Plutonium::Resource::Definition
371
+ # This automatically inherits allow_destroy: true and limit: 5 from the model
372
+ nested_input :comments do |n|
373
+ n.input :content, as: :textarea
374
+ n.input :author_name, as: :string
375
+ end
376
+ end
377
+ ```
378
+
379
+ ```ruby [Reference Definition]
380
+ # app/models/post.rb
381
+ class Post < ApplicationRecord
382
+ has_many :tags
383
+ accepts_nested_attributes_for :tags, update_only: true
384
+ end
385
+
386
+ # app/definitions/post_definition.rb
387
+ class PostDefinition < Plutonium::Resource::Definition
388
+ # This inherits update_only: true from the model
389
+ nested_input :tags,
390
+ using: TagDefinition,
391
+ fields: %i[name color]
392
+ end
393
+ ```
394
+ :::
395
+
396
+ ### Overriding Configuration
397
+
398
+ 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.
399
+
400
+ ```ruby
401
+ class PostDefinition < Plutonium::Resource::Definition
402
+ # Explicit options override the model's configuration
403
+ nested_input :comments,
404
+ allow_destroy: false, # Overrides model's allow_destroy: true
405
+ limit: 10, # Overrides model's limit: 5
406
+ description: "Add up to 10 comments for this post." do |n|
407
+ n.input :content
408
+ end
409
+ end
410
+ ```
411
+
412
+ ### Automatic Rendering
413
+
414
+ The `Form::Resource` class automatically renders the nested form based on your definition:
415
+ - For `has_many` associations, it provides "Add" and "Remove" buttons, respecting the `limit`.
416
+ - For `has_one` and `belongs_to` associations, it renders inline fields for a single record.
417
+ - If `allow_destroy: true`, a "Delete" checkbox is rendered for persisted records.
418
+ - If `update_only: true`, the "Add" button is hidden.
419
+
420
+ ::: details Nested Rendering Internals
421
+ 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.
422
+ :::
423
+
424
+ ## Theming
425
+
426
+ 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`.
427
+
428
+ ::: details Form Theme Configuration
429
+ 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.
430
+
431
+ ```ruby
432
+ class Plutonium::UI::Form::Theme < Phlexi::Form::Theme
433
+ def self.theme
434
+ super.merge({
435
+ # Layout
436
+ base: "relative bg-white dark:bg-gray-800 shadow-md sm:rounded-lg my-3 p-6 space-y-6",
437
+ fields_wrapper: "grid grid-cols-1 md:grid-cols-2 2xl:grid-cols-4 gap-4",
438
+ actions_wrapper: "flex justify-end space-x-2",
439
+
440
+ # Input styling
441
+ input: "w-full p-2 border rounded-md shadow-sm dark:bg-gray-700 focus:ring-2",
442
+ valid_input: "bg-green-50 border-green-500 ...",
443
+ invalid_input: "bg-red-50 border-red-500 ...",
444
+
445
+ # Enhanced component themes (aliases to base styles)
446
+ flatpickr: :input,
447
+ int_tel_input: :input,
448
+ uppy: :file,
449
+ association: :select,
450
+ # ...
451
+ })
452
+ end
453
+ end
454
+ ```
455
+ :::
456
+
457
+ ## JavaScript & Stimulus
458
+
459
+ Interactivity is powered by a set of dedicated Stimulus controllers. Plutonium automatically loads these controllers and the required third-party libraries.
460
+
461
+ - **`form`**: The main controller for handling pre-submit refreshes (for conditional fields).
462
+ - **`nested-resource-form-fields`**: Manages adding and removing nested form fields dynamically.
463
+ - **`slim-select`**: Initializes the SlimSelect library on select fields.
464
+ - **`easymde`**, **`flatpickr`**, **`intl-tel-input`**: Controllers for their respective input components.
465
+ - **`attachment-input`** & **`attachment-preview`**: Work together to manage the Uppy file upload experience.