Rails render 和 redirect_to 进阶理解

基本用法

不同的跳转姿势

Rails 新手开发者会简单地认为 redirect_togoto指令一样,单纯地跳转执行 Rails 代码,这是不正确的。正确的解释应是:当 Rails 程序运行到 redirect_to 时,需要等待浏览器发送一个新的 http 请求再继续执行后面的代码。它的原理是通过向客户端发送 302 的 HTTP 状态码告诉浏览器需要重定向到指定的 URL 。
先看看下面的代码片段

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    render action: "index"
  end
end

show中,如果 @book对象值为 nil, 将会执行 render action: "index",那么程序就会报错。这是为什么呢?
因为 render action:不会执行指定 action 的代码,在这里不会执行 index action,那么 views 视图所依赖的@books值为 nil
解决方法就是使用 redirect_to,请看下面这段代码:

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    redirect_to action: :index
  end
end

通过 redirect_to方法,服务器发送 302 HTTP 状态码到浏览器并重定向,客户端(用户浏览器)将发送一个新的请求到服务器,indexaction 将被执行。
上面的代码片段有不够优雅,那就是浏览器会重定向,服务器接受了两次从客户端发过来的请求。使用 render 和 redirect_to 时客户端与服务器交互如下图所示:
render_redirect_to.png
当请求 show action 时,如果 @book 对象值为 nil ,那么服务器会发送 302HTTP 状态码到浏览器并重定向再次请求 index action,index 从数据库中拿到所需的全部数据之后渲染 index 视图,最后在浏览器中显示。
为了缓解服务器压力和增强用户体验,当 @book 对象为值为 nil 时,在 show action 中 再次读数据库获取渲染 index 视图所需的数据,然后可使用 render 直接渲染 index 视图模板,以此避免了重定向。代码如下:

def index
  @books = Book.all
end

def show
  @book = Book.find_by(id: params[:id])
  if @book.nil?
    @books = Book.all
    flash.now[:alert] = "Your book was not found"
    render "index"
  end
end

不会结束所在 action 执行

控制器中的代码执行到 renderredirect_to时,如果两者不是放在最后,并不会跳出并结束当前 action。
继续看一个例子:

def destroy
  redirect_to login_path unless current_user.admin?
  article = Article.destroy(params[:id])
  redirect_to articles_path, :notice => "Article '#{article.title}' was deleted."
end

根据上面的描述分析一下这段代码。若当前用户为管理员,那么会把指定的文章记录删除并重新向到文章列表页面。但是若当前用户为非管理员,那么unless current_user.admin?条件成立,redirect_to login_path执行,但是destroy执行了redirect_to login_path,但是此时并没有跳出此 action,所以接下来的代码还会执行,指定的文章还是会被删除。
那么应如何处理呢,当前角色为非管理员时重定向并使用 and return 跳出当前 action。 render 亦然。

当记录在数据库中成功更更改后使用redirect_to 而不是 render

两者应用在 update, create action 更新数据库记录时注意事项。防止出现脏数据(表单重复提交等情况),当记录在数据库中成功更新后使用 redirect_to

  • 当使用 render ,用户提交表单之后紧接着刷新页面,此时表单就会重复被提交,从而可能导致了脏数据产生。

    render_update.png

  • 当使用 redirect_to,用户提交表单,服务器接受到请求之后发出重定向,如果用户主动再次刷新页面,此时浏览器的请求链接是服务器重定向的URL 而不是重复提交表单。这也是所谓的Post/Redirect/Get(PRG)模式。
    redirect_to_update.png

看一个例子:

def create
  @product = Product.new(params[:product])
  respond_to do |format|
    if @product.save
      format.html { redirect_to(@product, :notice => 'Product was successfully created.') }

    else
      format.html { render :action => "new" }
    end
  end
end

所以,当涉及到表单提交时,数据库记录更新成功使用 redirect_to ,更新失败使用 render ,除了避免表单重复提交之外,如果表单提交失败,之前表单填写的信息还会存在,增强用户体验。

0 条评论
您想说点什么吗?