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

app/models/project.rb
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

config/initializers/railspress.rb
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)

Ruby
railspress_fields :title, :description, :published

Types are automatically detected from:

  1. ActionText associations (has_rich_text)
  2. ActiveStorage attachments (has_one_attached, has_many_attached)
  3. Database column types

Explicit type override

Ruby
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

Form layout

Fields are automatically organized into a two-column layout:

  • Main column: String, text, and rich text fields
  • Sidebar: Boolean, numeric, and date fields 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

app/models/project.rb
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:

db/migrate/add_array_fields_to_projects.rb
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:

Auto-generated methods
# 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)
irb
project.tech_stack_list = "  Ruby ,, Rails , Ruby  "
project.tech_stack  # => ["Ruby", "Rails"]

:lines fields:

  • Split on newline (handles both \n and \r\n)
  • Strip whitespace from each item
  • Remove empty lines
  • Preserves duplicates (order matters for content like steps)

Entity Registration

config/initializers/railspress.rb
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

app/models/testimonial.rb
class Testimonial < ApplicationRecord
  include Railspress::Entity

  has_one_attached :avatar

  railspress_fields :name, :quote
  railspress_fields :avatar, as: :attachment
end

Multiple attachments

app/models/project.rb
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.

Full Example

app/models/team_member.rb
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