Rails render 和 redirect_to 进阶理解

作者: 老码农不上班 | 来源:发表于2016-07-17 12:23 被阅读2789次

    该篇文章在个人博客中也可查阅: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 ,除了避免表单重复提交之外,如果表单提交失败,之前表单填写的信息还会存在,增强用户体验。

    相关文章

      网友评论

      • auguszou:也可以使用ajax。不会导致重复提交
      • bookinstock_:谢谢分享,写得详细,图文并茂,很有帮助。简单来看,从 **render** 和 **redirect** 这两个词就可看出,render 是用其他 action 的视图层来渲染当前 action,所以不会发送新的请求;而 redirect_to 是重新发送请求,所以 render 的效率更高。
        哈尔湖:@咕噜咕噜撒 解释的厉害

      本文标题:Rails render 和 redirect_to 进阶理解

      本文链接:https://www.haomeiwen.com/subject/zwoujttx.html