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
data/docs/modules/definition.md
DELETED
|
@@ -1,1308 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: Definition Module
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Definition Module
|
|
6
|
-
|
|
7
|
-
The Definition module provides a powerful DSL for declaratively configuring how resources are displayed, edited, filtered, and interacted with. It serves as the central configuration point for resource behavior in Plutonium applications.
|
|
8
|
-
|
|
9
|
-
::: tip
|
|
10
|
-
The Definition module is located in `lib/plutonium/definition/`. Resource definitions are typically placed in `app/definitions/`.
|
|
11
|
-
:::
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
- **Field Configuration**: Define how fields are displayed and edited.
|
|
16
|
-
- **Display Customization**: Configure field presentation and rendering.
|
|
17
|
-
- **Input Management**: Control form inputs and validation.
|
|
18
|
-
- **Filter & Search**: Set up filtering and search capabilities.
|
|
19
|
-
- **Action Definitions**: Define custom actions and operations.
|
|
20
|
-
- **Conditional Logic**: Dynamic configuration based on context.
|
|
21
|
-
|
|
22
|
-
## Core DSL Methods
|
|
23
|
-
|
|
24
|
-
### Field, Display, and Input
|
|
25
|
-
|
|
26
|
-
The three core methods for defining a resource's attributes are `field`, `display`, and `input`. **All model attributes are automatically detected** - you only need to declare them when you want to override defaults or add custom options.
|
|
27
|
-
|
|
28
|
-
::: code-group
|
|
29
|
-
```ruby [field]
|
|
30
|
-
# Field declarations are OPTIONAL - all attributes are auto-detected
|
|
31
|
-
# You only need to declare fields when overriding auto-detected behavior
|
|
32
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
33
|
-
# These are all auto-detected from your Post model:
|
|
34
|
-
# - :title (string column)
|
|
35
|
-
# - :content (text column)
|
|
36
|
-
# - :published_at (datetime column)
|
|
37
|
-
# - :published (boolean column)
|
|
38
|
-
# - :author (belongs_to association)
|
|
39
|
-
# - :tags (has_many association)
|
|
40
|
-
# - :featured_image (has_one_attached)
|
|
41
|
-
|
|
42
|
-
# Only declare fields when you want to override:
|
|
43
|
-
field :content, as: :rich_text # Override text -> rich_text
|
|
44
|
-
field :author_id, as: :hidden # Override integer -> hidden
|
|
45
|
-
field :internal_notes, as: :text # Add custom field options
|
|
46
|
-
end
|
|
47
|
-
```
|
|
48
|
-
```ruby [display]
|
|
49
|
-
# Display declarations are also OPTIONAL for auto-detected fields
|
|
50
|
-
# Only declare when you want custom display behavior
|
|
51
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
52
|
-
# All model attributes auto-detected and displayed appropriately
|
|
53
|
-
|
|
54
|
-
# Only override when you need custom display:
|
|
55
|
-
display :content, as: :markdown # Override text -> markdown
|
|
56
|
-
display :published_at, as: :date # Override datetime -> date only
|
|
57
|
-
display :view_count, class: "font-bold" # Add custom styling
|
|
58
|
-
|
|
59
|
-
# Custom display with block for complex rendering
|
|
60
|
-
display :status do |field|
|
|
61
|
-
StatusBadgeComponent.new(value: field.value, class: field.dom.css_class)
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
```
|
|
65
|
-
```ruby [input]
|
|
66
|
-
# Input declarations are also OPTIONAL for auto-detected fields
|
|
67
|
-
# Only declare when you need custom input behavior
|
|
68
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
69
|
-
# All editable attributes auto-detected with appropriate inputs
|
|
70
|
-
|
|
71
|
-
# Only override when you need custom input behavior:
|
|
72
|
-
input :content, as: :rich_text # Override text -> rich_text
|
|
73
|
-
input :title, placeholder: "Enter title" # Add placeholder
|
|
74
|
-
input :category, as: :select, choices: %w[Tech Business] # Add options
|
|
75
|
-
input :published_at, as: :date # Override datetime -> date only
|
|
76
|
-
end
|
|
77
|
-
```
|
|
78
|
-
:::
|
|
79
|
-
|
|
80
|
-
## Field Type Auto-Detection
|
|
81
|
-
|
|
82
|
-
**Plutonium automatically detects ALL model attributes** and creates appropriate field, display, and input configurations. The system inspects your ActiveRecord model to discover:
|
|
83
|
-
|
|
84
|
-
- **Database columns** (string, text, integer, boolean, datetime, etc.)
|
|
85
|
-
- **Associations** (belongs_to, has_many, has_one, etc.)
|
|
86
|
-
- **Active Storage attachments** (has_one_attached, has_many_attached)
|
|
87
|
-
- **Enum attributes**
|
|
88
|
-
- **Virtual attributes** (with proper accessor methods)
|
|
89
|
-
|
|
90
|
-
::: details Complete Auto-Detection Logic
|
|
91
|
-
```ruby
|
|
92
|
-
# Database columns are automatically detected:
|
|
93
|
-
# CREATE TABLE posts (
|
|
94
|
-
# id bigint PRIMARY KEY,
|
|
95
|
-
# title varchar(255), # → field :title, as: :string
|
|
96
|
-
# content text, # → field :content, as: :text
|
|
97
|
-
# published_at timestamp, # → field :published_at, as: :datetime
|
|
98
|
-
# published boolean, # → field :published, as: :boolean
|
|
99
|
-
# view_count integer, # → field :view_count, as: :number
|
|
100
|
-
# rating decimal(3,2), # → field :rating, as: :decimal
|
|
101
|
-
# created_at timestamp, # → field :created_at, as: :datetime
|
|
102
|
-
# updated_at timestamp # → field :updated_at, as: :datetime
|
|
103
|
-
# );
|
|
104
|
-
|
|
105
|
-
# Associations are automatically detected:
|
|
106
|
-
class Post < ApplicationRecord
|
|
107
|
-
belongs_to :author, class_name: 'User' # → field :author, as: :association
|
|
108
|
-
has_many :comments # → field :comments, as: :association
|
|
109
|
-
has_many :tags, through: :post_tags # → field :tags, as: :association
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
# Active Storage attachments are automatically detected:
|
|
113
|
-
class Post < ApplicationRecord
|
|
114
|
-
has_one_attached :featured_image # → field :featured_image, as: :attachment
|
|
115
|
-
has_many_attached :documents # → field :documents, as: :attachment
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Enums are automatically detected:
|
|
119
|
-
class Post < ApplicationRecord
|
|
120
|
-
enum status: { draft: 0, published: 1, archived: 2 } # → field :status, as: :select
|
|
121
|
-
end
|
|
122
|
-
```
|
|
123
|
-
:::
|
|
124
|
-
|
|
125
|
-
## When to Declare Fields
|
|
126
|
-
|
|
127
|
-
You only need to explicitly declare fields, displays, or inputs in these scenarios:
|
|
128
|
-
|
|
129
|
-
### 1. **Override Auto-Detected Type**
|
|
130
|
-
```ruby
|
|
131
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
132
|
-
# Change text column to rich text editor
|
|
133
|
-
input :content, as: :rich_text
|
|
134
|
-
|
|
135
|
-
# Change datetime to date-only picker
|
|
136
|
-
input :published_at, as: :date
|
|
137
|
-
|
|
138
|
-
# Change text display to markdown rendering
|
|
139
|
-
display :content, as: :markdown
|
|
140
|
-
end
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### 2. **Add Custom Options**
|
|
144
|
-
```ruby
|
|
145
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
146
|
-
# Field-level options for inputs (affect the field wrapper)
|
|
147
|
-
input :title,
|
|
148
|
-
label: "Article Title",
|
|
149
|
-
hint: "Enter a descriptive title",
|
|
150
|
-
placeholder: "e.g. My First Post"
|
|
151
|
-
|
|
152
|
-
# Field-level options for displays (affect the field wrapper)
|
|
153
|
-
display :content,
|
|
154
|
-
label: "Article Content",
|
|
155
|
-
description: "Published article content",
|
|
156
|
-
placeholder: "No content available"
|
|
157
|
-
|
|
158
|
-
# Tag-level options (passed to the HTML element)
|
|
159
|
-
input :slug, class: "font-mono"
|
|
160
|
-
display :title, class: "text-2xl font-bold"
|
|
161
|
-
|
|
162
|
-
# Wrapper styling
|
|
163
|
-
display :content, wrapper: {class: "prose max-w-none"}
|
|
164
|
-
end
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
::: tip Field-level vs Tag-level Options
|
|
168
|
-
Plutonium distinguishes between two types of options:
|
|
169
|
-
- **Field-level options** (`label`, `hint`, `description`, `placeholder`) - Processed by the field wrapper, affect labels and help text
|
|
170
|
-
- **Tag-level options** (`class`, `data`, `required`, etc.) - Passed directly to the HTML element
|
|
171
|
-
|
|
172
|
-
The framework automatically routes each option to the correct destination.
|
|
173
|
-
:::
|
|
174
|
-
|
|
175
|
-
### 3. **Configure Select Options**
|
|
176
|
-
```ruby
|
|
177
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
178
|
-
# Provide options for select inputs
|
|
179
|
-
input :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
180
|
-
input :author, as: :select, choices: -> { User.active.pluck(:name, :id) }
|
|
181
|
-
end
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### 4. **Add Conditional Logic**
|
|
185
|
-
```ruby
|
|
186
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
187
|
-
# Conditional logic is for showing/hiding fields based on the application's
|
|
188
|
-
# state or other field values. It is not for authorization. Use policies
|
|
189
|
-
# to control access to data.
|
|
190
|
-
|
|
191
|
-
# Conditional fields based on the object's state
|
|
192
|
-
display :published_at, condition: -> { object.published? }
|
|
193
|
-
display :reason_for_rejection, condition: -> { object.rejected? }
|
|
194
|
-
column :published_at, condition: -> { object.published? }
|
|
195
|
-
|
|
196
|
-
# Use `pre_submit` to create dynamic forms where inputs appear based on other inputs.
|
|
197
|
-
input :send_notifications, as: :boolean, pre_submit: true
|
|
198
|
-
input :notification_channel, as: :select, choices: %w[Email SMS],
|
|
199
|
-
condition: -> { object.send_notifications? }
|
|
200
|
-
|
|
201
|
-
# Show debug fields only in development
|
|
202
|
-
field :debug_info, as: :string, condition: -> { Rails.env.development? }
|
|
203
|
-
end
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
::: danger Authorization with Policies
|
|
207
|
-
While the rendering context may provide access to `current_user`, it is strongly recommended to use **policies** for authorization logic (i.e., controlling who can see what data). The `condition` option is intended for cosmetic or state-based logic, such as hiding a field based on another field's value or the record's status. JSON requests for example are not affected by this.
|
|
208
|
-
:::
|
|
209
|
-
|
|
210
|
-
::: tip Condition Context & Dynamic Forms
|
|
211
|
-
`condition` procs are evaluated in their respective rendering contexts and have access to contextual data.
|
|
212
|
-
|
|
213
|
-
**For `input` fields** (form rendering context):
|
|
214
|
-
- `object` - The record being edited
|
|
215
|
-
- `current_parent` - Parent record for nested resources
|
|
216
|
-
- `request` and `params` - Request information
|
|
217
|
-
- All helper methods available in the form context
|
|
218
|
-
|
|
219
|
-
**For `display` fields** (display rendering context):
|
|
220
|
-
- `object` - The record being displayed
|
|
221
|
-
- `current_parent` - Parent record for nested resources
|
|
222
|
-
- All helper methods available in the display context
|
|
223
|
-
|
|
224
|
-
**For `column` fields** (table rendering context):
|
|
225
|
-
- `current_parent` - Parent record for nested resources
|
|
226
|
-
- All helper methods available in the table context
|
|
227
|
-
|
|
228
|
-
To create forms that dynamically show/hide inputs based on other form values, pair a `condition` option with `pre_submit: true` on the "trigger" input. This will cause the form to re-render whenever that input's value changes, re-evaluating any conditions that depend on it.
|
|
229
|
-
:::
|
|
230
|
-
|
|
231
|
-
### 5. Custom Field Rendering
|
|
232
|
-
|
|
233
|
-
Plutonium offers three main approaches for rendering fields in a definition. Choose the one that best fits your needs for clarity, flexibility, and control.
|
|
234
|
-
|
|
235
|
-
**Quick Reference:**
|
|
236
|
-
- **`as: :symbol`** - Use built-in components for input and display (e.g., `:rich_text`, `:date`, `:markdown`)
|
|
237
|
-
- **`as: ComponentClass`** - Use custom component classes for input and display (new feature)
|
|
238
|
-
- **Block syntax** - Use for conditional logic and custom builder method calls
|
|
239
|
-
- **`as: :phlexi_tag`** - Advanced inline rendering with maximum flexibility (display only)
|
|
240
|
-
|
|
241
|
-
#### 1. The `as:` Option (Recommended)
|
|
242
|
-
|
|
243
|
-
The `as:` option is the simplest and most common way to specify a rendering component for both `input` and `display` declarations. It's ideal for using standard built-in components or overriding auto-detected types.
|
|
244
|
-
|
|
245
|
-
**New Feature**: You can now pass a component class directly to the `as:` option for custom rendering in both input and display contexts.
|
|
246
|
-
|
|
247
|
-
**Use When:**
|
|
248
|
-
- Using standard or enhanced built-in components for input or display.
|
|
249
|
-
- You want clean, readable code with minimal boilerplate.
|
|
250
|
-
- Overriding an auto-detected type (e.g., `text` to `rich_text` for input, or `text` to `markdown` for display).
|
|
251
|
-
- Using custom component classes for rendering.
|
|
252
|
-
|
|
253
|
-
::: code-group
|
|
254
|
-
```ruby [Standard Input Fields]
|
|
255
|
-
# Simple and concise overrides
|
|
256
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
257
|
-
input :content, as: :rich_text
|
|
258
|
-
input :published_at, as: :date
|
|
259
|
-
input :avatar, as: :uppy
|
|
260
|
-
|
|
261
|
-
# With options
|
|
262
|
-
input :email, as: :email, placeholder: "Enter email"
|
|
263
|
-
end
|
|
264
|
-
```
|
|
265
|
-
```ruby [Custom Component Classes]
|
|
266
|
-
# Using custom component classes
|
|
267
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
268
|
-
# Pass component class to input
|
|
269
|
-
input :color_picker, as: ColorPickerComponent
|
|
270
|
-
input :custom_widget, as: MyCustomInputComponent
|
|
271
|
-
|
|
272
|
-
# Pass component class to display
|
|
273
|
-
display :status_badge, as: StatusBadgeComponent
|
|
274
|
-
display :chart, as: ChartComponent
|
|
275
|
-
end
|
|
276
|
-
```
|
|
277
|
-
```ruby [Standard Display Fields]
|
|
278
|
-
# Simple and concise overrides
|
|
279
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
280
|
-
display :content, as: :markdown
|
|
281
|
-
display :author, as: :association
|
|
282
|
-
display :documents, as: :attachment
|
|
283
|
-
|
|
284
|
-
# With styling options
|
|
285
|
-
display :status, as: :string, class: "badge badge-success"
|
|
286
|
-
end
|
|
287
|
-
```
|
|
288
|
-
:::
|
|
289
|
-
|
|
290
|
-
#### 2. The Block Syntax
|
|
291
|
-
|
|
292
|
-
The block syntax offers more control over rendering, allowing for custom components, complex layouts, and conditional logic. The block receives a `field` object that you can use to render custom output.
|
|
293
|
-
|
|
294
|
-
**Important for Input Blocks**: When using blocks with `input` declarations, you can only use existing form builder methods (like `f.date_tag`, `f.text_tag`, etc.). You cannot return arbitrary components because the form system requires inputs to be registered internally.
|
|
295
|
-
|
|
296
|
-
**Use When:**
|
|
297
|
-
- Integrating custom-built Phlex or ViewComponent components (for `display` only).
|
|
298
|
-
- Building complex layouts with multiple components or custom HTML (for `display` only).
|
|
299
|
-
- You need conditional logic to determine which component to render.
|
|
300
|
-
- You need to call specific form builder methods with custom logic (for `input`).
|
|
301
|
-
- **You need dynamic choices for select inputs** (since `choices:` option only accepts static arrays).
|
|
302
|
-
|
|
303
|
-
::: code-group
|
|
304
|
-
```ruby [Custom Display Components]
|
|
305
|
-
# Custom display component - can return any component
|
|
306
|
-
display :chart_data do |field|
|
|
307
|
-
ChartComponent.new(data: field.value, type: :bar)
|
|
308
|
-
end
|
|
309
|
-
```
|
|
310
|
-
```ruby [Custom Input with Builder Methods]
|
|
311
|
-
# Custom input - can only use form builder methods
|
|
312
|
-
input :birth_date do |f|
|
|
313
|
-
# Can use builder methods with custom logic
|
|
314
|
-
case object.age_category
|
|
315
|
-
when 'adult'
|
|
316
|
-
f.date_tag(min: 18.years.ago.to_date)
|
|
317
|
-
when 'minor'
|
|
318
|
-
f.date_tag(max: 18.years.ago.to_date)
|
|
319
|
-
else
|
|
320
|
-
f.date_tag
|
|
321
|
-
end
|
|
322
|
-
end
|
|
323
|
-
```
|
|
324
|
-
```ruby [Dynamic Choices for Select Inputs]
|
|
325
|
-
# Dynamic choices based on object state
|
|
326
|
-
input :widget_type do |f|
|
|
327
|
-
choices = case object.question_type
|
|
328
|
-
when nil, ""
|
|
329
|
-
[]
|
|
330
|
-
when "text"
|
|
331
|
-
["input", "textarea"]
|
|
332
|
-
when "choice"
|
|
333
|
-
["radio", "select", "checkbox"]
|
|
334
|
-
when "scale"
|
|
335
|
-
["slider", "radio_scale", "select_scale"]
|
|
336
|
-
when "date"
|
|
337
|
-
["date_picker", "datetime_picker"]
|
|
338
|
-
when "boolean"
|
|
339
|
-
["checkbox", "toggle", "yes_no"]
|
|
340
|
-
else
|
|
341
|
-
Question::WIDGET_TYPES
|
|
342
|
-
end
|
|
343
|
-
|
|
344
|
-
f.select_tag choices: choices
|
|
345
|
-
end
|
|
346
|
-
```
|
|
347
|
-
```ruby [Conditional Rendering]
|
|
348
|
-
# Conditional display based on value
|
|
349
|
-
display :metrics do |field|
|
|
350
|
-
if field.value.present?
|
|
351
|
-
MetricsChartComponent.new(data: field.value)
|
|
352
|
-
else
|
|
353
|
-
EmptyStateComponent.new(message: "No metrics available")
|
|
354
|
-
end
|
|
355
|
-
end
|
|
356
|
-
```
|
|
357
|
-
:::
|
|
358
|
-
|
|
359
|
-
#### 3. `as: :phlexi_tag` (Advanced)
|
|
360
|
-
|
|
361
|
-
`phlexi_tag` provides maximum rendering flexibility for `display` declarations. It's a powerful tool for building reusable component libraries and handling highly dynamic or polymorphic data.
|
|
362
|
-
|
|
363
|
-
**Use When:**
|
|
364
|
-
- Building reusable component libraries that need to be highly configurable.
|
|
365
|
-
- Working with polymorphic data that requires specialized renderers.
|
|
366
|
-
- You need complex rendering logic but want to keep it inline in the definition.
|
|
367
|
-
|
|
368
|
-
::: code-group
|
|
369
|
-
```ruby [With a Component Class]
|
|
370
|
-
# Pass a component class for rendering.
|
|
371
|
-
# The component's #initialize will receive (value, **attrs).
|
|
372
|
-
display :status, as: :phlexi_tag, with: StatusBadgeComponent
|
|
373
|
-
```
|
|
374
|
-
```ruby [With an Inline Proc]
|
|
375
|
-
# Use a proc for complex inline logic.
|
|
376
|
-
# The proc receives (value, attrs).
|
|
377
|
-
display :priority, as: :phlexi_tag, with: ->(value, attrs) {
|
|
378
|
-
case value
|
|
379
|
-
when 'high'
|
|
380
|
-
span(class: tokens("badge badge-danger", attrs[:class])) { "High" }
|
|
381
|
-
when 'medium'
|
|
382
|
-
span(class: tokens("badge badge-warning", attrs[:class])) { "Medium" }
|
|
383
|
-
else
|
|
384
|
-
span(class: tokens("badge badge-info", attrs[:class])) { "Low" }
|
|
385
|
-
end
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
```ruby [Handling Polymorphic Content]
|
|
389
|
-
# Dynamically render different components based on content type.
|
|
390
|
-
display :rich_content, as: :phlexi_tag, with: ->(value, attrs) {
|
|
391
|
-
# `value` is the rich_content object itself
|
|
392
|
-
case value&.content_type
|
|
393
|
-
when 'markdown'
|
|
394
|
-
MarkdownComponent.new(content: value.body, **attrs)
|
|
395
|
-
when 'image'
|
|
396
|
-
# Must return a proc for inline HTML rendering with Phlex
|
|
397
|
-
proc { img(src: value.url, alt: value.caption, **attrs) }
|
|
398
|
-
else
|
|
399
|
-
nil # Fallback to default rendering: <p>#{value}</p>
|
|
400
|
-
end
|
|
401
|
-
}
|
|
402
|
-
```
|
|
403
|
-
:::
|
|
404
|
-
|
|
405
|
-
## Minimal Definition Example
|
|
406
|
-
|
|
407
|
-
Here's what a typical definition looks like when leveraging auto-detection:
|
|
408
|
-
|
|
409
|
-
```ruby
|
|
410
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
411
|
-
# No field declarations needed! All attributes auto-detected.
|
|
412
|
-
# Post model columns, associations, and attachments are automatically available.
|
|
413
|
-
|
|
414
|
-
# Only customize what you need to override:
|
|
415
|
-
input :content, as: :rich_text
|
|
416
|
-
display :content, as: :markdown
|
|
417
|
-
|
|
418
|
-
# Add search and filtering:
|
|
419
|
-
search do |scope, query|
|
|
420
|
-
scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
424
|
-
|
|
425
|
-
# Add custom actions:
|
|
426
|
-
action :publish, interaction: PublishPostInteraction
|
|
427
|
-
end
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
This approach means you can create a fully functional admin interface with just a few lines of configuration, while still having the flexibility to customize anything you need.
|
|
431
|
-
|
|
432
|
-
## Search, Filters, and Scopes
|
|
433
|
-
|
|
434
|
-
Configure how users can query the resource index.
|
|
435
|
-
|
|
436
|
-
::: code-group
|
|
437
|
-
```ruby [Search]
|
|
438
|
-
# Defines the global search logic for the resource.
|
|
439
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
440
|
-
search do |scope, query|
|
|
441
|
-
scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
|
|
442
|
-
end
|
|
443
|
-
end
|
|
444
|
-
```
|
|
445
|
-
```ruby [Filters]
|
|
446
|
-
# Currently, only Text filter is implemented
|
|
447
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
448
|
-
filter :title, with: Plutonium::Query::Filters::Text, predicate: :contains
|
|
449
|
-
filter :status, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
450
|
-
filter :category, with: Plutonium::Query::Filters::Text, predicate: :eq
|
|
451
|
-
|
|
452
|
-
# Available predicates: :eq, :not_eq, :contains, :not_contains,
|
|
453
|
-
# :starts_with, :ends_with, :matches, :not_matches
|
|
454
|
-
end
|
|
455
|
-
```
|
|
456
|
-
```ruby [Scopes]
|
|
457
|
-
# Defines named scopes that appear as buttons.
|
|
458
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
459
|
-
scope :published
|
|
460
|
-
scope :featured
|
|
461
|
-
scope :recent, -> { where('created_at > ?', 1.week.ago) }
|
|
462
|
-
end
|
|
463
|
-
```
|
|
464
|
-
:::
|
|
465
|
-
|
|
466
|
-
## Actions
|
|
467
|
-
|
|
468
|
-
Define custom operations that can be performed on a resource.
|
|
469
|
-
|
|
470
|
-
```ruby
|
|
471
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
472
|
-
# Each `action` call defines ONE action.
|
|
473
|
-
action :publish, interaction: PublishPostInteraction
|
|
474
|
-
action :archive, interaction: ArchivePostInteraction, color: :warning
|
|
475
|
-
|
|
476
|
-
# Use an icon from Phlex::TablerIcons
|
|
477
|
-
action :feature, interaction: FeaturePostInteraction,
|
|
478
|
-
icon: Phlex::TablerIcons::Star
|
|
479
|
-
|
|
480
|
-
# Add a confirmation dialog
|
|
481
|
-
action :delete_permanently, interaction: DeletePostInteraction,
|
|
482
|
-
color: :danger, confirm: "Are you sure?"
|
|
483
|
-
end
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
## UI Customization
|
|
487
|
-
|
|
488
|
-
### Page Titles and Descriptions
|
|
489
|
-
|
|
490
|
-
Page titles and descriptions are rendered using `phlexi_render`, which means they can be **strings**, **procs**, or **component instances**:
|
|
491
|
-
|
|
492
|
-
```ruby
|
|
493
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
494
|
-
# Static strings
|
|
495
|
-
index_page_title "All Posts"
|
|
496
|
-
index_page_description "Manage your blog posts"
|
|
497
|
-
|
|
498
|
-
# Dynamic procs (have access to context)
|
|
499
|
-
show_page_title -> { h1 { "#{current_record!.title} - Post Details" } }
|
|
500
|
-
show_page_description -> { h2 { "Created by #{current_record!.author.name} on #{current_record!.created_at.strftime('%B %d, %Y')}" } }
|
|
501
|
-
|
|
502
|
-
# Component instances for complex rendering
|
|
503
|
-
new_page_title -> { PageTitleComponent.new(text: "Create New Post", icon: :plus) }
|
|
504
|
-
edit_page_title -> { PageTitleComponent.new(text: "Edit: #{current_record!.title}", icon: :edit) }
|
|
505
|
-
|
|
506
|
-
# Conditional titles based on state
|
|
507
|
-
index_page_title -> {
|
|
508
|
-
case params[:status]
|
|
509
|
-
when 'published' then "Published Posts"
|
|
510
|
-
when 'draft' then "Draft Posts"
|
|
511
|
-
else "All Posts"
|
|
512
|
-
end
|
|
513
|
-
}
|
|
514
|
-
end
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
::: tip phlexi_render Context
|
|
518
|
-
Title and description procs are evaluated in the page rendering context, giving you access to:
|
|
519
|
-
- `current_record!` - The current record (for show/edit pages)
|
|
520
|
-
- `params` - Request parameters
|
|
521
|
-
- `current_user` - The authenticated user
|
|
522
|
-
- All helper methods available in the view context
|
|
523
|
-
:::
|
|
524
|
-
|
|
525
|
-
### Custom Page Classes
|
|
526
|
-
|
|
527
|
-
Override page classes for complete control over page rendering:
|
|
528
|
-
|
|
529
|
-
```ruby
|
|
530
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
531
|
-
class IndexPage < Plutonium::UI::Page::Resource::Index
|
|
532
|
-
def view_template(&block)
|
|
533
|
-
# Custom page header
|
|
534
|
-
div(class: "mb-8") do
|
|
535
|
-
h1(class: "text-3xl font-bold") { "Content Management" }
|
|
536
|
-
p(class: "text-gray-600") { "Manage your blog posts and articles" }
|
|
537
|
-
|
|
538
|
-
# Custom stats dashboard
|
|
539
|
-
div(class: "grid grid-cols-1 md:grid-cols-4 gap-4 mt-6") do
|
|
540
|
-
render_stat_card("Total Posts", Post.count)
|
|
541
|
-
render_stat_card("Published", Post.published.count)
|
|
542
|
-
render_stat_card("Drafts", Post.draft.count)
|
|
543
|
-
render_stat_card("This Month", Post.where(created_at: 1.month.ago..Time.current).count)
|
|
544
|
-
end
|
|
545
|
-
end
|
|
546
|
-
|
|
547
|
-
# Standard table rendering
|
|
548
|
-
super(&block)
|
|
549
|
-
end
|
|
550
|
-
|
|
551
|
-
private
|
|
552
|
-
|
|
553
|
-
def render_stat_card(title, value)
|
|
554
|
-
div(class: "bg-white p-4 rounded-lg shadow") do
|
|
555
|
-
div(class: "text-sm text-gray-500") { title }
|
|
556
|
-
div(class: "text-2xl font-bold") { value }
|
|
557
|
-
end
|
|
558
|
-
end
|
|
559
|
-
end
|
|
560
|
-
|
|
561
|
-
class ShowPage < Plutonium::UI::Page::Resource::Show
|
|
562
|
-
def view_template(&block)
|
|
563
|
-
div(class: "max-w-4xl mx-auto") do
|
|
564
|
-
# Custom breadcrumbs
|
|
565
|
-
nav(class: "mb-6") do
|
|
566
|
-
ol(class: "flex space-x-2 text-sm") do
|
|
567
|
-
li { link_to("Posts", posts_path, class: "text-blue-600") }
|
|
568
|
-
li { span(class: "text-gray-500") { "/" } }
|
|
569
|
-
li { span(class: "text-gray-900") { current_record.title.truncate(50) } }
|
|
570
|
-
end
|
|
571
|
-
end
|
|
572
|
-
|
|
573
|
-
# Two-column layout
|
|
574
|
-
div(class: "grid grid-cols-1 lg:grid-cols-3 gap-8") do
|
|
575
|
-
# Main content
|
|
576
|
-
div(class: "lg:col-span-2") do
|
|
577
|
-
super(&block)
|
|
578
|
-
end
|
|
579
|
-
|
|
580
|
-
# Sidebar with metadata
|
|
581
|
-
div(class: "lg:col-span-1") do
|
|
582
|
-
render_metadata_sidebar
|
|
583
|
-
end
|
|
584
|
-
end
|
|
585
|
-
end
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
private
|
|
589
|
-
|
|
590
|
-
def render_metadata_sidebar
|
|
591
|
-
div(class: "bg-gray-50 p-6 rounded-lg") do
|
|
592
|
-
h3(class: "text-lg font-medium mb-4") { "Post Metadata" }
|
|
593
|
-
|
|
594
|
-
dl(class: "space-y-3") do
|
|
595
|
-
render_metadata_item("Status", current_record.status.humanize)
|
|
596
|
-
render_metadata_item("Created", time_ago_in_words(current_record.created_at))
|
|
597
|
-
render_metadata_item("Updated", time_ago_in_words(current_record.updated_at))
|
|
598
|
-
render_metadata_item("Views", current_record.view_count)
|
|
599
|
-
end
|
|
600
|
-
end
|
|
601
|
-
end
|
|
602
|
-
|
|
603
|
-
def render_metadata_item(label, value)
|
|
604
|
-
div do
|
|
605
|
-
dt(class: "text-sm text-gray-500") { label }
|
|
606
|
-
dd(class: "text-sm font-medium") { value }
|
|
607
|
-
end
|
|
608
|
-
end
|
|
609
|
-
end
|
|
610
|
-
|
|
611
|
-
class NewPage < Plutonium::UI::Page::Resource::New
|
|
612
|
-
def page_title
|
|
613
|
-
"Create New #{current_record.class.model_name.human}"
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
def page_description
|
|
617
|
-
"Fill out the form below to create a new post. All fields marked with * are required."
|
|
618
|
-
end
|
|
619
|
-
end
|
|
620
|
-
|
|
621
|
-
class EditPage < Plutonium::UI::Page::Resource::Edit
|
|
622
|
-
def page_title
|
|
623
|
-
"Edit: #{current_record.title}"
|
|
624
|
-
end
|
|
625
|
-
|
|
626
|
-
def page_description
|
|
627
|
-
"Last updated #{time_ago_in_words(current_record.updated_at)} ago"
|
|
628
|
-
end
|
|
629
|
-
end
|
|
630
|
-
end
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
### Custom Form Classes
|
|
634
|
-
|
|
635
|
-
Override form classes for complete control over form rendering:
|
|
636
|
-
|
|
637
|
-
```ruby
|
|
638
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
639
|
-
class Form < Plutonium::UI::Form::Resource
|
|
640
|
-
def form_template
|
|
641
|
-
# Custom form layout
|
|
642
|
-
div(class: "grid grid-cols-1 lg:grid-cols-3 gap-8") do
|
|
643
|
-
# Main content area
|
|
644
|
-
div(class: "lg:col-span-2") do
|
|
645
|
-
render_main_fields
|
|
646
|
-
end
|
|
647
|
-
|
|
648
|
-
# Sidebar
|
|
649
|
-
div(class: "lg:col-span-1") do
|
|
650
|
-
render_sidebar_fields
|
|
651
|
-
end
|
|
652
|
-
end
|
|
653
|
-
|
|
654
|
-
render_actions
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
private
|
|
658
|
-
|
|
659
|
-
def render_main_fields
|
|
660
|
-
# Group related fields
|
|
661
|
-
fieldset(class: "space-y-6") do
|
|
662
|
-
legend(class: "text-lg font-medium") { "Content" }
|
|
663
|
-
|
|
664
|
-
render field(:title).input_tag(placeholder: "Enter a compelling title")
|
|
665
|
-
render field(:content).easymde_tag
|
|
666
|
-
render field(:excerpt).input_tag(as: :textarea, rows: 3)
|
|
667
|
-
end
|
|
668
|
-
end
|
|
669
|
-
|
|
670
|
-
def render_sidebar_fields
|
|
671
|
-
# Publishing controls
|
|
672
|
-
fieldset(class: "space-y-4") do
|
|
673
|
-
legend(class: "text-lg font-medium") { "Publishing" }
|
|
674
|
-
|
|
675
|
-
render field(:status).input_tag(as: :select)
|
|
676
|
-
render field(:published_at).flatpickr_tag
|
|
677
|
-
render field(:featured).input_tag(as: :boolean)
|
|
678
|
-
end
|
|
679
|
-
|
|
680
|
-
# Categorization
|
|
681
|
-
fieldset(class: "space-y-4 mt-8") do
|
|
682
|
-
legend(class: "text-lg font-medium") { "Categorization" }
|
|
683
|
-
|
|
684
|
-
render field(:category).belongs_to_tag
|
|
685
|
-
render field(:tags).has_many_tag
|
|
686
|
-
end
|
|
687
|
-
end
|
|
688
|
-
end
|
|
689
|
-
end
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
## Policy Integration
|
|
693
|
-
|
|
694
|
-
**Field visibility is controlled by policies, not definitions:**
|
|
695
|
-
|
|
696
|
-
```ruby
|
|
697
|
-
# app/policies/post_policy.rb
|
|
698
|
-
class PostPolicy < Plutonium::Resource::Policy
|
|
699
|
-
def permitted_attributes_for_show
|
|
700
|
-
if user.admin?
|
|
701
|
-
[:title, :content, :admin_notes] # Admin sees admin_notes
|
|
702
|
-
else
|
|
703
|
-
[:title, :content] # Regular users don't
|
|
704
|
-
end
|
|
705
|
-
end
|
|
706
|
-
|
|
707
|
-
def permitted_attributes_for_create
|
|
708
|
-
if user.admin?
|
|
709
|
-
[:title, :content, :published, :featured, :admin_notes]
|
|
710
|
-
else
|
|
711
|
-
[:title, :content]
|
|
712
|
-
end
|
|
713
|
-
end
|
|
714
|
-
|
|
715
|
-
def permitted_attributes_for_update
|
|
716
|
-
attrs = permitted_attributes_for_create
|
|
717
|
-
|
|
718
|
-
# Authors can edit their own posts
|
|
719
|
-
if user == record.author
|
|
720
|
-
attrs + [:draft_notes]
|
|
721
|
-
else
|
|
722
|
-
attrs
|
|
723
|
-
end
|
|
724
|
-
end
|
|
725
|
-
|
|
726
|
-
def permitted_associations
|
|
727
|
-
[:author, :tags, :comments]
|
|
728
|
-
end
|
|
729
|
-
end
|
|
730
|
-
```
|
|
731
|
-
|
|
732
|
-
## Integration Points
|
|
733
|
-
|
|
734
|
-
### Resource Integration
|
|
735
|
-
|
|
736
|
-
Definitions are automatically discovered and used by resource controllers:
|
|
737
|
-
|
|
738
|
-
```ruby
|
|
739
|
-
# app/definitions/post_definition.rb
|
|
740
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
741
|
-
# All model attributes are auto-detected!
|
|
742
|
-
# No field declarations needed unless overriding
|
|
743
|
-
|
|
744
|
-
# Only customize what you need:
|
|
745
|
-
input :content, as: :rich_text # Override text -> rich_text
|
|
746
|
-
display :content, as: :markdown # Override text -> markdown
|
|
747
|
-
|
|
748
|
-
search { |scope, search| scope.where("title ILIKE ?", "%#{search}%") }
|
|
749
|
-
end
|
|
750
|
-
|
|
751
|
-
# app/controllers/posts_controller.rb
|
|
752
|
-
class PostsController < ApplicationController
|
|
753
|
-
include Plutonium::Resource::Controller
|
|
754
|
-
# PostDefinition is automatically used
|
|
755
|
-
end
|
|
756
|
-
```
|
|
757
|
-
|
|
758
|
-
### Interaction Integration
|
|
759
|
-
|
|
760
|
-
Actions integrate with the Interaction system:
|
|
761
|
-
|
|
762
|
-
```ruby
|
|
763
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
764
|
-
action :publish, interaction: PublishPostInteraction
|
|
765
|
-
end
|
|
766
|
-
|
|
767
|
-
class PublishPostInteraction < Plutonium::Interaction::Base
|
|
768
|
-
attribute :resource
|
|
769
|
-
attribute :publish_date, :date
|
|
770
|
-
|
|
771
|
-
def execute
|
|
772
|
-
resource.update!(published: true, published_at: publish_date || Time.current)
|
|
773
|
-
succeed(resource).with_redirect_response(resource_url_for(resource))
|
|
774
|
-
end
|
|
775
|
-
end
|
|
776
|
-
```
|
|
777
|
-
|
|
778
|
-
## Best Practices
|
|
779
|
-
|
|
780
|
-
### Field Type Philosophy
|
|
781
|
-
- **Let auto-detection work**: Don't declare fields unless you need to override
|
|
782
|
-
- **Override when needed**: Use declarations to change text to rich_text, datetime to date, etc.
|
|
783
|
-
- **Use conditions sparingly**: Prefer policy-based visibility over conditional fields
|
|
784
|
-
|
|
785
|
-
### Separation of Concerns
|
|
786
|
-
- **Definitions**: Configure HOW fields are rendered and processed
|
|
787
|
-
- **Policies**: Control WHAT fields are visible and editable
|
|
788
|
-
- **Interactions**: Handle business logic and operations
|
|
789
|
-
|
|
790
|
-
### Minimal Configuration Approach
|
|
791
|
-
```ruby
|
|
792
|
-
# Preferred: Let auto-detection work, only override what you need
|
|
793
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
794
|
-
# All fields auto-detected from Post model
|
|
795
|
-
|
|
796
|
-
# Only declare overrides:
|
|
797
|
-
input :content, as: :rich_text
|
|
798
|
-
display :content, as: :markdown
|
|
799
|
-
|
|
800
|
-
search { |scope, search| scope.where("title ILIKE ?", "%#{search}%") }
|
|
801
|
-
end
|
|
802
|
-
|
|
803
|
-
# Avoid: Over-declaring fields that would be auto-detected anyway
|
|
804
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
805
|
-
field :title, as: :string # Unnecessary - auto-detected
|
|
806
|
-
field :content, as: :text # Unnecessary - auto-detected
|
|
807
|
-
field :author, as: :association # Unnecessary - auto-detected
|
|
808
|
-
|
|
809
|
-
# This creates extra maintenance burden
|
|
810
|
-
end
|
|
811
|
-
```
|
|
812
|
-
|
|
813
|
-
The Definition module provides a clean, declarative way to configure resource behavior while maintaining clear separation between configuration (definitions), authorization (policies), and business logic (interactions).
|
|
814
|
-
|
|
815
|
-
## Structuring Your Definition File
|
|
816
|
-
|
|
817
|
-
A well-structured definition file is easy to read, maintain, and understand. Here's the recommended organization:
|
|
818
|
-
|
|
819
|
-
### 1. Logical Grouping
|
|
820
|
-
|
|
821
|
-
Group related declarations together for better readability:
|
|
822
|
-
|
|
823
|
-
```ruby
|
|
824
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
825
|
-
# === Field Type Overrides ===
|
|
826
|
-
# Group all basic field type changes together
|
|
827
|
-
field :content, as: :rich_text
|
|
828
|
-
field :author_id, as: :hidden
|
|
829
|
-
field :metadata, as: :json
|
|
830
|
-
|
|
831
|
-
# === Input Customizations ===
|
|
832
|
-
# Group form-specific configurations
|
|
833
|
-
input :title,
|
|
834
|
-
label: "Post Title",
|
|
835
|
-
hint: "Enter a descriptive title",
|
|
836
|
-
placeholder: "e.g. Getting Started with Plutonium"
|
|
837
|
-
|
|
838
|
-
input :slug,
|
|
839
|
-
label: "URL Slug",
|
|
840
|
-
hint: "Leave blank to auto-generate from title",
|
|
841
|
-
class: "font-mono"
|
|
842
|
-
|
|
843
|
-
input :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
844
|
-
|
|
845
|
-
# === Display Customizations ===
|
|
846
|
-
# Group show page configurations
|
|
847
|
-
display :content, as: :markdown
|
|
848
|
-
display :view_count, label: "Views", class: "font-bold"
|
|
849
|
-
display :metadata, wrapper: {class: "col-span-full"}
|
|
850
|
-
|
|
851
|
-
# === Column Customizations ===
|
|
852
|
-
# Group table-specific configurations
|
|
853
|
-
column :title, label: "Article Title"
|
|
854
|
-
column :view_count, align: :end, label: "Views"
|
|
855
|
-
column :published_at, align: :end
|
|
856
|
-
|
|
857
|
-
# === Search & Filtering ===
|
|
858
|
-
# Group query-related configurations
|
|
859
|
-
search do |scope, query|
|
|
860
|
-
scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
|
|
861
|
-
end
|
|
862
|
-
|
|
863
|
-
filter :status, as: :select, choices: %w[draft published archived]
|
|
864
|
-
filter :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
865
|
-
|
|
866
|
-
# === Scopes ===
|
|
867
|
-
scope :published
|
|
868
|
-
scope :draft
|
|
869
|
-
scope :archived
|
|
870
|
-
|
|
871
|
-
# === Sorting ===
|
|
872
|
-
sort :title
|
|
873
|
-
sort :created_at
|
|
874
|
-
sort :view_count
|
|
875
|
-
|
|
876
|
-
# === Actions ===
|
|
877
|
-
action :publish, interaction: Posts::Publish
|
|
878
|
-
action :archive, interaction: Posts::Archive
|
|
879
|
-
end
|
|
880
|
-
```
|
|
881
|
-
|
|
882
|
-
### 2. Use Comments to Delineate Sections
|
|
883
|
-
|
|
884
|
-
Add section headers with comments to make the file scannable:
|
|
885
|
-
|
|
886
|
-
```ruby
|
|
887
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
888
|
-
# ========================================
|
|
889
|
-
# Field Configuration
|
|
890
|
-
# ========================================
|
|
891
|
-
|
|
892
|
-
field :content, as: :rich_text
|
|
893
|
-
|
|
894
|
-
# ========================================
|
|
895
|
-
# Form Inputs
|
|
896
|
-
# ========================================
|
|
897
|
-
|
|
898
|
-
input :title, hint: "Enter a descriptive title"
|
|
899
|
-
|
|
900
|
-
# ========================================
|
|
901
|
-
# Display Fields
|
|
902
|
-
# ========================================
|
|
903
|
-
|
|
904
|
-
display :content, as: :markdown
|
|
905
|
-
|
|
906
|
-
# ========================================
|
|
907
|
-
# Table Columns
|
|
908
|
-
# ========================================
|
|
909
|
-
|
|
910
|
-
column :view_count, align: :end
|
|
911
|
-
|
|
912
|
-
# ========================================
|
|
913
|
-
# Search, Filters & Scopes
|
|
914
|
-
# ========================================
|
|
915
|
-
|
|
916
|
-
search { |scope, query| scope.where("title ILIKE ?", "%#{query}%") }
|
|
917
|
-
filter :status, as: :select, choices: %w[draft published]
|
|
918
|
-
|
|
919
|
-
# ========================================
|
|
920
|
-
# Actions
|
|
921
|
-
# ========================================
|
|
922
|
-
|
|
923
|
-
action :publish, interaction: Posts::Publish
|
|
924
|
-
end
|
|
925
|
-
```
|
|
926
|
-
|
|
927
|
-
### 3. Order Within Each Section
|
|
928
|
-
|
|
929
|
-
Within each section, order declarations logically:
|
|
930
|
-
|
|
931
|
-
**For fields/inputs/displays:**
|
|
932
|
-
1. Basic field type overrides first
|
|
933
|
-
2. Fields with simple options next
|
|
934
|
-
3. Fields with complex options or blocks last
|
|
935
|
-
|
|
936
|
-
**For filters and scopes:**
|
|
937
|
-
1. Most commonly used first
|
|
938
|
-
2. Grouped by related functionality
|
|
939
|
-
|
|
940
|
-
**For actions:**
|
|
941
|
-
1. Primary actions first (create, publish)
|
|
942
|
-
2. Secondary actions next (archive, duplicate)
|
|
943
|
-
3. Destructive actions last (delete)
|
|
944
|
-
|
|
945
|
-
### 4. Use `field` vs `input` vs `display` Appropriately
|
|
946
|
-
|
|
947
|
-
Understanding when to use each declaration:
|
|
948
|
-
|
|
949
|
-
```ruby
|
|
950
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
951
|
-
# Use `field` when the configuration applies to ALL contexts
|
|
952
|
-
# (form inputs, show page displays, and table columns)
|
|
953
|
-
field :content, as: :rich_text # Changes type everywhere
|
|
954
|
-
|
|
955
|
-
# Use `input` for form-specific options
|
|
956
|
-
input :title, hint: "Shown only in forms", placeholder: "Form placeholder"
|
|
957
|
-
|
|
958
|
-
# Use `display` for show-page-specific options
|
|
959
|
-
display :content,
|
|
960
|
-
description: "Shown only on show pages",
|
|
961
|
-
wrapper: {class: "col-span-full"}
|
|
962
|
-
|
|
963
|
-
# Use `column` for table-specific options
|
|
964
|
-
column :title, label: "Custom header", align: :center
|
|
965
|
-
end
|
|
966
|
-
```
|
|
967
|
-
|
|
968
|
-
### 5. Avoid Redundant Declarations
|
|
969
|
-
|
|
970
|
-
Remember that **all model attributes are auto-detected**. Only declare overrides:
|
|
971
|
-
|
|
972
|
-
::: code-group
|
|
973
|
-
```ruby [✅ Good - Only Overrides]
|
|
974
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
975
|
-
# Only override what needs customization
|
|
976
|
-
field :content, as: :rich_text
|
|
977
|
-
input :title, hint: "Enter a descriptive title"
|
|
978
|
-
display :metadata, wrapper: {class: "col-span-full"}
|
|
979
|
-
end
|
|
980
|
-
```
|
|
981
|
-
|
|
982
|
-
```ruby [❌ Bad - Redundant]
|
|
983
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
984
|
-
# Unnecessary - these are auto-detected correctly
|
|
985
|
-
field :title, as: :string
|
|
986
|
-
field :content, as: :text
|
|
987
|
-
field :published_at, as: :datetime
|
|
988
|
-
field :author, as: :association
|
|
989
|
-
|
|
990
|
-
# Only these override defaults
|
|
991
|
-
field :content, as: :rich_text # This actually works, overrides the earlier :text
|
|
992
|
-
input :title, hint: "Enter a descriptive title"
|
|
993
|
-
end
|
|
994
|
-
```
|
|
995
|
-
:::
|
|
996
|
-
|
|
997
|
-
### 6. Complex Customizations at the End
|
|
998
|
-
|
|
999
|
-
Place complex blocks and custom components at the end of their sections:
|
|
1000
|
-
|
|
1001
|
-
```ruby
|
|
1002
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
1003
|
-
# Simple overrides first
|
|
1004
|
-
input :title, hint: "Enter title"
|
|
1005
|
-
input :category, as: :select, choices: %w[Tech Business]
|
|
1006
|
-
|
|
1007
|
-
# Complex blocks last
|
|
1008
|
-
input :related_posts do |f|
|
|
1009
|
-
choices = if object.persisted?
|
|
1010
|
-
Post.where.not(id: object.id).published.pluck(:title, :id)
|
|
1011
|
-
else
|
|
1012
|
-
[]
|
|
1013
|
-
end
|
|
1014
|
-
f.select_tag choices: choices
|
|
1015
|
-
end
|
|
1016
|
-
|
|
1017
|
-
display :status do |f|
|
|
1018
|
-
StatusBadgeComponent.new(
|
|
1019
|
-
status: f.value,
|
|
1020
|
-
class: "inline-flex items-center"
|
|
1021
|
-
)
|
|
1022
|
-
end
|
|
1023
|
-
end
|
|
1024
|
-
```
|
|
1025
|
-
|
|
1026
|
-
### 7. Example: Well-Structured Definition
|
|
1027
|
-
|
|
1028
|
-
Here's a complete example following all best practices:
|
|
1029
|
-
|
|
1030
|
-
```ruby
|
|
1031
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
1032
|
-
# ========================================
|
|
1033
|
-
# Field Type Overrides
|
|
1034
|
-
# ========================================
|
|
1035
|
-
|
|
1036
|
-
field :content, as: :rich_text
|
|
1037
|
-
field :author_id, as: :hidden
|
|
1038
|
-
|
|
1039
|
-
# ========================================
|
|
1040
|
-
# Form Inputs
|
|
1041
|
-
# ========================================
|
|
1042
|
-
|
|
1043
|
-
# Basic fields with simple options
|
|
1044
|
-
input :title,
|
|
1045
|
-
label: "Post Title",
|
|
1046
|
-
hint: "Enter a descriptive title",
|
|
1047
|
-
placeholder: "e.g. Getting Started"
|
|
1048
|
-
|
|
1049
|
-
input :slug,
|
|
1050
|
-
hint: "Leave blank to auto-generate",
|
|
1051
|
-
class: "font-mono"
|
|
1052
|
-
|
|
1053
|
-
# Select inputs
|
|
1054
|
-
input :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
1055
|
-
input :author, as: :association
|
|
1056
|
-
|
|
1057
|
-
# Conditional inputs
|
|
1058
|
-
input :published_at,
|
|
1059
|
-
condition: -> { object.published? },
|
|
1060
|
-
hint: "When this post was published"
|
|
1061
|
-
|
|
1062
|
-
# ========================================
|
|
1063
|
-
# Display Fields
|
|
1064
|
-
# ========================================
|
|
1065
|
-
|
|
1066
|
-
display :content, as: :markdown
|
|
1067
|
-
display :view_count, label: "Total Views", class: "font-bold text-lg"
|
|
1068
|
-
display :metadata, wrapper: {class: "col-span-full"}
|
|
1069
|
-
|
|
1070
|
-
# ========================================
|
|
1071
|
-
# Table Columns
|
|
1072
|
-
# ========================================
|
|
1073
|
-
|
|
1074
|
-
column :title, label: "Article Title"
|
|
1075
|
-
column :category, align: :center
|
|
1076
|
-
column :view_count, align: :end, label: "Views"
|
|
1077
|
-
column :published_at, align: :end
|
|
1078
|
-
|
|
1079
|
-
# ========================================
|
|
1080
|
-
# Search & Filtering
|
|
1081
|
-
# ========================================
|
|
1082
|
-
|
|
1083
|
-
search do |scope, query|
|
|
1084
|
-
scope.where("title ILIKE ? OR content ILIKE ?", "%#{query}%", "%#{query}%")
|
|
1085
|
-
end
|
|
1086
|
-
|
|
1087
|
-
filter :status, as: :select, choices: %w[draft published archived]
|
|
1088
|
-
filter :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
1089
|
-
filter :published_after, as: :date
|
|
1090
|
-
|
|
1091
|
-
# ========================================
|
|
1092
|
-
# Scopes
|
|
1093
|
-
# ========================================
|
|
1094
|
-
|
|
1095
|
-
scope :published
|
|
1096
|
-
scope :draft
|
|
1097
|
-
scope :scheduled
|
|
1098
|
-
|
|
1099
|
-
# ========================================
|
|
1100
|
-
# Sorting
|
|
1101
|
-
# ========================================
|
|
1102
|
-
|
|
1103
|
-
sort :title
|
|
1104
|
-
sort :published_at
|
|
1105
|
-
sort :view_count
|
|
1106
|
-
|
|
1107
|
-
# ========================================
|
|
1108
|
-
# Actions
|
|
1109
|
-
# ========================================
|
|
1110
|
-
|
|
1111
|
-
action :publish, interaction: Posts::Publish
|
|
1112
|
-
action :schedule, interaction: Posts::Schedule
|
|
1113
|
-
action :archive, interaction: Posts::Archive
|
|
1114
|
-
end
|
|
1115
|
-
```
|
|
1116
|
-
|
|
1117
|
-
## Related Modules
|
|
1118
|
-
|
|
1119
|
-
- **[Resource Record](./resource_record.md)** - Resource controllers and CRUD operations
|
|
1120
|
-
- **[UI](./ui.md)** - User interface components
|
|
1121
|
-
- **[Query](./query.md)** - Query objects and filtering
|
|
1122
|
-
- **[Action](./action.md)** - Custom actions and operations
|
|
1123
|
-
- **[Interaction](./interaction.md)** - Business logic encapsulation
|
|
1124
|
-
|
|
1125
|
-
## Available Field Types
|
|
1126
|
-
|
|
1127
|
-
### Input Types (Form Components)
|
|
1128
|
-
- **Text**: `:string`, `:text`, `:email`, `:url`, `:tel`, `:password`
|
|
1129
|
-
- **Rich Text**: `:rich_text`, `:markdown` (uses EasyMDE)
|
|
1130
|
-
- **Numeric**: `:number`, `:integer`, `:decimal`, `:range`
|
|
1131
|
-
- **Boolean**: `:boolean`
|
|
1132
|
-
- **Date/Time**: `:date`, `:time`, `:datetime` (uses Flatpickr)
|
|
1133
|
-
- **Selection**: `:select`, `:slim_select`, `:radio_buttons`, `:check_boxes`
|
|
1134
|
-
- **Files**: `:file`, `:uppy`, `:attachment` (uses Uppy)
|
|
1135
|
-
- **Associations**: `:association`, `:secure_association`, `:belongs_to`, `:has_many`, `:has_one`
|
|
1136
|
-
- **Special**: `:hidden`, `:color`, `:phone` (uses IntlTelInput)
|
|
1137
|
-
|
|
1138
|
-
### Display Types (Show/Index Components)
|
|
1139
|
-
- **Text**: `:string`, `:text`, `:email`, `:url`, `:phone`
|
|
1140
|
-
- **Rich Content**: `:markdown` (renders with Redcarpet)
|
|
1141
|
-
- **Numeric**: `:number`, `:integer`, `:decimal`
|
|
1142
|
-
- **Boolean**: `:boolean`
|
|
1143
|
-
- **Date/Time**: `:date`, `:time`, `:datetime`
|
|
1144
|
-
- **Associations**: `:association` (auto-links to show page)
|
|
1145
|
-
- **Files**: `:attachment` (shows previews/downloads)
|
|
1146
|
-
- **Custom**: `:phlexi_render` (for custom components)
|
|
1147
|
-
|
|
1148
|
-
## Available Configuration Options
|
|
1149
|
-
|
|
1150
|
-
### Field Options
|
|
1151
|
-
```ruby
|
|
1152
|
-
field :name, as: :string, class: "custom-class", wrapper: {class: "field-wrapper"}
|
|
1153
|
-
```
|
|
1154
|
-
|
|
1155
|
-
### Input Options
|
|
1156
|
-
```ruby
|
|
1157
|
-
input :title,
|
|
1158
|
-
as: :string,
|
|
1159
|
-
# Field-level options (passed to the field wrapper):
|
|
1160
|
-
label: "Article Title", # Custom field label
|
|
1161
|
-
hint: "Enter a descriptive title", # Help text shown below input
|
|
1162
|
-
placeholder: "e.g. My First Post", # Input placeholder text
|
|
1163
|
-
# Tag-level options (passed to the input tag):
|
|
1164
|
-
required: true,
|
|
1165
|
-
class: "custom-input",
|
|
1166
|
-
data: {controller: "custom"},
|
|
1167
|
-
# Wrapper options:
|
|
1168
|
-
wrapper: {class: "input-wrapper"},
|
|
1169
|
-
# Conditional rendering:
|
|
1170
|
-
condition: -> { current_user.admin? },
|
|
1171
|
-
# Pre-submit for dynamic forms:
|
|
1172
|
-
pre_submit: false
|
|
1173
|
-
```
|
|
1174
|
-
|
|
1175
|
-
::: tip Field-level vs Tag-level Options
|
|
1176
|
-
- **Field-level options** (`label`, `hint`, `placeholder`) are processed by the field wrapper and affect the field's label and help text display
|
|
1177
|
-
- **Tag-level options** (like `class`, `data`, `required`) are passed directly to the HTML input element
|
|
1178
|
-
- The system automatically routes options to the correct destination
|
|
1179
|
-
:::
|
|
1180
|
-
|
|
1181
|
-
### Display Options
|
|
1182
|
-
```ruby
|
|
1183
|
-
display :content,
|
|
1184
|
-
as: :markdown,
|
|
1185
|
-
# Field-level options (passed to the field wrapper):
|
|
1186
|
-
label: "Article Content", # Custom field label
|
|
1187
|
-
description: "Published content", # Description text shown below value
|
|
1188
|
-
placeholder: "No content yet", # Shown when value is empty
|
|
1189
|
-
# Tag-level options (passed to the display tag):
|
|
1190
|
-
class: "prose",
|
|
1191
|
-
# Wrapper options:
|
|
1192
|
-
wrapper: {class: "content-wrapper"},
|
|
1193
|
-
# Conditional rendering:
|
|
1194
|
-
condition: -> { current_user.can_see_content? }
|
|
1195
|
-
```
|
|
1196
|
-
|
|
1197
|
-
::: tip Display Uses Description, Forms Use Hint
|
|
1198
|
-
- **Display fields** use `:description` for explanatory text below the value
|
|
1199
|
-
- **Form fields** use `:hint` for help text below the input
|
|
1200
|
-
- Both support `:label` and `:placeholder` options
|
|
1201
|
-
:::
|
|
1202
|
-
|
|
1203
|
-
### Column Options
|
|
1204
|
-
```ruby
|
|
1205
|
-
column :title,
|
|
1206
|
-
as: :string,
|
|
1207
|
-
# Column-level options:
|
|
1208
|
-
label: "Article Title", # Custom column header
|
|
1209
|
-
align: :start, # Alignment: :start, :center, :end
|
|
1210
|
-
# Tag-level options (passed to the cell renderer):
|
|
1211
|
-
class: "font-bold",
|
|
1212
|
-
# Conditional rendering:
|
|
1213
|
-
condition: -> { current_user.admin? }
|
|
1214
|
-
```
|
|
1215
|
-
|
|
1216
|
-
::: tip Table Columns
|
|
1217
|
-
Table columns support a minimal set of field-level options since they render in a compact table format:
|
|
1218
|
-
- `:label` - Column header text
|
|
1219
|
-
- `:align` - Column alignment
|
|
1220
|
-
- Options like `:description` and `:placeholder` are filtered out as they don't make sense in table cells
|
|
1221
|
-
:::
|
|
1222
|
-
|
|
1223
|
-
### Choices Options (for selects)
|
|
1224
|
-
```ruby
|
|
1225
|
-
# Static choices
|
|
1226
|
-
input :category, as: :select, choices: %w[Tech Business Lifestyle]
|
|
1227
|
-
|
|
1228
|
-
# Dynamic choices require block form
|
|
1229
|
-
input :author do |f|
|
|
1230
|
-
choices = User.active.pluck(:name, :id)
|
|
1231
|
-
f.select_tag choices: choices
|
|
1232
|
-
end
|
|
1233
|
-
|
|
1234
|
-
# Dynamic choices with access to context
|
|
1235
|
-
input :team_members do |f|
|
|
1236
|
-
choices = current_user.organization.users.active.pluck(:name, :id)
|
|
1237
|
-
f.select_tag choices: choices
|
|
1238
|
-
end
|
|
1239
|
-
|
|
1240
|
-
# Dynamic choices based on object state
|
|
1241
|
-
input :related_posts do |f|
|
|
1242
|
-
choices = if object.persisted?
|
|
1243
|
-
Post.where.not(id: object.id).published.pluck(:title, :id)
|
|
1244
|
-
else
|
|
1245
|
-
[]
|
|
1246
|
-
end
|
|
1247
|
-
f.select_tag choices: choices
|
|
1248
|
-
end
|
|
1249
|
-
```
|
|
1250
|
-
|
|
1251
|
-
::: tip Dynamic Choices Context
|
|
1252
|
-
When using block forms for dynamic choices, you have access to:
|
|
1253
|
-
- `current_user` - The authenticated user
|
|
1254
|
-
- `current_parent` - Parent record for nested resources
|
|
1255
|
-
- `object` - The record being edited (in edit forms)
|
|
1256
|
-
- `request` and `params` - Request information
|
|
1257
|
-
- All helper methods available in the form context
|
|
1258
|
-
|
|
1259
|
-
Block forms are required for dynamic choices since the `choices:` option only accepts static arrays.
|
|
1260
|
-
:::
|
|
1261
|
-
|
|
1262
|
-
### File Upload Options
|
|
1263
|
-
```ruby
|
|
1264
|
-
input :avatar, as: :file, multiple: false
|
|
1265
|
-
input :documents, as: :file, multiple: true,
|
|
1266
|
-
allowed_file_types: ['.pdf', '.doc', '.docx'],
|
|
1267
|
-
max_file_size: 5.megabytes
|
|
1268
|
-
```
|
|
1269
|
-
|
|
1270
|
-
## Dynamic Configuration & Policies
|
|
1271
|
-
|
|
1272
|
-
::: danger IMPORTANT
|
|
1273
|
-
Definitions are instantiated outside the controller context, which means **`current_user` and other controller methods are NOT available** within the definition file itself. However, `condition` procs and input blocks ARE evaluated in the rendering context where `current_user` and the record (`object`) are available.
|
|
1274
|
-
:::
|
|
1275
|
-
|
|
1276
|
-
The `condition` option configures **if an input is rendered**. It does not control if a field's *value* is accessible. For that, you must use policies.
|
|
1277
|
-
|
|
1278
|
-
::: code-group
|
|
1279
|
-
```ruby [Static Definition]
|
|
1280
|
-
# app/definitions/post_definition.rb
|
|
1281
|
-
class PostDefinition < Plutonium::Resource::Definition
|
|
1282
|
-
# This configuration is static.
|
|
1283
|
-
# The :admin_notes field is always defined here.
|
|
1284
|
-
def customize_fields
|
|
1285
|
-
field :admin_notes, as: :text
|
|
1286
|
-
end
|
|
1287
|
-
|
|
1288
|
-
def customize_displays
|
|
1289
|
-
display :admin_notes, as: :text
|
|
1290
|
-
end
|
|
1291
|
-
|
|
1292
|
-
def customize_inputs
|
|
1293
|
-
input :admin_notes, as: :text
|
|
1294
|
-
end
|
|
1295
|
-
end
|
|
1296
|
-
```
|
|
1297
|
-
```ruby [Dynamic Policy]
|
|
1298
|
-
# app/policies/post_policy.rb
|
|
1299
|
-
class PostPolicy < Plutonium::Resource::Policy
|
|
1300
|
-
# The policy determines if the user can SEE the field.
|
|
1301
|
-
def permitted_attributes_for_show
|
|
1302
|
-
if user.admin?
|
|
1303
|
-
[:title, :content, :admin_notes] # Admin sees admin_notes
|
|
1304
|
-
else
|
|
1305
|
-
[:title, :content] # Regular users do not
|
|
1306
|
-
end
|
|
1307
|
-
end
|
|
1308
|
-
end
|