Що таке 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 допомагає знати, де місце кожному шматку коду, а розуміння його меж — вчасно додавати потрібні шари в міру зростання застосунку.


Назад