Rails MVC 架构深入理解
原创2024/1/21大约 5 分钟
Rails MVC 架构深入理解
Ruby on Rails 基于 MVC (Model-View-Controller) 架构模式,提供了清晰的代码组织结构和开发约定。
Rails MVC 架构图
上图展示了 Rails 的 MVC 架构,让我们深入了解每个组件的作用和交互方式。
Model 层
Model 层使用 Active Record 模式,负责业务逻辑和数据持久化。
基本 Model
# app/models/user.rb
class User < ApplicationRecord
# 关联关系
has_many :posts, dependent: :destroy
has_many :comments
has_one :profile
# 验证
validates :email, presence: true, uniqueness: true
validates :username, presence: true, length: { minimum: 3, maximum: 20 }
validates :password, length: { minimum: 8 }, if: :password_required?
# 回调
before_save :normalize_email
after_create :send_welcome_email
before_destroy :check_can_delete
# Scope
scope :active, -> { where(active: true) }
scope :recent, -> { order(created_at: :desc).limit(10) }
scope :by_role, ->(role) { where(role: role) }
# 实例方法
def full_name
"#{first_name} #{last_name}"
end
def admin?
role == 'admin'
end
# 类方法
def self.find_by_credentials(email, password)
user = find_by(email: email)
user&.authenticate(password) ? user : nil
end
private
def normalize_email
self.email = email.downcase.strip
end
def send_welcome_email
UserMailer.welcome_email(self).deliver_later
end
def check_can_delete
throw(:abort) if admin?
end
def password_required?
new_record? || password.present?
end
endModel 职责
- ✅ 定义数据结构和关联
- ✅ 数据验证
- ✅ 业务逻辑
- ✅ 数据库交互
- ✅ 回调处理
- ❌ 不处理 HTTP 请求
- ❌ 不包含视图逻辑
关联关系详解
# app/models/post.rb
class Post < ApplicationRecord
# 一对多
belongs_to :user
has_many :comments, dependent: :destroy
# 多对多 (通过中间表)
has_many :post_tags
has_many :tags, through: :post_tags
# 多态关联
has_many :likes, as: :likeable
# 嵌套属性
accepts_nested_attributes_for :comments, allow_destroy: true
# 关联查询
scope :with_user, -> { includes(:user) }
scope :popular, -> { where('likes_count > ?', 100) }
end
# app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
belongs_to :user
# 自关联 (评论的回复)
belongs_to :parent, class_name: 'Comment', optional: true
has_many :replies, class_name: 'Comment', foreign_key: 'parent_id'
end查询接口
# 基本查询
User.find(1)
User.find_by(email: 'user@example.com')
User.where(active: true)
User.where('created_at > ?', 1.week.ago)
# 链式查询
User.where(role: 'admin')
.where('created_at > ?', 1.month.ago)
.order(created_at: :desc)
.limit(10)
# Scope 查询
User.active.recent.by_role('admin')
# 关联查询 (避免 N+1)
Post.includes(:user, :comments).where(published: true)
# 聚合查询
User.count
User.sum(:posts_count)
User.average(:age)
User.group(:role).countView 层
View 层负责展示数据,使用 ERB 或 Haml 模板。
ERB 模板
<!-- app/views/users/index.html.erb -->
<div class="users-list">
<h1>用户列表</h1>
<% if @users.any? %>
<table class="table">
<thead>
<tr>
<th>用户名</th>
<th>邮箱</th>
<th>角色</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= link_to user.username, user_path(user) %></td>
<td><%= user.email %></td>
<td><%= user.role %></td>
<td>
<%= link_to '编辑', edit_user_path(user), class: 'btn btn-sm btn-primary' %>
<%= link_to '删除', user_path(user), method: :delete,
data: { confirm: '确定删除吗?' },
class: 'btn btn-sm btn-danger' %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>暂无用户</p>
<% end %>
<%= will_paginate @users %>
</div>Partial (部分视图)
<!-- app/views/users/_user.html.erb -->
<div class="user-card" id="user-<%= user.id %>">
<div class="user-avatar">
<%= image_tag user.avatar_url, alt: user.username %>
</div>
<div class="user-info">
<h3><%= user.username %></h3>
<p><%= user.email %></p>
<%= render 'user_stats', user: user %>
</div>
</div>
<!-- 使用 partial -->
<%= render @users %>
<%= render partial: 'user', collection: @users %>
<%= render 'shared/header' %>View Helper
# app/helpers/application_helper.rb
module ApplicationHelper
def formatted_date(date)
date.strftime('%Y年%m月%d日')
end
def user_avatar(user, size: 50)
image_tag user.avatar_url || default_avatar,
size: "#{size}x#{size}",
class: 'rounded-circle'
end
def flash_class(level)
{
'notice' => 'alert-info',
'success' => 'alert-success',
'error' => 'alert-danger',
'alert' => 'alert-warning'
}[level] || 'alert-info'
end
endController 层
Controller 层处理 HTTP 请求,协调 Model 和 View。
基本 Controller
# app/controllers/users_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!
before_action :set_user, only: [:show, :edit, :update, :destroy]
before_action :authorize_user, only: [:edit, :update, :destroy]
# GET /users
def index
@users = User.includes(:profile)
.page(params[:page])
.per(20)
respond_to do |format|
format.html
format.json { render json: @users }
end
end
# GET /users/:id
def show
@posts = @user.posts.published.recent
respond_to do |format|
format.html
format.json { render json: @user, include: :posts }
end
end
# GET /users/new
def new
@user = User.new
end
# POST /users
def create
@user = User.new(user_params)
if @user.save
flash[:success] = '用户创建成功'
redirect_to @user
else
flash.now[:error] = '创建失败'
render :new, status: :unprocessable_entity
end
end
# GET /users/:id/edit
def edit
end
# PATCH/PUT /users/:id
def update
if @user.update(user_params)
flash[:success] = '更新成功'
redirect_to @user
else
flash.now[:error] = '更新失败'
render :edit, status: :unprocessable_entity
end
end
# DELETE /users/:id
def destroy
@user.destroy
flash[:success] = '用户已删除'
redirect_to users_path
end
private
def set_user
@user = User.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:error] = '用户不存在'
redirect_to users_path
end
def user_params
params.require(:user).permit(
:username, :email, :password, :password_confirmation,
:first_name, :last_name, :bio
)
end
def authorize_user
unless current_user == @user || current_user.admin?
flash[:error] = '权限不足'
redirect_to root_path
end
end
endController 职责
- ✅ 处理 HTTP 请求
- ✅ 参数处理
- ✅ 调用 Model
- ✅ 准备视图数据
- ✅ 返回响应
- ❌ 不包含业务逻辑
- ❌ 不直接操作数据库
RESTful 路由
# config/routes.rb
Rails.application.routes.draw do
root 'home#index'
# RESTful 资源路由
resources :users do
resources :posts, only: [:index, :show]
member do
get 'profile'
post 'follow'
delete 'unfollow'
end
collection do
get 'search'
end
end
# 嵌套资源
resources :posts do
resources :comments, except: [:index]
end
# 命名空间
namespace :admin do
resources :users
resources :posts
end
# API 路由
namespace :api do
namespace :v1 do
resources :users, defaults: { format: :json }
end
end
end请求流程
1. 浏览器发送请求
↓
2. Router 匹配路由
↓
3. Controller 接收请求
↓
4. Controller 调用 Model
↓
5. Model 查询数据库
↓
6. Model 返回数据
↓
7. Controller 准备视图数据
↓
8. View 渲染模板
↓
9. 返回 HTML 响应最佳实践
1. Skinny Controller, Fat Model
# 不好的做法 - 逻辑在 Controller
class PostsController < ApplicationController
def publish
@post = Post.find(params[:id])
@post.published = true
@post.published_at = Time.current
@post.save
# 发送通知...
# 更新统计...
end
end
# 好的做法 - 逻辑在 Model
class PostsController < ApplicationController
def publish
@post = Post.find(params[:id])
@post.publish!
end
end
class Post < ApplicationRecord
def publish!
transaction do
update!(published: true, published_at: Time.current)
notify_subscribers
update_statistics
end
end
end2. Service Objects
# app/services/user_registration_service.rb
class UserRegistrationService
def initialize(params)
@params = params
end
def call
ActiveRecord::Base.transaction do
user = create_user
create_profile(user)
send_verification_email(user)
user
end
end
private
def create_user
User.create!(@params.slice(:email, :password))
end
def create_profile(user)
user.create_profile!(@params.slice(:first_name, :last_name))
end
def send_verification_email(user)
UserMailer.verification_email(user).deliver_later
end
end性能优化
N+1 查询问题
# 不好 - N+1 查询
@posts = Post.all
@posts.each do |post|
puts post.user.name # 每次都查询数据库
end
# 好 - 预加载
@posts = Post.includes(:user)
@posts.each do |post|
puts post.user.name # 使用预加载的数据
end
# 复杂预加载
Post.includes(user: :profile, comments: :user)缓存
# Fragment 缓存
<% cache @post do %>
<%= render @post %>
<% end %>
# Russian Doll 缓存
<% cache ['posts', @posts.maximum(:updated_at)] do %>
<% @posts.each do |post| %>
<% cache post do %>
<%= render post %>
<% end %>
<% end %>
<% end %>
# Low-level 缓存
def expensive_operation
Rails.cache.fetch('expensive_operation', expires_in: 12.hours) do
# 耗时操作
end
end总结
Rails 的 MVC 架构通过清晰的职责分离,让代码更易维护和测试。遵循 "约定优于配置" 的原则,能够快速开发出高质量的 Web 应用。