Rails render 和 redirect_to 进阶理解
基本用法
不同的跳转姿势
Rails 新手开发者会简单地认为 redirect_to
和 goto
指令一样,单纯地跳转执行 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 状态码到浏览器并重定向,客户端(用户浏览器)将发送一个新的请求到服务器,index
action 将被执行。
上面的代码片段有不够优雅,那就是浏览器会重定向,服务器接受了两次从客户端发过来的请求。使用 render 和 redirect_to 时客户端与服务器交互如下图所示:
当请求 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 执行
控制器中的代码执行到 render
和redirect_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 ,用户提交表单之后紧接着刷新页面,此时表单就会重复被提交,从而可能导致了脏数据产生。
当使用 redirect_to,用户提交表单,服务器接受到请求之后发出重定向,如果用户主动再次刷新页面,此时浏览器的请求链接是服务器重定向的URL 而不是重复提交表单。这也是所谓的Post/Redirect/Get(PRG)模式。
看一个例子:
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 ,除了避免表单重复提交之外,如果表单提交失败,之前表单填写的信息还会存在,增强用户体验。