Настройка ActiveRecord для Ruby on Rails

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Настройка ActiveRecord для Ruby on Rails
Средняя
~1 рабочий день
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Настройка ActiveRecord для Ruby on Rails

ActiveRecord — реализация паттерна Active Record от DHH, встроенная в Rails. В Rails 7.x появились async queries, encrypts, строгие модели и инструмент компоновки запросов через with. Рассматриваем актуальную настройку для Rails 7.1+.

Конфигурация database.yml

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  timeout: 5000
  connect_timeout: 5
  checkout_timeout: 5
  reaping_frequency: 10
  variables:
    statement_timeout: '10s'    # убивает запросы длиннее 10 секунд

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

production:
  primary:
    <<: *default
    url: <%= ENV['DATABASE_URL'] %>
  replica:
    <<: *default
    url: <%= ENV['DATABASE_REPLICA_URL'] %>
    replica: true

statement_timeout на уровне PostgreSQL-сессии — страховка от случайного full-scan на продакшне. Долгие операции (миграции, экспорт) нужно запускать с SET statement_timeout = 0 явно.

Подключение реплики

# config/application.rb (или config/environments/production.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

Rails автоматически направляет SELECT на реплику с задержкой 2.seconds после последней записи (чтобы учесть репликационный лаг).

Модель

# app/models/product.rb
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, format: { with: /\A[a-z0-9-]+\z/ }
  validates :price, numericality: { greater_than: 0 }

  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: []) }

  # Rails 7.1: encrypted attribute
  # encrypts :internal_notes

  private

  def generate_slug
    self.slug = title.parameterize
  end
end

enum с prefix: true даёт методы status_published?, status_published! — это избегает конфликта имён, когда несколько enum используют одно значение.

Миграция

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, null: false
      t.string  :status,      limit: 20,  null: false, default: 'draft'
      t.references :category, null: false, foreign_key: { on_delete: :restrict }
      t.boolean :is_featured,             null: false, default: false
      t.jsonb   :meta
      t.timestamps
    end

    add_index :products, :slug, unique: true
    add_index :products, [:status, :created_at]
    add_index :products, [:category_id, :status]
    add_index :products, :meta, using: :gin  # для поиска по jsonb
  end
end

Запросы без N+1

# app/controllers/catalog_controller.rb
def index
  @products = Product
    .published
    .in_category(params[:category_id])
    .with_preview
    .recent
    .page(params[:page]).per(24)
end

with_preview подгружает category, tags и images через три дополнительных IN запроса — не JOIN. В Rails 7 для ассоциаций has_many :through это работает корректно.

Для определения N+1 в development используем Bullet:

# Gemfile (development)
gem 'bullet'
# config/environments/development.rb
config.after_initialize do
  Bullet.enable = true
  Bullet.rails_logger = true
  Bullet.add_footer = true
end

Async queries (Rails 7.1)

# Параллельная загрузка данных без блокировки
products_promise = Product.published.recent.limit(10).load_async
stats_promise    = Order.where(created_at: 1.week.ago..).count_async

products = products_promise.value  # ждёт, если ещё не готово
stats    = stats_promise.value

Запросы выполняются в фоновом потоке пула ActiveRecord. На PostgreSQL с несколькими коннекциями это даёт реальный выигрыш для dashboard-страниц.

Транзакции

ActiveRecord::Base.transaction do
  order = Order.create!(user: current_user, status: :pending)

  items.each do |item|
    order.order_items.create!(
      product_id: item[:product_id],
      quantity:   item[:quantity],
      price:      item[:price],
    )
    Product.find(item[:product_id]).decrement!(:stock, item[:quantity])
  end
end

create! и decrement! с восклицательным знаком выбрасывают исключение при ошибке — транзакция откатится автоматически.

Сроки

Настройка ActiveRecord для нового Rails-проекта с нуля, включая реплику, миграции, seed-данные и Bullet: 1 день. Оптимизация существующего приложения (устранение N+1, добавление индексов, переход на async queries): 1–2 дня.