Model Reference
Complete reference for Plutonium resource models.
Base Class
All resource models inherit from ResourceRecord:
class Post < ResourceRecord
# Your model code
endIn packages, models inherit from the package's ResourceRecord:
module Blogging
class Post < Blogging::ResourceRecord
# Your model code
end
endResourceRecord is an abstract class that inherits from ApplicationRecord and is created by the Plutonium installer.
Standard ActiveRecord Features
All standard ActiveRecord features work:
class Post < ResourceRecord
# Associations
belongs_to :user
has_many :comments, dependent: :destroy
has_one :featured_image
has_many :tags, through: :post_tags
# Validations
validates :title, presence: true, length: { maximum: 200 }
validates :slug, uniqueness: true
validates :status, inclusion: { in: %w[draft published] }
# Scopes
scope :published, -> { where(status: 'published') }
scope :recent, -> { order(created_at: :desc) }
scope :by_author, ->(user) { where(user: user) }
# Callbacks
before_save :generate_slug
after_create :notify_subscribers
# Methods
def publish!
update!(status: 'published', published_at: Time.current)
end
endNested Resources
Nesting is automatic via belongs_to associations:
class Comment < ResourceRecord
belongs_to :post
endWhen both Post and Comment are registered in a portal, Plutonium automatically creates nested routes (/posts/:post_id/comments). The associated_with scope is automatically available for querying.
See the Nested Resources Guide for details.
Entity Scoping (Multi-tenancy)
Entity scoping is configured on the portal engine, not the model:
# packages/customer_portal/lib/engine.rb
module CustomerPortal
class Engine < Rails::Engine
include Plutonium::Portal::Engine
config.after_initialize do
scope_to_entity Organization
end
end
endSee the Multi-tenancy Guide for details.
Plutonium Features
See Model Features for:
has_cents- Store monetary values as integers, expose as decimalsto_label- Human-readable record labelspath_parameter/dynamic_path_parameter- Custom URL parameters- Secure association SGIDs - Auto-generated SGID accessors for associations
associated_with- Scope for nested resource queries- Field introspection methods
Field Introspection
Plutonium introspects models to detect:
Column Types
| Database Type | Detected As |
|---|---|
string | :string |
text | :text |
integer | :integer |
bigint | :integer |
float | :float |
decimal | :decimal |
boolean | :boolean |
date | :date |
datetime | :datetime |
time | :time |
json/jsonb | :json |
Constraints
# NULL constraint detected
t.string :title, null: false # Required fieldAssociations
belongs_to :user # Detected as association field
has_many :comments # Available for association panelsValidations
validates :title, presence: true # Required
validates :email, format: { ... } # Format hint
validates :role, inclusion: { in: [...] } # Select optionsModel Organization
Feature Package Models
# packages/blogging/app/models/blogging/post.rb
module Blogging
class Post < ResourceRecord
# Namespaced model
end
endTable Naming
Namespaced models use prefixed tables:
module Blogging
class Post < ResourceRecord
# Table: blogging_posts
end
endOverride if needed:
self.table_name = "posts"Best Practices
Keep Models Thin
Put complex logic in Interactions:
# Model: simple validations and associations
class Post < ResourceRecord
validates :title, presence: true
end
# Interaction: complex logic
class PublishPost < ResourceInteraction
def execute
resource.update!(published: true)
notify_subscribers
update_search_index
succeed(resource)
end
endUse Meaningful Scopes
# Good: intention-revealing names
scope :visible_to, ->(user) { where(user: user).or(where(published: true)) }
# Avoid: generic names
scope :filtered, -> { where(status: 'active') }Validate at the Right Level
- Model: Data integrity (presence, format, uniqueness)
- Interaction: Business rules (can only publish once)
- Policy: Authorization (user must own the record)
