Entity System
Manage any ActiveRecord model through the RailsPress admin interface without writing custom controllers or views.
Quick Start
1. Include the Entity concern in your model
class Project < ApplicationRecord
include Railspress::Entity
has_rich_text :body
has_many_attached :gallery
# Declare which fields appear in the CMS
railspress_fields :title, :client, :featured
railspress_fields :description
railspress_fields :body
railspress_fields :gallery, as: :attachments
# Optional: Custom sidebar label
railspress_label "Client Projects"
validates :title, presence: true
end
2. Register the entity in an initializer
Railspress.configure do |config|
config.register_entity :project
end
That's it. Your model now has full CRUD at /railspress/admin/entities/projects.
The railspress_fields DSL
Declare which model attributes should appear in the admin forms and index table.
Basic usage (auto-detected types)
railspress_fields :title, :description, :published
Types are automatically detected from:
- ActionText associations (
has_rich_text) - ActiveStorage attachments (
has_one_attached,has_many_attached) - Database column types
Explicit type override
railspress_fields :body, as: :rich_text
railspress_fields :gallery, as: :attachments
Supported Field Types
| Type | Detection | Form Input | Index Display |
|---|---|---|---|
:string |
String columns | Text field | Truncated text |
:text |
Text columns | Textarea | Truncated text |
:rich_text |
has_rich_text |
Trix editor | Stripped/truncated |
:boolean |
Boolean columns | Checkbox | Yes/No badge |
:integer |
Integer columns | Number field | Raw value |
:decimal |
Decimal/float columns | Number field (step: any) | Raw value |
:datetime |
Datetime columns | Datetime-local picker | Formatted date |
:date |
Date columns | Date picker | Formatted date |
:attachment |
has_one_attached |
File input | Attached/None badge |
:attachments |
has_many_attached |
Multiple file input | "N images" badge |
:focal_point_image |
focal_point_image macro |
Image upload + focal picker | Attached/None badge |
:list |
Explicit only | Text field (comma-separated) | "N items" badge |
:lines |
Explicit only | Textarea (line-separated) | "N items" badge |
Form layout
Fields are automatically organized into a two-column layout:
- Main column: String, text, rich text, and
:linesfields - Sidebar: Boolean, numeric, date, and
:listfields in an "Options" section - Sidebar: Each attachment field gets its own section with preview and removal
Array Fields
Store arrays of strings in JSON columns using :list and :lines field types. These are useful for things like tech stacks, feature lists, or highlights.
Quick Start
class Project < ApplicationRecord
include Railspress::Entity
railspress_fields :title, :client
railspress_fields :tech_stack, as: :list # Comma-separated input
railspress_fields :highlights, as: :lines # Line-separated input
end
Virtual attributes are auto-generated. No extra concerns or boilerplate needed.
Migration
Use JSON or JSONB columns with array defaults:
class AddArrayFieldsToProjects < ActiveRecord::Migration[8.0]
def change
add_column :projects, :tech_stack, :jsonb, default: [], null: false
add_column :projects, :highlights, :jsonb, default: [], null: false
end
end
Two Field Types
| Type | Input Format | Best For | Deduplicates? |
|---|---|---|---|
:list |
Comma-separated | Short items (tags, tech names) | Yes |
:lines |
One per line | Sentences, paragraphs | No |
How It Works
When you declare railspress_fields :tech_stack, as: :list, RailsPress auto-generates:
# Nil guard - always returns array, never nil
def tech_stack
super || []
end
# Virtual getter: array → comma string (for form population)
def tech_stack_list
tech_stack.join(", ")
end
# Virtual setter: comma string → array (for form submission)
def tech_stack_list=(value)
self.tech_stack = value.split(",").map(&:strip).reject(&:blank?).uniq
end
For :lines fields, the separator is newline instead of comma, and duplicates are preserved.
Form Behavior
:list fields render as a single-line text input with a hint to separate items with commas.
:lines fields render as a textarea with a hint to enter one item per line.
Display Behavior
- Index view: Shows item count badge ("3 items")
- Show view
:list: Displays inline as "Ruby, Rails, PostgreSQL" - Show view
:lines: Displays as bullet list
Input Parsing
:list fields:
- Split on comma
- Strip whitespace from each item
- Remove empty items
- Deduplicate (preserves first occurrence)
project.tech_stack_list = " Ruby ,, Rails , Ruby "
project.tech_stack # => ["Ruby", "Rails"]
:lines fields:
- Split on newline (handles both
\nand\r\n) - Strip whitespace from each item
- Remove empty lines
- Preserves duplicates (order matters for content like steps)
Entity Registration
Railspress.configure do |config|
# Symbol registration (preferred)
config.register_entity :project
config.register_entity :testimonial
config.register_entity :team_member
# Custom sidebar label
config.register_entity :case_study, label: "Client Work"
end
Routes
Entity routes are automatically generated under /railspress/admin/entities/:entity_type:
| Method | Path | Action |
|---|---|---|
| GET | /entities/projects |
index |
| GET | /entities/projects/new |
new |
| POST | /entities/projects |
create |
| GET | /entities/projects/:id |
show |
| GET | /entities/projects/:id/edit |
edit |
| PATCH/PUT | /entities/projects/:id |
update |
| DELETE | /entities/projects/:id |
destroy |
Attachments
Single attachment
class Testimonial < ApplicationRecord
include Railspress::Entity
has_one_attached :avatar
railspress_fields :name, :quote
railspress_fields :avatar, as: :attachment
end
Multiple attachments
class Project < ApplicationRecord
include Railspress::Entity
has_many_attached :gallery
railspress_fields :title
railspress_fields :gallery, as: :attachments
end
Attachment fields support image preview thumbnails, individual removal checkboxes, direct upload (if configured), and adding new files while keeping existing ones.
Focal Point Images
For images that need focal point editing (hero banners, cards, OG images), use the focal_point_image macro. It combines ActiveStorage attachment, focal point support, and entity field registration in one call.
Basic Usage
class Project < ApplicationRecord
include Railspress::Entity
include Railspress::HasFocalPoint
focal_point_image :cover_image
end
With Variants
class Project < ApplicationRecord
include Railspress::Entity
include Railspress::HasFocalPoint
focal_point_image :main_image do |attachable|
attachable.variant :hero, resize_to_fill: [2100, 900, { crop: :centre }]
attachable.variant :card, resize_to_fill: [800, 500, { crop: :centre }]
attachable.variant :thumb, resize_to_fill: [400, 250, { crop: :centre }]
attachable.variant :og, resize_to_fill: [1200, 630, { crop: :centre }]
end
# No need to declare main_image in railspress_fields - auto-registered
railspress_fields :title, :client, :featured
end
One call handles three things: has_one_attached (declares the ActiveStorage attachment with optional variants), has_focal_point (adds focal point editing support), and railspress_fields registration (automatic).
Using in Views
<%= image_tag url_for(@project.main_image.variant(:hero)),
style: @project.focal_point_css(:main_image),
class: "object-cover w-full h-full" %>
See Image Focal Points for full documentation on focal points, context overrides, and the admin UI.
Pagination & Scopes
Built-in Scopes
Every Entity includes these scopes:
| Scope | Description |
|---|---|
ordered |
By created_at descending |
recent |
First 10 records, ordered |
page(n) |
Pagination helper (uses PER_PAGE) |
Project.ordered # All projects, newest first
Project.recent # Last 10 projects
Project.ordered.page(2) # Second page of projects
Project.where(featured: true).recent # Last 10 featured projects
Custom Page Size
Override the default page size (20) in your model:
class Project < ApplicationRecord
include Railspress::Entity
PER_PAGE = 50 # Override default of 20
railspress_fields :title, :client
end
Tagging
Make any entity taggable by including the Railspress::Taggable concern:
class Project < ApplicationRecord
include Railspress::Entity
include Railspress::Taggable
railspress_fields :title, :description
end
What Taggable Provides
| Method | Description |
|---|---|
tag_list |
Returns tags as comma-separated string |
tag_list= |
Sets tags from comma-separated string |
tags |
Returns associated Railspress::Tag records |
taggings |
Returns the join records |
Form Integration
<%= rp_string_field f, :tag_list, label: "Tags", hint: "Comma-separated" %>
Tags are shared across all taggable models. A tag "ruby" used on a Post and a Project points to the same Railspress::Tag record.
Custom Index Columns
By default, entity index pages display columns based on Railspress.default_index_columns (defaults to :id, :title, :name, :created_at). Only columns that the model responds to are shown.
Overriding with a Constant
class Project < ApplicationRecord
include Railspress::Entity
RAILSPRESS_INDEX_COLUMNS = [:title, :client, :status, :created_at]
railspress_fields :title, :client, :description, :status, :featured
end
Overriding with a Method
For dynamic logic, override the railspress_index_columns class method:
class Project < ApplicationRecord
include Railspress::Entity
def self.railspress_index_columns
columns = [:title, :client, :created_at]
columns << :budget if Current.user&.admin?
columns
end
end
Global Default
Railspress.configure do |config|
config.default_index_columns = [:title, :name, :updated_at]
end
Full Example
class TeamMember < ApplicationRecord
include Railspress::Entity
has_one_attached :headshot
has_rich_text :bio
railspress_fields :name, :role, :email
railspress_fields :bio
railspress_fields :headshot, as: :attachment
railspress_fields :featured, :display_order
railspress_label "Team"
validates :name, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true
scope :featured, -> { where(featured: true) }
scope :ordered, -> { order(display_order: :asc, name: :asc) }
end
Admin available at: /railspress/admin/entities/team_members