Что такое MVC? Является ли Rails MVC-фреймворком?

Основы программирования
Oleksandr Vykhor
64
01-10-2026 11:07:37


Что такое MVC? Является ли Rails MVC-фреймворком?

MVC — один из самых известных архитектурных паттернов в разработке и один из первых терминов, с которыми сталкиваешься при изучении Ruby on Rails. В этой статье разберём, что такое MVC, откуда он появился, как запрос проходит через Rails-приложение, можно ли действительно назвать Rails MVC-фреймворком и как держать каждый слой в чистоте по мере роста проекта.

Что такое MVC?

MVC (Model–View–Controller, Модель–Представление–Контроллер) — это архитектурный паттерн, который делит приложение на три части с разными обязанностями:

  • Модель (Model) — данные и бизнес-логика. Модель знает, что такое заказ, пользователь или счёт, какие правила к ним применяются и как они хранятся.
  • Представление (View) — отображение. Представление решает, как данные выглядят для пользователя: HTML-страница, JSON, письмо.
  • Контроллер (Controller) — координация. Контроллер принимает ввод пользователя, поручает модели выполнить работу и выбирает, какое представление показать.

Главная идея — разделение ответственности (separation of concerns): бизнес-правила не зависят от того, как выглядит страница, а страница не решает, как сохраняются данные. Каждую часть можно менять, тестировать и понимать отдельно.

Немного истории

MVC описал Трюгве Реенскауг в 1978–1979 годах, когда работал со Smalltalk в Xerox PARC. Паттерн создавался для настольных графических интерфейсов: представление подписывалось на изменения модели и само перерисовывалось, а контроллер обрабатывал ввод с мыши и клавиатуры.

Позже веб-фреймворки адаптировали эту идею к миру HTTP с его циклом «запрос–ответ». Такую веб-версию иногда называют Model 2: браузер отправляет запрос, контроллер его обрабатывает, работает с моделями и рендерит представление, а готовый ответ отправляется обратно. Ruby on Rails (2004) сделал этот подход очень популярным, и за ним последовали многие фреймворки: Django, Laravel, ASP.NET MVC, Spring MVC, Phoenix.

Как запрос проходит через Rails-приложение

  1. Браузер отправляет запрос, например GET /articles/42.
  2. Роутер (config/routes.rb) находит подходящий контроллер и действие: ArticlesController#show.
  3. Контроллер читает параметры и запрашивает данные у модели: Article.find(42).
  4. Модель (Active Record) строит SQL-запрос, получает строку из базы и возвращает объект Article.
  5. Контроллер передаёт объект в представление, которое рендерит HTML (или JSON).
  6. Ответ возвращается в браузер.

Посмотрим на каждую часть в коде.

Маршрутизация

# config/routes.rb
Rails.application.routes.draw do
  resources :articles
end

Эта одна строка создаёт семь RESTful-маршрутов: index, show, new, create, edit, update и destroy.

Модель

# app/models/article.rb
class Article < ApplicationRecord
  belongs_to :author, class_name: "User"
  has_many :comments, dependent: :destroy

  validates :title, presence: true, length: { maximum: 200 }
  validates :body, presence: true

  scope :published, -> { where.not(published_at: nil) }

  def publish!
    update!(published_at: Time.current)
  end

  def reading_time
    (body.split.size / 200.0).ceil
  end
end

Модель содержит связи, валидации, запросы (скоупы) и поведение предметной области, например publish!. Она ничего не знает об HTTP, сессиях или HTML.

Контроллер

# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  before_action :set_article, only: %i[show edit update destroy]

  def index
    @articles = Article.published.order(published_at: :desc)
  end

  def show
  end

  def create
    @article = Current.user.articles.build(article_params)

    if @article.save
      redirect_to @article, notice: "Article created"
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.expect(article: [:title, :body])
  end
end

Контроллер тонкий: он читает параметры, вызывает модель и решает, что делать дальше — сделать редирект или отрендерить шаблон. params.expect — способ фильтрации параметров в Rails 8; в Rails 7 то же самое пишется как params.require(:article).permit(:title, :body).

Представление

<%# app/views/articles/show.html.erb %>
<article>
  <h1><%= @article.title %></h1>
  <p class="meta">
    <%= @article.author.name %> · <%= @article.reading_time %> min read
  </p>

  <%= simple_format(@article.body) %>

  <%= render @article.comments %>
</article>

Представление только отображает данные, полученные от контроллера. Оно не выполняет запросы к базе по собственной инициативе и не содержит бизнес-правил.

Так является ли Rails MVC-фреймворком?

Да. MVC — это основа Rails: папки app/models, app/views и app/controllers, библиотеки Active Record, Action View и Action Controller и соглашения об именовании, которые связывают их вместе, построены по этому паттерну. Официальные руководства Rails описывают его как MVC-фреймворк.

Но есть несколько важных нюансов.

1. Это веб-MVC, а не классический MVC

В оригинальном MVC из Smalltalk представление наблюдало за моделью и обновлялось при её изменении. В Rails представление рендерится один раз на запрос и ничего не знает о последующих изменениях. Обновления в реальном времени добавляются отдельными инструментами: Action Cable и Turbo Streams.

2. Модель — это Active Record

В Rails модель обычно — это класс, который объединяет бизнес-логику и доступ к базе данных (паттерн Active Record, описанный Мартином Фаулером). Это очень удобно, но означает, что логика предметной области и хранение данных живут в одном классе. В других архитектурах их часто разделяют (например, паттернами Data Mapper или Repository).

3. Rails — это больше, чем три буквы

Современное Rails-приложение содержит много частей, которые не укладываются в M, V или C:

  • Роутер — сопоставляет URL с контроллерами.
  • Хелперы — функции для представлений.
  • Мейлеры — работают как контроллеры для писем, со своими представлениями.
  • Джобы (Active Job, Solid Queue) — фоновая работа.
  • Каналы (Action Cable) — WebSockets.
  • Hotwire (Turbo и Stimulus) — интерактивность на фронтенде.
  • Concerns — общие модули для моделей и контроллеров.

Поэтому точнее сказать, что Rails построен вокруг MVC и добавляет поверх него много других компонентов.

4. В API-приложениях меняется V

При rails new my_api --api HTML-шаблонов нет. «Представлением» становится JSON, который строится через render json:, Jbuilder или сериализаторы, а интерфейс живёт в отдельном фронтенде (React, Vue или мобильное приложение). Разделение ответственности при этом сохраняется.

Типичные проблемы и их решения

Толстые контроллеры

Типичная ошибка новичков — класть бизнес-логику в контроллеры: расчёты, отправку писем, вызовы внешних API. Такой контроллер трудно читать и тестировать. Классический совет Rails — «тонкий контроллер, толстая модель» (skinny controller, fat model).

Толстые модели

Но если переносить всё в модели, через год User или Order может разрастись до тысячи строк. Поэтому в Rails-проектах часто добавляют дополнительные слои:

  • Сервисные объекты (service objects) — один класс на одну бизнес-операцию.
  • Объекты форм (form objects) — формы, работающие сразу с несколькими моделями.
  • Объекты запросов (query objects) — сложные запросы к базе.
  • Презентеры / декораторы — логика отображения, которой не место ни в представлении, ни в модели.
  • ViewComponent или Phlex — переиспользуемые и тестируемые компоненты представления.

Пример сервисного объекта, который оставляет контроллер тонким:

# app/services/orders/checkout.rb
module Orders
  class Checkout
    def initialize(order, payment_gateway: PaymentGateway.new)
      @order = order
      @payment_gateway = payment_gateway
    end

    def call
      Order.transaction do
        @payment_gateway.charge!(@order.total, @order.user)
        @order.update!(status: :paid, paid_at: Time.current)
      end

      OrderMailer.with(order: @order).confirmation.deliver_later
      @order
    end
  end
end

# app/controllers/checkouts_controller.rb
class CheckoutsController < ApplicationController
  def create
    order = Current.user.orders.find(params[:order_id])
    Orders::Checkout.new(order).call
    redirect_to order, notice: "Thank you for your order!"
  rescue PaymentGateway::Error => e
    redirect_to order, alert: e.message
  end
end

Логика в представлениях

Запросы к базе и сложные условия в шаблонах делают их трудночитаемыми и могут вызывать N+1 запросы. Загружайте данные в контроллере (через includes), выносите форматирование в хелперы или презентеры и держите шаблоны максимально простыми.

MVC и его родственники: MVP и MVVM

  • MVP (Model–View–Presenter) — представление полностью пассивно, а презентер берёт на себя всю логику отображения. Популярен в старых Android- и Windows Forms-приложениях.
  • MVVM (Model–View–ViewModel) — представление связано с моделью представления через привязку данных (data binding) и обновляется автоматически. Типичен для Vue, Angular, WPF и SwiftUI.

У всех этих паттернов та же цель, что и у MVC: отделить данные и бизнес-логику от интерфейса.

Плюсы и минусы MVC

Плюсы:

  • Понятная структура: все знают, где искать код. В Rails это особенно сильно благодаря соглашениям.
  • Разделение ответственности: проще менять интерфейс, не трогая бизнес-правила, и наоборот.
  • Простое тестирование: модели, контроллеры и представления можно тестировать отдельно.
  • Параллельная работа: фронтенд-разработчик может работать с представлениями, пока бэкенд-разработчик работает с моделями.

Минусы:

  • Трёх слоёв мало для больших приложений, поэтому появляются дополнительные (сервисы, формы, запросы).
  • Границы не всегда очевидны, и команды спорят, где должен жить код.
  • Для очень маленьких скриптов или одностраничных утилит MVC может быть избыточным.

Лучшие практики MVC в Rails

  1. Держите контроллеры тонкими: параметры, авторизация, вызов модели или сервиса и ответ.
  2. Помещайте бизнес-правила в модели или сервисы, а не в контроллеры и представления.
  3. Делайте представления простыми: никаких запросов и сложной логики; используйте хелперы, партиалы и компоненты.
  4. Следуйте RESTful-маршрутам: семь стандартных действий на ресурс. Если нужно нестандартное действие, подумайте о новом ресурсе или контроллере.
  5. Соблюдайте соглашения Rails для имён и папок: именно на них работает «магия».
  6. Добавляйте новые слои только при необходимости: сервисный объект на каждые две строки кода — это тоже переусложнение.
  7. Следите за N+1 запросами: используйте includes в контроллере или в скоупах модели.

Заключение

MVC делит приложение на три части: модель хранит данные и бизнес-правила, представление показывает их пользователю, а контроллер связывает их, обрабатывая запросы. Rails, безусловно, MVC-фреймворк: вся структура проекта построена вокруг этого паттерна. При этом это веб-версия MVC с моделями на Active Record, и Rails добавляет к ней маршрутизацию, джобы, мейлеры, Hotwire и многое другое. Понимание MVC помогает знать, где место каждому куску кода, а понимание его границ — вовремя добавлять нужные слои по мере роста приложения.


Назад