Table of Contents
- What is MVC Architecture?
- Origins and Purpose
- Core Principles
- The Three Components of MVC
- Model: The Data Layer
- View: The Presentation Layer
- Controller: The Mediator
- How MVC Components Interact: The Request Cycle
- Step-by-Step Tutorial: Building a Book Review App
- Prerequisites
- Step 1: Set Up a New Rails Project
- Step 2: Generate the Book Model
- Step 3: Seed Sample Data
- Step 4: Generate the Books Controller
- Step 5: Configure Routes
- Step 6: Implement Controller Actions
- Step 7: Create Views for Display
- Step 8: Test the Application
- Best Practices for Ruby MVC Development
- Conclusion
- References
What is MVC Architecture?
MVC (Model-View-Controller) is a software design pattern that divides an application into three interconnected components. It was first introduced in the 1970s by Trygve Reenskaug at Xerox PARC for desktop GUI applications, but it has since become the standard for web development—especially in frameworks like Ruby on Rails, Django (Python), and Laravel (PHP).
Origins and Purpose
The goal of MVC is to solve a common problem in software development: tightly coupled code. Without structure, applications often mix data handling, user interface, and logic into a single blob, making them hard to debug, update, or scale. MVC fixes this by enforcing separation of concerns—each component has a clear, single responsibility.
Core Principles
- Separation of Concerns: Each component (Model, View, Controller) handles one part of the application’s workflow, reducing overlap and complexity.
- Reusability: Components can be reused across the application (e.g., a
Bookmodel can power both a list view and a detail view). - Testability: Isolating components makes it easier to write unit tests (e.g., test model logic without involving views).
The Three Components of MVC
Let’s dive into each component, using a “Book Review” app as a running example. We’ll build an app that lets users view a list of books and their details (title, author, rating).
Model: The Data Layer
The Model is the “brain” of the application. It manages the application’s data, enforces business rules, and interacts with the database. In Ruby (and Rails), models are typically tied to database tables via ActiveRecord (Rails’ ORM, or Object-Relational Mapper), which simplifies database operations (e.g., creating, reading, updating, deleting records).
Responsibilities of a Model:
- Data Validation: Ensuring data meets criteria (e.g., a book must have a title).
- Business Logic: Implementing rules like “a book’s rating must be between 1 and 5.”
- Database Interaction: Fetching, creating, updating, or deleting records (via ActiveRecord).
Example: A Book Model
In Rails, generating a model automatically creates a Ruby class that inherits from ApplicationRecord (which itself inherits from ActiveRecord::Base). This class maps to a database table (e.g., books).
# app/models/book.rb
class Book < ApplicationRecord
# Validations: Ensure title and author are present, rating is 1-5
validates :title, presence: true
validates :author, presence: true
validates :rating, numericality: { only_integer: true, in: 1..5 }
# Business logic: A method to format the book's full info
def full_info
"#{title} by #{author} (Rating: #{rating}/5)"
end
end
Here, the Book model defines validations (data rules) and a full_info method (business logic). ActiveRecord handles the rest: it will automatically map to the books database table and provide methods like Book.all (fetch all books) or Book.create (add a new book).
View: The Presentation Layer
The View is the “face” of the application. It presents data to the user in a readable format (e.g., HTML, JSON) and handles user input (e.g., form submissions). In Rails, views are typically written with ERB (Embedded Ruby), which lets you embed Ruby code directly into HTML.
Responsibilities of a View:
- Display Data: Render data from the model (e.g., list of books).
- User Interface: Provide forms, buttons, and links for user interaction.
- No Business Logic: Views should focus on presentation, not data processing.
Example: A Book List View
A view for listing all books might look like this (using ERB):
# app/views/books/index.html.erb
<h1>All Books</h1>
<ul>
<% @books.each do |book| %>
<li>
<%= link_to book.title, book_path(book) %>
by <%= book.author %> - Rating: <%= book.rating %>/5
</li>
<% end %>
</ul>
Here:
<% ... %>executes Ruby code (e.g., looping through@books).<%= ... %>outputs Ruby code results (e.g.,book.title).link_tois a Rails helper to generate HTML links.
Controller: The Mediator
The Controller is the “traffic cop” of the application. It receives user requests, fetches or updates data via the model, and passes that data to the view for rendering. Controllers act as a bridge between models and views, ensuring they don’t communicate directly.
Responsibilities of a Controller:
- Handle Requests: Respond to user actions (e.g., clicking a link, submitting a form).
- Fetch Data: Use models to retrieve or modify data (e.g.,
Book.allto get all books). - Pass Data to Views: Make data available to views via instance variables (e.g.,
@books). - Render Responses: Choose which view to display (or redirect to another page).
Example: A BooksController
In Rails, controllers are Ruby classes that inherit from ApplicationController. Each public method in a controller is an action that handles a specific request (e.g., index for listing books, show for displaying a single book).
# app/controllers/books_controller.rb
class BooksController < ApplicationController
# List all books
def index
@books = Book.all # Fetch all books from the model
end
# Show details for a single book
def show
@book = Book.find(params[:id]) # Fetch a book by ID from the model
end
end
Here:
indexandshoware controller actions.@booksand@bookare instance variables, which are automatically available to the corresponding views (index.html.erbandshow.html.erb).params[:id]is a hash containing request parameters (e.g., the ID of the book to display).
How MVC Components Interact: The Request Cycle
Now that we understand the components, let’s see how they work together. Here’s the request cycle for a user visiting /books (the list of all books):
- User Makes a Request: The user types
localhost:3000/booksinto their browser. - Routes Direct the Request: Rails routes (
config/routes.rb) map the URL/booksto theindexaction inBooksController. - Controller Fetches Data: The
BooksController#indexaction runs@books = Book.all, which uses theBookmodel to fetch all books from the database. - Controller Renders the View: The controller automatically renders
app/views/books/index.html.erb, passing@booksto the view. - View Generates HTML: The ERB view processes
@books, loops through them, and generates HTML. - Response Sent to User: The HTML is sent back to the user’s browser, which displays the list of books.
Step-by-Step Tutorial: Building a Book Review App
Let’s put this into practice by building the “Book Review” app from scratch. We’ll use Ruby on Rails, as it natively implements MVC and simplifies setup.
Prerequisites
- Ruby (3.0+ recommended) and Rails installed. If not, run:
gem install rails - Basic familiarity with the command line.
Step 1: Set Up a New Rails Project
First, create a new Rails application. Open your terminal and run:
rails new book_review
cd book_review
This creates a new Rails project with all the MVC structure pre-built (models, views, controllers, etc.).
Step 2: Generate the Book Model
Next, generate a Book model to store book data. We’ll include title (string), author (string), and rating (integer) attributes.
Run this command in the terminal:
rails generate model Book title:string author:string rating:integer
This creates:
- A model file:
app/models/book.rb - A database migration file:
db/migrate/[timestamp]_create_books.rb
Now, run the migration to create the books database table:
rails db:migrate
Step 3: Seed Sample Data
Let’s add sample books to the database. Open db/seeds.rb and add:
# db/seeds.rb
Book.create([
{ title: "The Great Gatsby", author: "F. Scott Fitzgerald", rating: 4 },
{ title: "1984", author: "George Orwell", rating: 5 },
{ title: "To Kill a Mockingbird", author: "Harper Lee", rating: 5 }
])
Run the seed command to insert the data:
rails db:seed
Step 4: Generate the Books Controller
Generate a BooksController with index and show actions (to list all books and show a single book):
rails generate controller Books index show
This creates:
- A controller file:
app/controllers/books_controller.rb - View files:
app/views/books/index.html.erbandapp/views/books/show.html.erb - Test files and route stubs.
Step 5: Configure Routes
Rails uses config/routes.rb to map URLs to controller actions. Open this file and replace its contents with:
# config/routes.rb
Rails.application.routes.draw do
# Map GET /books to BooksController#index
get 'books', to: 'books#index', as: 'books'
# Map GET /books/:id to BooksController#show
get 'books/:id', to: 'books#show', as: 'book'
end
Alternatively, use Rails’ resources shortcut to auto-generate RESTful routes (recommended):
# config/routes.rb
Rails.application.routes.draw do
resources :books, only: [:index, :show] # Generates index and show routes
end
Step 6: Implement Controller Actions
Open app/controllers/books_controller.rb and update the index and show actions to fetch data:
# app/controllers/books_controller.rb
class BooksController < ApplicationController
def index
@books = Book.all # Fetch all books from the database
end
def show
@book = Book.find(params[:id]) # Fetch a single book by ID (from the URL)
end
end
Step 7: Create Views for Display
Update the Index View
Open app/views/books/index.html.erb and replace it with:
<h1>Book Review Library</h1>
<p>Here are all the books in our collection:</p>
<ul>
<% @books.each do |book| %>
<li>
<%= link_to book.title, book_path(book) %>
<small>by <%= book.author %></small> -
<strong>Rating: <%= book.rating %>/5</strong>
</li>
<% end %>
</ul>
Update the Show View
Open app/views/books/show.html.erb and add:
<h1><%= @book.title %></h1>
<p><strong>Author:</strong> <%= @book.author %></p>
<p><strong>Rating:</strong> <%= @book.rating %>/5</p>
<%= link_to "Back to all books", books_path %>
Step 8: Test the Application
Start the Rails server:
rails server
Visit http://localhost:3000/books in your browser. You should see a list of books. Click a book title to view its details page.
Best Practices for Ruby MVC Development
-
Keep Controllers Thin: Move logic (e.g., data filtering) to the model. For example:
# Bad: Controller with logic def index @books = Book.where(rating: 5).order(created_at: :desc) end # Good: Model method class Book < ApplicationRecord def self.top_rated where(rating: 5).order(created_at: :desc) end end # Controller uses model method def index @books = Book.top_rated end -
Views Should Be “Dumb”: Avoid complex Ruby logic in views. Use helpers (e.g.,
app/helpers/books_helper.rb) for reusable view logic. -
Models Own Business Logic: Validate data, compute values, and handle database queries in models.
-
Use Strong Parameters: In Rails, restrict which attributes can be updated via forms to prevent mass assignment vulnerabilities:
private def book_params params.require(:book).permit(:title, :author, :rating) end -
Follow REST Conventions: Use Rails’
resourcesroutes and standard actions (index,show,new,create,edit,update,destroy) for consistency.
Conclusion
MVC is more than just a pattern—it’s a philosophy that promotes clean, maintainable code by separating concerns. By dividing your application into Models (data), Views (presentation), and Controllers (logic), you make it easier to debug, scale, and collaborate on.
Ruby on Rails simplifies MVC implementation with built-in tools like ActiveRecord (models), ERB (views), and routing (controllers). The “Book Review” app we built demonstrates how these components work together to handle requests, fetch data, and display it to users.
Now that you understand MVC, try extending the app: add a form to create new books, implement user authentication, or add reviews for each book. The possibilities are endless!