The MVC Architecture
MVC (Model-View-Controller) is the architectural pattern that Rails is built upon. Understanding MVC is essential for building well-organized Rails applications.
What is MVC?
MVC separates your application into three interconnected components:
- Model: Handles data and business logic
- View: Displays information to the user
- Controller: Coordinates between Model and View
How MVC Works in Rails
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Browser │────▶│ Controller │────▶│ Model │
│ Request │ │ │ │ │
└─────────────┘ └──────────────┘ └─────────────┘
│ │
│ │
▼ │
┌─────────────┐ │
│ View │◀────────────┘
│ │
└─────────────┘
│
▼
┌─────────────┐
│ Response │
│ (HTML) │
└─────────────┘
The Request Cycle
Let's trace a request through a Rails app:
1. User Makes a Request
GET /articles/1
2. Router Matches the Request
ruby
1# config/routes.rb
2Rails.application.routes.draw do
3 resources :articles
4end
5
6# This routes GET /articles/1 to ArticlesController#show3. Controller Receives the Request
ruby
1# app/controllers/articles_controller.rb
2class ArticlesController < ApplicationController
3 def show
4 @article = Article.find(params[:id])
5 # params[:id] is "1" from the URL
6 end
7end4. Model Fetches Data
ruby
1# app/models/article.rb
2class Article < ApplicationRecord
3 # Active Record handles database queries
4 # Article.find(1) generates:
5 # SELECT * FROM articles WHERE id = 1
6end5. View Renders the Response
erb
1<!-- app/views/articles/show.html.erb -->
2<article>
3 <h1><%= @article.title %></h1>
4 <p><%= @article.body %></p>
5 <small>Published: <%= @article.created_at.strftime("%B %d, %Y") %></small>
6</article>6. HTML Response Sent to Browser
html
1<article>
2 <h1>My First Article</h1>
3 <p>This is the content of my article...</p>
4 <small>Published: January 15, 2024</small>
5</article>Models in Detail
Models handle:
- Database interactions
- Data validation
- Business logic
- Relationships between data
ruby
1# app/models/article.rb
2class Article < ApplicationRecord
3 # Relationships
4 belongs_to :author
5 has_many :comments
6
7 # Validations
8 validates :title, presence: true, length: { minimum: 5 }
9 validates :body, presence: true
10
11 # Scopes (query shortcuts)
12 scope :published, -> { where(published: true) }
13 scope :recent, -> { order(created_at: :desc) }
14
15 # Business logic
16 def publish!
17 update(published: true, published_at: Time.current)
18 end
19
20 def reading_time
21 (body.split.size / 200.0).ceil
22 end
23endViews in Detail
Views handle:
- Displaying data to users
- HTML structure
- Presenting formatted content
erb
1<!-- app/views/articles/index.html.erb -->
2<h1>All Articles</h1>
3
4<% @articles.each do |article| %>
5 <div class="article-card">
6 <h2><%= link_to article.title, article %></h2>
7 <p><%= truncate(article.body, length: 150) %></p>
8 <div class="meta">
9 By <%= article.author.name %> •
10 <%= article.reading_time %> min read
11 </div>
12 </div>
13<% end %>Controllers in Detail
Controllers handle:
- Receiving HTTP requests
- Processing user input
- Calling models for data
- Selecting which view to render
ruby
1# app/controllers/articles_controller.rb
2class ArticlesController < ApplicationController
3 before_action :set_article, only: [:show, :edit, :update, :destroy]
4 before_action :authenticate_user!, except: [:index, :show]
5
6 # GET /articles
7 def index
8 @articles = Article.published.recent.page(params[:page])
9 end
10
11 # GET /articles/1
12 def show
13 end
14
15 # GET /articles/new
16 def new
17 @article = Article.new
18 end
19
20 # POST /articles
21 def create
22 @article = current_user.articles.build(article_params)
23
24 if @article.save
25 redirect_to @article, notice: 'Article was created!'
26 else
27 render :new, status: :unprocessable_entity
28 end
29 end
30
31 # GET /articles/1/edit
32 def edit
33 end
34
35 # PATCH/PUT /articles/1
36 def update
37 if @article.update(article_params)
38 redirect_to @article, notice: 'Article was updated!'
39 else
40 render :edit, status: :unprocessable_entity
41 end
42 end
43
44 # DELETE /articles/1
45 def destroy
46 @article.destroy
47 redirect_to articles_url, notice: 'Article was deleted!'
48 end
49
50 private
51
52 def set_article
53 @article = Article.find(params[:id])
54 end
55
56 def article_params
57 params.require(:article).permit(:title, :body, :published)
58 end
59endThe Golden Rules of MVC
1. Fat Models, Skinny Controllers
Put business logic in models, not controllers:
ruby
1# Bad - logic in controller
2class ArticlesController < ApplicationController
3 def publish
4 @article = Article.find(params[:id])
5 @article.published = true
6 @article.published_at = Time.current
7 @article.save
8 UserMailer.article_published(@article).deliver_later
9 end
10end
11
12# Good - logic in model
13class Article < ApplicationRecord
14 def publish!
15 update(published: true, published_at: Time.current)
16 notify_subscribers
17 end
18
19 private
20
21 def notify_subscribers
22 UserMailer.article_published(self).deliver_later
23 end
24end
25
26class ArticlesController < ApplicationController
27 def publish
28 @article = Article.find(params[:id])
29 @article.publish!
30 end
31end2. Don't Put Logic in Views
Views should only display data:
erb
1<!-- Bad - logic in view -->
2<% if article.published && article.published_at < 7.days.ago %>
3 <span class="badge">New!</span>
4<% end %>
5
6<!-- Good - use a helper or model method -->
7<% if article.recent? %>
8 <span class="badge">New!</span>
9<% end %>ruby
1# In the model
2class Article < ApplicationRecord
3 def recent?
4 published && published_at > 7.days.ago
5 end
6end3. Controllers Should Be RESTful
Stick to the standard CRUD actions:
ruby
1# Standard RESTful actions
2def index # GET /articles
3def show # GET /articles/1
4def new # GET /articles/new
5def create # POST /articles
6def edit # GET /articles/1/edit
7def update # PATCH /articles/1
8def destroy # DELETE /articles/1Why MVC Matters
- Separation of Concerns: Each component has a single responsibility
- Testability: Easy to test each component in isolation
- Maintainability: Changes in one area don't affect others
- Reusability: Models and views can be reused across controllers
- Team Collaboration: Developers can work on different components simultaneously
Understanding MVC is the foundation for everything else in Rails!
