Czym jest MVC? Czy Rails jest frameworkiem MVC?

Podstawy Programowania
Oleksandr Vykhor
64
01-10-2026 11:07:37


Czym jest MVC? Czy Rails jest frameworkiem MVC?

MVC to jeden z najbardziej znanych wzorców architektonicznych w programowaniu i jedno z pierwszych pojęć, na jakie trafia się podczas nauki Ruby on Rails. W tym artykule wyjaśnimy, czym jest MVC, skąd się wziął, jak żądanie przechodzi przez aplikację Rails, czy Rails naprawdę można nazwać frameworkiem MVC oraz jak utrzymać porządek w każdej warstwie, gdy projekt rośnie.

Czym jest MVC?

MVC (Model–View–Controller, Model–Widok–Kontroler) to wzorzec architektoniczny, który dzieli aplikację na trzy części o różnych zadaniach:

  • Model: dane i logika biznesowa. Model wie, czym jest zamówienie, użytkownik czy faktura, jakie reguły ich dotyczą i jak są przechowywane.
  • Widok (View): prezentacja. Widok decyduje, jak dane wyglądają dla użytkownika: strona HTML, JSON, e-mail.
  • Kontroler (Controller): koordynacja. Kontroler przyjmuje dane od użytkownika, zleca modelowi wykonanie pracy i wybiera, który widok pokazać.

Główna idea to rozdzielenie odpowiedzialności (separation of concerns): reguły biznesowe nie zależą od wyglądu strony, a strona nie decyduje, jak zapisywane są dane. Każdą część można zmieniać, testować i rozumieć osobno.

Trochę historii

MVC opisał Trygve Reenskaug w latach 1978–1979, pracując ze Smalltalkiem w Xerox PARC. Wzorzec powstał dla desktopowych interfejsów graficznych: widok subskrybował zmiany modelu i sam się przerysowywał, a kontroler obsługiwał mysz i klawiaturę.

Później frameworki webowe dostosowały tę ideę do świata HTTP z jego cyklem „żądanie–odpowiedź”. Taką webową wersję nazywa się czasem Model 2: przeglądarka wysyła żądanie, kontroler je obsługuje, pracuje z modelami i renderuje widok, a gotowa odpowiedź wraca do przeglądarki. Ruby on Rails (2004) bardzo spopularyzował to podejście, a za nim poszło wiele frameworków: Django, Laravel, ASP.NET MVC, Spring MVC, Phoenix.

Jak żądanie przechodzi przez aplikację Rails

  1. Przeglądarka wysyła żądanie, na przykład GET /articles/42.
  2. Router (config/routes.rb) znajduje odpowiedni kontroler i akcję: ArticlesController#show.
  3. Kontroler odczytuje parametry i prosi model o dane: Article.find(42).
  4. Model (Active Record) buduje zapytanie SQL, pobiera wiersz z bazy i zwraca obiekt Article.
  5. Kontroler przekazuje obiekt do widoku, który renderuje HTML (lub JSON).
  6. Odpowiedź wraca do przeglądarki.

Przyjrzyjmy się każdej części w kodzie.

Routing

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

Ta jedna linia tworzy siedem tras RESTful: index, show, new, create, edit, update i destroy.

Model

# 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

Model zawiera asocjacje, walidacje, zapytania (scope'y) i zachowanie domenowe, takie jak publish!. Nie wie nic o HTTP, sesjach ani HTML.

Kontroler

# 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

Kontroler jest cienki: odczytuje parametry, wywołuje model i decyduje, co dalej, czyli przekierować czy wyrenderować szablon. params.expect to sposób filtrowania parametrów w Rails 8; w Rails 7 to samo zapisuje się jako params.require(:article).permit(:title, :body).

Widok

<%# 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>

Widok tylko wyświetla dane otrzymane od kontrolera. Nie wykonuje z własnej inicjatywy zapytań do bazy i nie zawiera reguł biznesowych.

Czy zatem Rails jest frameworkiem MVC?

Tak. MVC to fundament Rails: foldery app/models, app/views i app/controllers, biblioteki Active Record, Action View i Action Controller oraz konwencje nazewnictwa, które je łączą, zbudowane są według tego wzorca. Oficjalne przewodniki Rails opisują go jako framework MVC.

Są jednak ważne niuanse.

1. To webowe MVC, a nie klasyczne

W oryginalnym MVC ze Smalltalka widok obserwował model i aktualizował się, gdy model się zmieniał. W Rails widok jest renderowany raz na żądanie i nie wie nic o późniejszych zmianach. Aktualizacje w czasie rzeczywistym dodaje się osobnymi narzędziami: Action Cable i Turbo Streams.

2. Model to Active Record

W Rails model to zwykle klasa, która łączy logikę biznesową z dostępem do bazy danych (wzorzec Active Record opisany przez Martina Fowlera). Jest to bardzo wygodne, ale oznacza, że logika domenowa i zapis danych żyją w tej samej klasie. W innych architekturach często się je rozdziela (np. wzorcami Data Mapper lub Repository).

3. Rails to więcej niż trzy litery

Nowoczesna aplikacja Rails zawiera wiele części, które nie mieszczą się w M, V ani C:

  • Router: mapuje adresy URL na kontrolery.
  • Helpery: funkcje dla widoków.
  • Mailery: działają jak kontrolery dla e-maili, z własnymi widokami.
  • Joby (Active Job, Solid Queue): praca w tle.
  • Kanały (Action Cable): WebSockety.
  • Hotwire (Turbo i Stimulus): interaktywność po stronie frontendu.
  • Concerns: wspólne moduły dla modeli i kontrolerów.

Dlatego dokładniej jest powiedzieć, że Rails jest zbudowany wokół MVC i dodaje do niego wiele innych komponentów.

4. W aplikacjach API zmienia się V

Przy rails new my_api --api nie ma szablonów HTML. „Widokiem” staje się JSON budowany przez render json:, Jbuilder lub serializery, a interfejs żyje w osobnym frontendzie (React, Vue lub aplikacja mobilna). Rozdzielenie odpowiedzialności pozostaje takie samo.

Typowe problemy i ich rozwiązania

Grube kontrolery

Typowy błąd początkujących to umieszczanie logiki biznesowej w kontrolerach: obliczeń, wysyłania e-maili, wywołań zewnętrznych API. Taki kontroler trudno czytać i testować. Klasyczna rada Rails brzmi: „chudy kontroler, gruby model” (skinny controller, fat model).

Grube modele

Jeśli jednak przeniesiesz wszystko do modeli, po roku User czy Order może urosnąć do tysiąca linii. Dlatego w projektach Rails często dodaje się dodatkowe warstwy:

  • Obiekty serwisowe (service objects): jedna klasa na jedną operację biznesową.
  • Obiekty formularzy (form objects): formularze pracujące z kilkoma modelami naraz.
  • Obiekty zapytań (query objects): złożone zapytania do bazy.
  • Prezentery / dekoratory: logika wyświetlania, która nie pasuje ani do widoku, ani do modelu.
  • ViewComponent lub Phlex: komponenty widoku wielokrotnego użytku, łatwe do testowania.

Przykład obiektu serwisowego, dzięki któremu kontroler pozostaje cienki:

# 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

Logika w widokach

Zapytania do bazy i złożone warunki w szablonach utrudniają ich czytanie i mogą powodować zapytania N+1. Ładuj dane w kontrolerze (przez includes), przenoś formatowanie do helperów lub prezenterów i utrzymuj szablony jak najprostsze.

MVC i jego krewni: MVP i MVVM

  • MVP (Model–View–Presenter): widok jest całkowicie pasywny, a prezenter przejmuje całą logikę prezentacji. Popularny w starszych aplikacjach Android i Windows Forms.
  • MVVM (Model–View–ViewModel): widok jest powiązany z modelem widoku przez wiązanie danych (data binding) i aktualizuje się automatycznie. Typowy dla Vue, Angulara, WPF i SwiftUI.

Wszystkie te wzorce mają ten sam cel co MVC: oddzielić dane i logikę biznesową od interfejsu.

Zalety i wady MVC

Zalety:

  • Przejrzysta struktura: każdy wie, gdzie szukać kodu. W Rails jest to szczególnie widoczne dzięki konwencjom.
  • Rozdzielenie odpowiedzialności: łatwiej zmienić interfejs bez ruszania reguł biznesowych i odwrotnie.
  • Prostsze testowanie: modele, kontrolery i widoki można testować osobno.
  • Równoległa praca: frontendowiec może pracować nad widokami, a backendowiec nad modelami.

Wady:

  • Trzy warstwy to za mało dla dużych aplikacji, więc pojawiają się dodatkowe (serwisy, formularze, zapytania).
  • Granice nie zawsze są oczywiste, a zespoły spierają się, gdzie powinien żyć kod.
  • Dla bardzo małych skryptów lub jednostronicowych narzędzi MVC może być przesadą.

Dobre praktyki MVC w Rails

  1. Utrzymuj cienkie kontrolery: parametry, autoryzacja, wywołanie modelu lub serwisu i odpowiedź.
  2. Umieszczaj reguły biznesowe w modelach lub serwisach, a nie w kontrolerach i widokach.
  3. Utrzymuj proste widoki: żadnych zapytań ani złożonej logiki; używaj helperów, partiali i komponentów.
  4. Trzymaj się tras RESTful: siedem standardowych akcji na zasób. Jeśli potrzebujesz niestandardowej akcji, rozważ nowy zasób lub kontroler.
  5. Przestrzegaj konwencji Rails dotyczących nazw i folderów: to na nich opiera się cała „magia”.
  6. Dodawaj nowe warstwy tylko wtedy, gdy są potrzebne: obiekt serwisowy na każde dwie linie kodu to także przekombinowanie.
  7. Pilnuj zapytań N+1: używaj includes w kontrolerze lub w scope'ach modelu.

Podsumowanie

MVC dzieli aplikację na trzy części: model przechowuje dane i reguły biznesowe, widok pokazuje je użytkownikowi, a kontroler łączy je, obsługując żądania. Rails zdecydowanie jest frameworkiem MVC: cała struktura projektu zbudowana jest wokół tego wzorca. Jest to jednak webowa wersja MVC z modelami opartymi na Active Record, a Rails dodaje do niej routing, joby, mailery, Hotwire i wiele więcej. Zrozumienie MVC pomaga wiedzieć, gdzie powinien trafić każdy fragment kodu, a znajomość jego ograniczeń pozwala w porę dodać odpowiednie warstwy, gdy aplikacja rośnie.


Wstecz