The MVC Pattern in Rails
MVC stands for Model-View-Controller, and it’s the architectural pattern that powers every Rails application. Understanding MVC is essential for building maintainable Rails apps. In this tutorial, you’ll learn what each layer does and how they communicate.
What is MVC?
MVC divides your application into three interconnected components:
- Model — Handles data and business logic
- View — Handles presentation and user interface
- Controller — Handles requests and coordinates between Model and View
This separation keeps your code organized and each part focused on one responsibility.
The Model Layer
The Model represents your data and the rules that govern it. In Rails, models typically interact with a database through ActiveRecord.
# app/models/article.rb
class Article < ApplicationRecord
validates :title, presence: true
validates :body, presence: true
scope :published, -> { where(published: true) }
end
Models define relationships between data:
class Author < ApplicationRecord
has_many :articles
end
class Article < ApplicationRecord
belongs_to :author
end
The Model layer is where your business logic lives — validations, calculations, and database queries.
The View Layer
Views are what users see. They render the data from the Model into HTML, JSON, or other formats.
<!-- app/views/articles/show.html.erb -->
<h1><%= @article.title %></h1>
<p><%= @article.body %></p>
<%= link_to "Edit", edit_article_path(@article) %>
Views should contain minimal logic — just enough to display data. Complex logic belongs in the Model or Controller.
Rails supports multiple view formats:
# Respond with HTML or JSON based on the request
def show
@article = Article.find(params[:id])
respond_to do |format|
format.html # renders show.html.erb
format.json { render json: @article }
end
end
The Controller Layer
Controllers handle incoming requests, coordinate with Models, and return responses. They’re the bridge between the user and your application.
# app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
def index
@articles = Article.published
end
def show
@article = Article.find(params[:id])
end
def create
@article = Article.new(article_params)
if @article.save
redirect_to @article, notice: "Article created!"
else
render :new, status: :unprocessable_entity
end
end
private
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
Controllers should be thin — they coordinate between Model and View rather than containing business logic.
How MVC Works Together
Here’s the typical request flow in a Rails application:
- Request — User visits
/articles/1 - Route — Rails routes
GET /articles/:idtoArticlesController#show - Controller — Controller asks Model for the data
- Model — Retrieves data from database
- Controller — Passes data to View
- View — Renders HTML with the data
- Response — Server sends HTML back to the browser
Example: A Complete Flow
# routes.rb
Rails.application.routes.draw do
resources :articles
end
# articles_controller.rb
class ArticlesController < ApplicationController
def index
@articles = Article.published.order(created_at: :desc)
end
end
<!-- app/views/articles/index.html.erb -->
<h1>Articles</h1>
<% @articles.each do |article| %>
<article>
<h2><%= article.title %></h2>
<p><%= article.body.truncate(100) %></p>
</article>
<% end %>
# article.rb (Model)
class Article < ApplicationRecord
validates :title, presence: true
def truncated_body
body.truncate(100)
end
end
Notice how each layer has a single responsibility:
- Model manages data and provides helper methods
- Controller handles the request flow
- View focuses on presentation
When MVC Matters
Following MVC conventions makes your Rails app:
- Maintainable — Each layer has a clear purpose
- Testable — You can test each layer independently
- Scalable — Adding features doesn’t require rewriting everything
- Collaborative — Multiple developers can work on different layers
Common Mistakes to Avoid
Putting too much logic in controllers or views is a common pitfall:
# BAD: Logic in controller
def index
@articles = Article.where("created_at > ?", 7.days.ago)
.where(published: true)
.order(created_at: :desc)
.limit(10)
end
# GOOD: Logic in model (or a query object)
# app/models/article.rb
class Article < ApplicationRecord
scope :recent_published, -> {
where("created_at > ?", 7.days.ago)
.where(published: true)
.order(created_at: :desc)
.limit(10)
}
end
# Controller stays thin
def index
@articles = Article.recent_published
end
Summary
The MVC pattern is the foundation of Rails architecture:
- Model — Data and business logic (ActiveRecord)
- View — Presentation layer (ERB, JSON, etc.)
- Controller — Request handling and coordination
Each layer has a clear responsibility, making your code easier to maintain and test. Keep controllers thin, put logic in models, and use views primarily for display.
In the next tutorial, we’ll dive deeper into Rails routing and see how requests are mapped to controllers.