Skip to content

Ruby on Rails - 控制器

Ruby on Rails - 控制器(Controllers)

Section titled “Ruby on Rails - 控制器(Controllers)”

控制器(Controller)是 Rails 应用中 MVC 模式的 ‘C’。它充当用户(通过路由和视图)与应用数据(模型)之间的中介。控制器处理接收到的 Web 请求,使用模型检索或修改数据,然后通过选择一个视图模板来渲染响应。

控制器的主要职责:

  • 从路由接收 HTTP 请求。
  • 与模型交互,用于获取、创建、更新或删除数据。
  • 管理会话(session)信息和 Cookie。
  • 处理用户身份验证和授权(通常借助辅助 Gem)。
  • 通过实例变量(例如 @books)将数据提供给视图(view)。
  • 选择要渲染的视图模板或要重定向(redirect)到的 URL。

您可以使用 Rails 生成器(generator)来创建控制器。对于我们的图书馆应用,让我们创建一个 BooksController:

$ bin/rails generate controller Books index show new edit create update destroy

按照惯例,控制器名称是复数形式并使用驼峰命名法(CamelCased)(例如,BooksController)。此命令会创建:

  • app/controllers/books_controller.rb 文件。
  • 指定动作(action)的视图模板文件(例如,app/views/books/index.html.erb)。
  • 测试文件、辅助方法(helper)文件以及可能的资源文件(asset files)。
  • 如果 config/routes.rb 中不存在,它还会为 books 添加 RESTful 风格的路由(resourceful routes)。

生成的 app/controllers/books_controller.rb 大致如下所示:

class BooksController < ApplicationController
def index
end
def show
end
# ... other generated actions ...
end

控制器类继承自 ApplicationController(在 app/controllers/application_controller.rb 中定义),这是放置应用范围的控制器逻辑(如身份验证或布局设置)的好地方。

让我们为 BooksController 实现常见的 CRUD (Create, Read, Update, Delete) 动作。

index 动作通常显示资源列表。

def index
@books = Book.all.order(title: :asc) # 获取所有书籍,按标题排序
# Rails 将自动渲染 app/views/books/index.html.erb
end

show 动作显示单个资源的详细信息,通过 params[:id] 标识。

before_action :set_book, only: [:show, :edit, :update, :destroy]
# ...
def show
# @book 由 set_book before_action 设置
# 渲染 app/views/books/show.html.erb
end
# ...
private
def set_book
@book = Book.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The book you were looking for could not be found."
redirect_to books_path
end

before_action :set_book 会在指定的动作之前调用 set_book 方法。这减少了查找书籍的代码重复(DRY - Don’t Repeat Yourself)。对 RecordNotFound 的错误处理是良好的实践。

new 动作准备一个空的 @book 对象,用于新建书籍表单。

def new
@book = Book.new
@subjects = Subject.all.order(:name) # 用于表单中的主题下拉列表
# 渲染 app/views/books/new.html.erb
end

create 动作处理来自 new 表单的提交,并尝试保存新书。

def create
@book = Book.new(book_params)
if @book.save
redirect_to @book, notice: 'Book was successfully created.' # 重定向到显示页面
else
@subjects = Subject.all.order(:name) # 如果重新渲染表单,重新加载主题数据
render :new, status: :unprocessable_entity # 重新渲染 new 表单并显示错误
end
end

book_params 是一个私有方法,它使用 Strong Parameters(强参数)仅允许指定的属性,从而防止批量赋值(mass assignment)漏洞。

private
def book_params
params.require(:book).permit(:title, :price, :subject_id, :description, :published_at)
end

edit 动作获取一本现有书籍(使用 set_book 方法),用于编辑表单。

def edit
# @book 由 set_book 设置
@subjects = Subject.all.order(:name)
# 渲染 app/views/books/edit.html.erb
end

update 动作处理来自 edit 表单的提交。

def update
# @book 由 set_book 设置
if @book.update(book_params)
redirect_to @book, notice: 'Book was successfully updated.'
else
@subjects = Subject.all.order(:name)
render :edit, status: :unprocessable_entity
end
end

destroy 动作删除一本书。

def destroy
# @book 由 set_book 设置
@book.destroy
redirect_to books_url, notice: 'Book was successfully destroyed.', status: :see_other
end

status: :see_other (HTTP 303) 推荐用于在改变状态的非 GET 请求后进行重定向,尤其是在使用 Turbo 时。

class BooksController < ApplicationController
before_action :set_book, only: [:show, :edit, :update, :destroy]
before_action :load_subjects, only: [:new, :edit, :create, :update] # 为表单加载主题数据
def index
@books = Book.includes(:subject).all.order(title: :asc)
end
def show
end
def new
@book = Book.new
end
def edit
end
def create
@book = Book.new(book_params)
if @book.save
redirect_to @book, notice: 'Book was successfully created.'
else
render :new, status: :unprocessable_entity
end
end
def update
if @book.update(book_params)
redirect_to @book, notice: 'Book was successfully updated.'
else
render :edit, status: :unprocessable_entity
end
end
def destroy
@book.destroy
redirect_to books_url, notice: 'Book was successfully destroyed.', status: :see_other
end
# Example custom action for listing books by subject (if needed here)
# 例如,用于按主题列出书籍的自定义动作(如果需要)
# def books_by_subject
# @subject = Subject.find(params[:subject_id])
# @books = @subject.books.order(title: :asc)
# render :index # Or a custom view like 'books_by_subject.html.erb'
# # 渲染 :index 或自定义视图如 'books_by_subject.html.erb'
# end
private
def set_book
@book = Book.find(params[:id])
rescue ActiveRecord::RecordNotFound
flash[:alert] = "The book you were looking for could not be found."
redirect_to books_path
end
def load_subjects
@subjects = Subject.all.order(:name)
end
def book_params
params.require(:book).permit(:title, :price, :subject_id, :description, :published_at)
end
end

现在,这个控制器处理了书籍资源(resource)的完整生命周期。

定义好控制器动作后,您需要定义路由(route)将 URL 映射到这些动作。然后,您将创建视图模板(view template)来显示信息和表单以便用户交互。