ActiveRecord Setup for Ruby on Rails
ActiveRecord — Active Record pattern implementation from DHH, built into Rails. In Rails 7.x async queries, encrypts, strict models, and query composition with with appeared. We cover current setup for Rails 7.1+.
database.yml Configuration
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
variables:
statement_timeout: '10s'
development:
<<: *default
database: myapp_development
production:
primary:
<<: *default
url: <%= ENV['DATABASE_URL'] %>
replica:
<<: *default
url: <%= ENV['DATABASE_REPLICA_URL'] %>
replica: true
Replica Configuration
# config/application.rb
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
config.active_record.database_resolver_context = ActiveRecord::Middleware::DatabaseSelector::Resolver::Session
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
connects_to database: { writing: :primary, reading: :replica }
end
Model
class Product < ApplicationRecord
belongs_to :category
has_many :product_tags, dependent: :destroy
has_many :tags, through: :product_tags
has_many :images, -> { order(:sort_order) }, class_name: 'ProductImage', dependent: :destroy
enum :status, { draft: 'draft', published: 'published', archived: 'archived' }, prefix: true
validates :title, presence: true, length: { maximum: 500 }
validates :slug, presence: true, uniqueness: true
before_validation :generate_slug, if: -> { slug.blank? && title.present? }
scope :published, -> { where(status: :published) }
scope :in_category, ->(id) { where(category_id: id) }
scope :recent, -> { order(created_at: :desc) }
scope :with_preview, -> { includes(:category, :tags, images: []) }
private
def generate_slug
self.slug = title.parameterize
end
end
Migration
class CreateProducts < ActiveRecord::Migration[7.1]
def change
create_table :products do |t|
t.string :title, limit: 500, null: false
t.string :slug, limit: 520, null: false
t.decimal :price, precision: 12, scale: 2
t.string :status, limit: 20, null: false, default: 'draft'
t.references :category, null: false, foreign_key: { on_delete: :restrict }
t.jsonb :meta
t.timestamps
end
add_index :products, :slug, unique: true
add_index :products, [:status, :created_at]
add_index :products, [:category_id, :status]
end
end
Queries
def index
@products = Product
.published
.in_category(params[:category_id])
.with_preview
.recent
.page(params[:page]).per(24)
end
Use Bullet gem in development to detect N+1:
# Gemfile
gem 'bullet', group: :development
# config/environments/development.rb
Bullet.enable = true
Bullet.rails_logger = true
Timeline
Setup for new Rails project: 1 day. Optimization: 1–2 days.







