
plutonium-definition-fields
by radioactive-labs
Build production-ready Rails apps in minutes, not days. Convention-driven, fully customizable, AI-ready.
SKILL.md
name: plutonium-definition-fields description: Configure how resource fields are rendered in forms, show pages, and tables
Definition Fields
Configure how fields are rendered using field, input, display, and column declarations.
Core Methods
| Method | Applies To | Use When |
|---|---|---|
field | Forms + Show + Table | Universal type override |
input | Forms only | Form-specific options |
display | Show page only | Display-specific options |
column | Table only | Table-specific options |
Basic Usage
class PostDefinition < ResourceDefinition
# field - changes type everywhere
field :content, as: :markdown
# input - form-specific
input :title,
label: "Article Title",
hint: "Enter a descriptive title",
placeholder: "e.g. Getting Started"
# display - show page specific
display :content,
as: :markdown,
description: "Published content",
wrapper: {class: "col-span-full"}
# column - table specific
column :title, label: "Article", align: :center
column :view_count, align: :end
end
Available Field Types
Input Types (Forms)
| Category | Types |
|---|---|
| Text | :string, :text, :email, :url, :tel, :password |
| Rich Text | :markdown (EasyMDE editor) |
| Numeric | :number, :integer, :decimal, :range |
| Boolean | :boolean |
| Date/Time | :date, :time, :datetime |
| Selection | :select, :slim_select, :radio_buttons, :check_boxes |
| Files | :file, :uppy, :attachment |
| Associations | :association, :secure_association, :belongs_to, :has_many, :has_one |
| Special | :hidden, :color, :phone |
Display Types (Show/Index)
:string, :text, :email, :url, :phone, :markdown, :number, :integer, :decimal, :boolean, :date, :time, :datetime, :association, :attachment
Field Options
Field-Level Options (wrapper)
input :title,
label: "Custom Label", # Custom label text
hint: "Help text for forms", # Form help text
placeholder: "Enter value", # Input placeholder
description: "For displays" # Display description
Tag-Level Options (HTML element)
input :title,
class: "custom-class", # CSS class
data: {controller: "custom"}, # Data attributes
required: true, # HTML required
readonly: true, # HTML readonly
disabled: true # HTML disabled
Wrapper Options
display :content, wrapper: {class: "col-span-full"}
input :notes, wrapper: {class: "bg-gray-50"}
Select/Choices
Static Choices
input :category, as: :select, choices: %w[Tech Business Lifestyle]
input :status, as: :select, choices: Post.statuses.keys
Dynamic Choices (requires block)
# Basic dynamic
input :author do |f|
choices = User.active.pluck(:name, :id)
f.select_tag choices: choices
end
# With context access
input :team_members do |f|
choices = current_user.organization.users.pluck(:name, :id)
f.select_tag choices: choices
end
# Based on object state
input :related_posts do |f|
choices = if object.persisted?
Post.where.not(id: object.id).published.pluck(:title, :id)
else
[]
end
f.select_tag choices: choices
end
Conditional Rendering
class PostDefinition < ResourceDefinition
# Show based on object state
display :published_at, condition: -> { object.published? }
display :rejection_reason, condition: -> { object.rejected? }
# Show based on environment
field :debug_info, condition: -> { Rails.env.development? }
end
Note: Use condition for UI state logic. Use policies for authorization.
Dynamic Forms (pre_submit)
Use pre_submit: true to create forms that dynamically show/hide fields based on other field values. When a pre_submit field changes, the form re-renders server-side and conditions are re-evaluated.
Basic Pattern
class PostDefinition < ResourceDefinition
# Trigger field - causes form to re-render on change
input :send_notifications, pre_submit: true
# Dependent field - only shown when condition is true
input :notification_channel,
as: :select,
choices: %w[Email SMS],
condition: -> { object.send_notifications? }
end
How It Works
- User changes a
pre_submit: truefield - Form submits via Turbo (no page reload)
- Server re-renders the form with updated
objectstate - Fields with
conditionprocs are re-evaluated - Newly visible fields appear, hidden fields disappear
Multiple Dependent Fields
class QuestionDefinition < ResourceDefinition
# Primary selector
input :question_type, as: :select,
choices: %w[text choice scale date boolean],
pre_submit: true
# Conditional fields based on question_type
input :max_length,
as: :integer,
condition: -> { object.question_type == "text" }
input :choices,
as: :text,
hint: "One choice per line",
condition: -> { object.question_type == "choice" }
input :min_value,
as: :integer,
condition: -> { object.question_type == "scale" }
input :max_value,
as: :integer,
condition: -> { object.question_type == "scale" }
end
Cascading Dependencies
class PropertyDefinition < ResourceDefinition
# First level
input :property_type, as: :select,
choices: %w[residential commercial],
pre_submit: true
# Second level - depends on property_type
input :residential_type, as: :select,
choices: %w[apartment house condo],
condition: -> { object.property_type == "residential" },
pre_submit: true
input :commercial_type, as: :select,
choices: %w[office retail warehouse],
condition: -> { object.property_type == "commercial" },
pre_submit: true
# Third level - depends on residential_type
input :apartment_floor,
as: :integer,
condition: -> { object.residential_type == "apartment" }
end
Dynamic Choices with pre_submit
Combine pre_submit with block-based dynamic choices:
class SurveyResponseDefinition < ResourceDefinition
input :category, as: :select,
choices: Category.pluck(:name, :id),
pre_submit: true
# Choices change based on selected category
input :subcategory do |f|
choices = if object.category.present?
Category.find(object.category).subcategories.pluck(:name, :id)
else
[]
end
f.select_tag choices: choices
end
end
Tips
- Only add
pre_submit: trueto fields that control visibility of other fields - Keep dependencies simple - deeply nested conditions are hard to debug
- The form submits on change, so avoid
pre_submiton frequently-changed fields
Custom Rendering
Block Syntax
For Display (can return any component):
display :status do |field|
StatusBadgeComponent.new(value: field.value, class: field.dom.css_class)
end
display :metrics do |field|
if field.value.present?
MetricsChartComponent.new(data: field.value)
else
EmptyStateComponent.new(message: "No metrics")
end
end
For Input (must use form builder methods):
input :birth_date do |f|
case object.age_category
when 'adult'
f.date_tag(min: 18.years.ago.to_date)
when 'minor'
f.date_tag(max: 18.years.ago.to_date)
else
f.date_tag
end
end
phlexi_tag (Advanced Display)
# With component class
display :status, as: :phlexi_tag, with: StatusBadgeComponent
# With inline proc
display :priority, as: :phlexi_tag, with: ->(value, attrs) {
case value
when 'high'
span(class: "badge badge-danger") { "High" }
when 'medium'
span(class: "badge badge-warning") { "Medium" }
else
span(class: "badge badge-info") { "Low" }
end
}
Custom Component Class
input :color_picker, as: ColorPickerComponent
display :chart, as: ChartComponent
Column Options
Alignment
column :title, align: :start # Left (default)
column :status, align: :center # Center
column :amount, align: :end # Right
Value Formatting
Use formatter for simple value transformations without a full block:
# Truncate long text
column :description, formatter: ->(value) { value&.truncate(30) }
# Format numbers
column :price, formatter: ->(value) { "$%.2f" % value if value }
# Transform values
column :status, formatter: ->(value) { value&.humanize&.upcase }
The formatter option:
- Receives the field value as its argument
- Returns the transformed value for display
- Works with
columnanddisplaydeclarations - Is simpler than block syntax when you only need to transform the value
formatter vs block: Use formatter when you only need the value. Use a block when you need access to the full record:
# formatter - receives just the value
column :name, formatter: ->(value) { value&.titleize }
# block - receives the full record
column :full_name do |record|
"#{record.first_name} #{record.last_name}"
end
Custom Column Rendering
Use a block to customize how a column value is displayed. The block receives the raw record:
column :price do |record|
"$#{"%.2f" % record.price}" if record.price
end
column :status do |record|
case record.status
when 'active' then "✓ Active"
when 'pending' then "⏳ Pending"
else record.status.humanize
end
end
column :description do |record|
record.description&.truncate(50)
end
column :author do |record|
record.author&.name || "Unknown"
end
Nested Inputs
Render inline forms for associated records. Requires accepts_nested_attributes_for on the model.
Model Setup
class Post < ResourceRecord
has_many :comments
has_one :metadata
accepts_nested_attributes_for :comments, allow_destroy: true, limit: 10
accepts_nested_attributes_for :metadata, update_only: true
end
Basic Declaration
class PostDefinition < ResourceDefinition
# Block syntax
nested_input :comments do |n|
n.input :body, as: :text
n.input :author_name
end
# Using another definition
nested_input :metadata, using: PostMetadataDefinition, fields: %i[seo_title seo_description]
end
Options
| Option | Description |
|---|---|
limit | Max records (auto-detected from model, default: 10) |
allow_destroy | Show delete checkbox (auto-detected from model) |
update_only | Hide "Add" button, only edit existing |
description | Help text above the section |
condition | Proc to show/hide section |
using | Reference another Definition class |
fields | Which fields to render from the definition |
nested_input :amenities,
allow_destroy: true,
limit: 20,
description: "Add property amenities" do |n|
n.input :name
n.input :icon, as: :select, choices: ICONS
end
Singular Associations
For has_one and belongs_to, limit is automatically 1:
nested_input :profile do |n| # has_one
n.input :bio
n.input :website
end
Conditional Nested Inputs
nested_input :shipping_address,
condition: -> { object.requires_shipping? } do |n|
n.input :street
n.input :city
end
How It Works
- Renders a template (hidden) for new records
- Renders fieldsets for existing records
- Stimulus controller handles Add/Remove
_destroycheckbox marks records for deletion- Parameters submitted as
model[association_attributes][id][field]
Gotchas
- Model must have
accepts_nested_attributes_for - For custom class names, use
class_name:in both model andusing:in definition update_only: truehides the Add button- Limit is enforced in UI (Add button hidden when reached)
File Uploads
input :avatar, as: :file
input :avatar, as: :uppy
input :documents, as: :file, multiple: true
input :documents, as: :uppy,
allowed_file_types: ['.pdf', '.doc'],
max_file_size: 5.megabytes
Common Patterns
Rich Text Content
field :content, as: :markdown # Form: rich editor
display :content, as: :markdown # Show: rendered markdown
Money Fields
input :price, as: :decimal, class: "font-mono"
display :price, class: "font-bold text-green-600"
Status Badges
display :status do |field|
color = case field.value
when 'active' then 'green'
when 'pending' then 'yellow'
else 'gray'
end
span(class: "badge badge-#{color}") { field.value.humanize }
end
Hidden Fields
field :author_id, as: :hidden
input :tenant_id, as: :hidden
Context in Blocks
Inside condition procs and input blocks:
object- The record being edited/displayedcurrent_user- The authenticated usercurrent_parent- Parent record for nested resourcesrequest,params- Request information- All helper methods
Related Skills
plutonium-definition- Overview and structureplutonium-definition-actions- Actions and interactionsplutonium-definition-query- Search, filters, scopesplutonium-forms- Custom form templates and field buildersplutonium-views- Custom page and display templates
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon
