Create Resource Skill
Use the pu:res:scaffold generator to create complete resources in Plutonium applications.
Command Syntax
rails g pu:res:scaffold MODEL_NAME \
field1:type \
field2:type \
--dest=DESTINATION
IMPORTANT: Always specify --dest to avoid interactive prompts:
--dest=main_appfor resources in the main application--dest=package_namefor resources in a feature package
IMPORTANT: Quote fields containing ? or {} to prevent shell expansion:
'field:type?' # Nullable - must quote
'field:decimal{10,2}' # Options - must quote
'field:decimal?{10,2}' # Both - must quote
From Existing Models
For existing Rails projects with models you want to convert to Plutonium resources:
Option 1: Model already includes Plutonium::Resource::Record
rails g pu:res:scaffold Post --no-migration --dest=main_app
This generates only the definition, policy, and controller - leaving your model unchanged.
Option 2: Let the generator update the model
rails g pu:res:scaffold Post --dest=main_app
Run without attributes to auto-import fields from model.content_columns. This regenerates the model file, so review changes carefully.
Don't forget to include the module
Your model must include Plutonium::Resource::Record (directly or via inheritance):
class Post < ApplicationRecord
include Plutonium::Resource::Record
end
# Or inherit from a base class
class Post < ResourceRecord
end
Field Type Syntax
Format: name:type:index_type
Basic Types
| Syntax | Result |
|---|---|
name:string |
Required string |
'name:string?' |
Nullable string |
age:integer |
Required integer |
'age:integer?' |
Nullable integer |
active:boolean |
Required boolean |
'active:boolean?' |
Nullable boolean |
content:text |
Required text |
'content:text?' |
Nullable text |
birth_date:date |
Required date |
'anniversary:date?' |
Nullable date |
starts_at:datetime |
Required datetime |
'ends_at:datetime?' |
Nullable datetime |
alarm_time:time |
Required time |
'reminder_time:time?' |
Nullable time |
metadata:json |
JSON field |
settings:jsonb |
JSONB (PostgreSQL) |
external_id:uuid |
UUID field |
PostgreSQL-Specific Types
These types work in both PostgreSQL and SQLite (automatically mapped):
| Type | PostgreSQL | SQLite |
|---|---|---|
jsonb |
jsonb |
json |
hstore |
hstore |
json |
uuid |
uuid |
string |
inet |
inet |
string |
cidr |
cidr |
string |
macaddr |
macaddr |
string |
ltree |
ltree |
string |
Default Values
Use {default:value} syntax for default values:
'status:string{default:draft}' # String default
'active:boolean{default:true}' # Boolean default (true/false/yes/1)
'priority:integer{default:0}' # Integer default
'rating:float{default:4.5}' # Float default
'status:string?{default:pending}' # Nullable with default
JSON/JSONB Default Values
Supports nested braces for structured defaults:
'metadata:jsonb{default:{}}' # Empty hash
'tags:jsonb{default:[]}' # Empty array
'settings:jsonb{default:{"theme":"dark"}}' # Object with values
'config:jsonb?{default:{}}' # Nullable with empty hash default
Default values are parsed as JSON first. If JSON parsing fails, the value is treated as a string (or coerced based on column type for integers, floats, and booleans).
Decimal with Precision and Default
'amount:decimal{10,2}' # precision: 10, scale: 2
'price:decimal{10,2,default:0}' # with default value
'balance:decimal?{15,2,default:0}' # nullable with precision and default
References/Associations
company:belongs_to # Required foreign key
'parent:belongs_to?' # Nullable (null: true + optional: true)
user:references # Same as belongs_to
blogging/post:belongs_to # Cross-package reference
'author:belongs_to{class_name:User}' # Custom class_name (author_id -> User)
'reviewer:belongs_to?{class_name:User}' # Nullable with class_name
Nullable references generate:
- Migration:
null: true - Model:
belongs_to :parent, optional: true
Index Types (third segment)
email:string:index # Regular index
email:string:uniq # Unique index
Special Types
password_digest # has_secure_password
auth_token:token # has_secure_token (auto unique index)
content:rich_text # has_rich_text
avatar:attachment # has_one_attached
photos:attachments # has_many_attached
price_cents:integer # has_cents (money field)
Token fields automatically get a unique index in the migration.
Generator Options
--dest=DESTINATION- Target destination (always required to avoid prompts)main_appfor main application resourcespackage_namefor feature package resources
--no-model- Skip model generation (keeps existing model)--no-migration- Skip migration generation (use with--no-modelfor existing models)
What Gets Generated
For main_app resources:
- Model -
app/models/model_name.rb - Migration -
db/migrate/xxx_create_model_names.rb - Controller -
app/controllers/model_names_controller.rb - Policy -
app/policies/model_name_policy.rb - Definition -
app/definitions/model_name_definition.rb
For packaged resources:
- Model -
app/models/package_name/model_name.rb - Migration -
db/migrate/xxx_create_package_name_model_names.rb - Controller -
packages/package_name/app/controllers/package_name/model_names_controller.rb - Policy -
packages/package_name/app/policies/package_name/model_name_policy.rb - Definition -
packages/package_name/app/definitions/package_name/model_name_definition.rb
Migration Customizations
The generator creates basic migrations. Always review and customize the migration before running:
Inline Indexes (preferred)
create_table :model_names do |t|
t.belongs_to :parent, null: false, foreign_key: true
t.string :name, null: false
t.timestamps
t.index :name
t.index [:parent_id, :name], unique: true
end
Cascade Delete
t.belongs_to :parent, null: false, foreign_key: {on_delete: :cascade}
Default Values
Default values can be set directly in the generator using {default:value} syntax (see Field Type Syntax above). For more complex defaults or expressions, edit the migration:
t.boolean :is_active, default: true
t.integer :status, default: 0
t.datetime :published_at, default: -> { "CURRENT_TIMESTAMP" }
Examples
Main App Resource
rails g pu:res:scaffold Post \
user:belongs_to \
title:string \
'content:text?' \
'published_at:datetime?' \
--dest=main_app
Resource with Precision and Indexes
rails g pu:res:scaffold Property \
company:belongs_to \
code:string:uniq \
'latitude:decimal{11,8}' \
'longitude:decimal?{11,8}' \
'value:decimal?{15,2}' \
'notes:text?' \
--dest=main_app
Optional Association
rails g pu:res:scaffold Comment \
user:belongs_to \
'parent:belongs_to?' \
body:text \
--dest=blogging
Cross-Package Reference
rails g pu:res:scaffold Comment \
user:belongs_to \
blogging/post:belongs_to \
body:text \
--dest=comments
After Generation
- Review and customize the migration (add cascade delete, defaults, composite indexes)
- Run
rails db:migrate - Connect resource to portal:
rails g pu:res:conn Post --dest=admin_portal - Customize policy permissions as needed
- Add definition customizations for UI behavior