MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Ruby on Rails 控制器详解

2023-05-193.0k 阅读

1. Rails 控制器基础

在 Ruby on Rails 应用程序中,控制器(Controller)起着至关重要的作用。它充当了模型(Model)和视图(View)之间的桥梁,处理用户请求并决定如何响应。

1.1 控制器的创建

在 Rails 项目的根目录下,通过 Rails 命令行工具可以轻松创建一个新的控制器。例如,要创建一个名为 PostsController 的控制器,可以执行以下命令:

rails generate controller Posts

这会在 app/controllers 目录下生成一个 posts_controller.rb 文件,同时还会创建一些相关的测试文件以及视图目录结构。

1.2 控制器的基本结构

一个典型的 Rails 控制器类继承自 ApplicationController,如下所示:

class PostsController < ApplicationController
  def index
    @posts = Post.all
  end

  def show
    @post = Post.find(params[:id])
  end

  def new
    @post = Post.new
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
    else
      render :new
    end
  end

  def edit
    @post = Post.find(params[:id])
  end

  def update
    @post = Post.find(params[:id])
    if @post.update(post_params)
      redirect_to @post
    else
      render :edit
    end
  end

  def destroy
    @post = Post.find(params[:id])
    @post.destroy
    redirect_to posts_path
  end

  private

  def post_params
    params.require(:post).permit(:title, :content)
  end
end

在这个例子中,PostsController 定义了一系列的动作(Action),如 indexshownew 等。每个动作通常对应一个特定的用户请求,并且负责处理相关的业务逻辑。

2. 控制器动作与请求处理

2.1 动作的定义

控制器中的每个公开方法都可以被视为一个动作。例如,上述 PostsController 中的 index 动作:

def index
  @posts = Post.all
end

这个动作从数据库中获取所有的 Post 记录,并将其赋值给实例变量 @posts。这个实例变量随后可以在相应的视图中使用,以展示这些文章。

2.2 请求方法与动作映射

Rails 支持多种 HTTP 请求方法,如 GETPOSTPUTDELETE 等。不同的请求方法通常映射到不同的控制器动作。例如:

  • GET /posts 请求通常会被映射到 PostsControllerindex 动作,用于获取文章列表。
  • GET /posts/1 请求会被映射到 show 动作,这里的 1 会作为参数传递,用于获取特定文章的详细信息。
  • POST /posts 请求会被映射到 create 动作,用于创建新的文章。

这种映射关系是通过 Rails 的路由系统来实现的。例如,在 config/routes.rb 文件中,可能会有如下配置:

resources :posts

这简单的一行代码就自动为 PostsController 生成了一系列默认的路由,对应不同的请求方法和动作。

2.3 参数处理

控制器可以从请求中获取参数。例如,在 create 动作中:

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post
  else
    render :new
  end
end

private

def post_params
  params.require(:post).permit(:title, :content)
end

params 是一个包含所有请求参数的哈希。params.require(:post) 确保请求参数中包含一个名为 post 的哈希,否则会抛出异常。permit(:title, :content) 则允许 post 哈希中包含 titlecontent 这两个参数,防止参数注入攻击。

3. 控制器与视图的交互

3.1 渲染视图

控制器通过 render 方法来渲染视图。例如,在 new 动作中:

def new
  @post = Post.new
  render :new
end

这里的 render :new 会渲染 app/views/posts/new.html.erb 视图文件。视图文件中可以使用控制器中定义的实例变量,如 @post,来展示数据。

3.2 重定向

除了渲染视图,控制器还可以使用 redirect_to 方法进行重定向。例如,在 create 动作成功保存文章后:

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post
  else
    render :new
  end
end

redirect_to @post 会将用户重定向到 @post 的展示页面。这里 @post 会根据 Rails 的路由规则解析为相应的 URL。

3.3 传递数据到视图

如前所述,控制器通过实例变量将数据传递到视图。例如,在 show 动作中:

def show
  @post = Post.find(params[:id])
end

app/views/posts/show.html.erb 视图中,可以这样使用 @post

<h1><%= @post.title %></h1>
<p><%= @post.content %></p>

通过这种方式,控制器将数据库中的数据传递给视图,由视图负责展示给用户。

4. 控制器与模型的交互

4.1 数据查询

控制器经常需要从模型中查询数据。例如,在 index 动作中:

def index
  @posts = Post.all
end

这里使用 Post.all 从数据库中获取所有的 Post 记录。Post 是一个继承自 ActiveRecord::Base 的模型类,它提供了各种数据库查询方法,如 findwherelimit 等。

4.2 数据创建与更新

createupdate 动作中,控制器与模型交互来创建和更新数据。例如,在 create 动作中:

def create
  @post = Post.new(post_params)
  if @post.save
    redirect_to @post
  else
    render :new
  end
end

Post.new(post_params) 使用传入的参数创建一个新的 Post 对象,然后通过 @post.save 将其保存到数据库中。如果保存成功,重定向到文章展示页面;否则,重新渲染创建文章的表单。

4.3 数据删除

destroy 动作中,控制器负责从数据库中删除数据:

def destroy
  @post = Post.find(params[:id])
  @post.destroy
  redirect_to posts_path
end

这里先通过 Post.find(params[:id]) 找到要删除的文章,然后使用 @post.destroy 将其从数据库中删除,最后重定向到文章列表页面。

5. 过滤器(Filters)

5.1 过滤器的类型

Rails 提供了三种类型的过滤器:before_actionafter_actionaround_action

  • before_action:在指定的动作之前运行。例如:
class PostsController < ApplicationController
  before_action :authenticate_user!, only: [:create, :update, :destroy]

  def index
    @posts = Post.all
  end

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
    else
      render :new
    end
  end

  # 其他动作...
end

这里的 authenticate_user! 是一个自定义的方法,会在 createupdatedestroy 动作之前运行,用于验证用户是否已经登录。

  • after_action:在指定的动作之后运行。例如:
class PostsController < ApplicationController
  after_action :log_action, only: [:create, :update, :destroy]

  def create
    @post = Post.new(post_params)
    if @post.save
      redirect_to @post
    else
      render :new
    end
  end

  def log_action
    logger.info "Action #{action_name} was executed"
  end
end

log_action 方法会在 createupdatedestroy 动作执行完毕后记录一条日志。

  • around_action:围绕指定的动作运行,既可以在动作之前也可以在动作之后执行代码。例如:
class PostsController < ApplicationController
  around_action :measure_time, only: [:index]

  def index
    @posts = Post.all
  end

  def measure_time
    start_time = Time.now
    yield
    end_time = Time.now
    logger.info "Action index took #{(end_time - start_time).to_f} seconds"
  end
end

measure_time 方法会在 index 动作执行前后记录时间,计算该动作的执行时间并记录日志。

5.2 过滤器的应用场景

过滤器在很多场景下都非常有用,比如:

  • 身份验证:在需要用户登录才能执行的动作前使用 before_action 验证用户身份。
  • 日志记录:使用 after_actionaround_action 记录动作的执行情况,便于调试和监控。
  • 数据预处理和后处理:在动作执行前或后对数据进行一些处理,如数据格式化、缓存更新等。

6. 控制器的高级特性

6.1 嵌套资源

在 Rails 中,经常会遇到资源嵌套的情况。例如,一个文章(Post)可能有多个评论(Comment),评论属于特定的文章。可以通过嵌套资源来实现这种关系。

首先,在 config/routes.rb 中定义嵌套路由:

resources :posts do
  resources :comments
end

然后,在 CommentsController 中,可以通过参数获取所属的文章。例如:

class CommentsController < ApplicationController
  def create
    @post = Post.find(params[:post_id])
    @comment = @post.comments.new(comment_params)
    if @comment.save
      redirect_to [@post, @comment]
    else
      render :new
    end
  end

  private

  def comment_params
    params.require(:comment).permit(:content)
  end
end

这里通过 params[:post_id] 获取所属文章的 ID,然后创建与该文章关联的评论。

6.2 响应格式

Rails 控制器可以根据请求的格式返回不同类型的响应,如 JSON、XML 等。例如,要为 PostsController 添加 JSON 格式的响应支持:

class PostsController < ApplicationController
  def index
    @posts = Post.all
    respond_to do |format|
      format.html
      format.json { render json: @posts }
    end
  end
end

当请求的格式为 html 时,会渲染默认的视图;当请求格式为 json 时,会将文章列表以 JSON 格式返回。

6.3 自定义动作

除了默认的资源动作(如 indexshow 等),控制器还可以定义自定义动作。例如,在 PostsController 中添加一个自定义动作 search

class PostsController < ApplicationController
  def search
    @posts = Post.where("title LIKE? OR content LIKE?", "%#{params[:query]}%", "%#{params[:query]}%")
    render :index
  end

  # 其他默认动作...
end

然后在 config/routes.rb 中定义该动作的路由:

resources :posts do
  collection do
    get :search
  end
end

这样,通过 GET /posts/search?query=keyword 请求就可以执行 search 动作,在文章标题或内容中搜索包含指定关键词的文章,并渲染 index 视图展示结果。

7. 错误处理与异常

7.1 动作内的错误处理

在控制器动作中,经常需要处理各种可能出现的错误。例如,在 show 动作中,如果文章不存在,可能会引发 ActiveRecord::RecordNotFound 异常:

def show
  begin
    @post = Post.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    redirect_to posts_path, alert: "Post not found"
  end
end

这里使用 begin...rescue 块捕获 ActiveRecord::RecordNotFound 异常,并将用户重定向到文章列表页面,同时显示一条提示信息。

7.2 全局错误处理

Rails 还提供了全局错误处理机制。可以在 config/application.rb 中配置:

config.exceptions_app = self.routes

然后在 config/routes.rb 中定义错误处理的路由:

get "/404", to: "errors#not_found"
get "/500", to: "errors#internal_server_error"

接着创建 ErrorsController 来处理这些错误:

class ErrorsController < ApplicationController
  def not_found
    render status: 404
  end

  def internal_server_error
    render status: 500
  end
end

这样,当应用程序发生 404 或 500 错误时,会分别渲染 errors/not_found.html.erberrors/internal_server_error.html.erb 视图,给用户提供友好的错误提示。

通过深入理解 Rails 控制器的这些方面,开发人员能够更加高效地构建功能丰富、健壮的 Web 应用程序。从基础的创建和结构,到与模型、视图的交互,再到高级特性和错误处理,每个环节都紧密相连,共同构成了 Rails 应用程序的核心逻辑。在实际开发中,根据具体的业务需求,灵活运用这些知识,能够打造出优秀的用户体验和高效的业务流程。同时,不断探索和实践新的技术和模式,有助于提升 Rails 应用程序的性能、可维护性和可扩展性。