homeASCIIcasts

169: 动态页面缓存 

(view original Railscast)

Other translations: En It

Other formats:

Written by Calf

这次我们将要处理的是一个钢琴演奏爱好者的论坛程序。下面是它正在载入的主页。

The home page of our site takes a few seconds to load.

这个网页的加载花费了很长的时间,因为它很复杂。事实上,在加载网页的同时,通过设置两秒钟的睡眠模拟延迟。我们假设这个延迟是真实存在的,但是我们想让网页尽可能快地加载完毕。我们将尽可能地优化网页的性能来缩短载入时间。

缓存页面

在rails程序中,提高网页性能的最好方法是将它缓存起来(页面缓存在98节 中已经有提及)。让我们转到页面缓存来看看我们将得到说明结果。

在开发环境下,缓存是默认关闭的,所以我们必打开文件 /config/environments/development.rb 把里面的 config.action_controller.perform_caching设置为true。这样我们就能为 forums controller 中的 index action 添加缓存功能了。

class ForumsController < ApplicationController  

  before_filter :admin_required, :except => [:index, :show]  
  caches_page :index  

  def index  
    @forums = Forum.all  
    sleep 2  
  end  

  # other methods omitted.  

end  

为了使刚才的设置生效,我们还必须重启服务器。重新加载我们的主页面。第一次它将花费几秒时间,但是当我们再次刷新是它几乎是立即显示,因为此时是从缓存中提取出来的。

缓存页面时在文件夹/public中生成静态的HTML文件。当我们对rails程序提交request请求时,服务器就从之前的静态文件中查找对应的文件。例如,服务器将为这个页面查找/public/episodes/169-dynamic-page-caching.html。如果服务器找到该文件,它将把该文件返回给浏览器,而rails不需要处理此次request请求。但是如果找不到文件,服务器将把请求交给rails程序处理。如果一个action请求已经打开了缓存,Rails在处理了该请求之后将把生成的静态HTML文件写到/public目录下相应的地方,这样在下一个相同的请求到来时服务器就可以直接返回给浏览器。(另外,这也是为什么你在创建一个新的Rails程序的时候要将静态的index.html页面删除。它的行为就像一个被缓存的文件一样,会阻止一个原始的request请求)

缓存后产生的页面没有任何动态的内容,这将产生一个问题,例如有一个复杂的页面,它的根据当前已登陆用户显示不同的内容,如果我们把整个页面都缓存起来,那么无论谁来访问,它都把当前已登陆的用户当成eifion.

<div id="user_nav">  
  <p>Welcome <strong>eifion</strong>! Not you? <a href="/logout">Logout</a></p>  
</div>  

即使点击“log out”链接,我们仍然被显示成已经登陆的用户。因为我们退出后,”logout”页面重定向回主页面,而服务器直接把缓存中的静态主页面返回给我们。

这个页面看上去有太多动态内容,以至于我们不能使用页面缓存(page caching)。下面将用片段缓存(fragment caching)来缓存显示论坛列表的内容,但是即使是在这里仍然有“edit”和“destroy”这两个动态的链接,它们只对管理员用户可见。

在页面缓存中使用Javascript

我们可以把动态内容从页面移除,用Page caching把基本的页面缓存起来,然后在通过JavaScript把动态内容添加上去。

开始之前,我们把forums controller的缓存功能和睡眠功能暂时关闭。同时还要把/public/index.html文件删除,否则我们所做的改变将产生不了效果。

rm public/index.html

在页面的最开始,我们为管理员用户提供编辑(edit)和删除(delete)论坛的链接,在页面的底部提供新建论坛(“new forum”)的链接。下面的代码由两部分组成,在if语句中的代码只对管理员可见。

<% title "Piano Forums" %>  

<div id="forums">  
  <% for forum in @forums %>  
    <div class="forum">  
      <h2><%= link_to h(forum.name), forum %></h2>  
      <p><%= h forum.description %></p>  
      <% if admin? %>  
        <p class="admin">  
          <%= link_to "Edit", edit_forum_path(forum) %> |  
          <%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %>  
        </p>  
      <% end %>  
    </div>  
  <% end %>  
</div>  

<% if admin? %>  
  <p class="admin"><%= link_to "New Forum", new_forum_path %></p>  
<% end %>  

我们删除if语句然后用CSS把链接隐藏起来。

<p class="admin" style="display:none;">  
  <%= link_to "Edit", edit_forum_path(forum) %> |  
  <%= link_to "Destroy", forum, :confirm => 'Are you sure?', :method => :delete %>  
</p>  
<p class="admin" style="display:none;"><%= link_to "New Forum", new_forum_path %></p> 
 

如果我们现在重新载入页面,管理链接仍然在页面上,但已经被隐藏起来了。

The edit, destroy and new links are now hidden.

现在,如果当前用户是管理员,我们就使用JavaScript来使以上管理链接重新显示出来。在视图的顶部,我们使用一个helper方法把两个JavaScript文件引入到页面的HEAD区域中。(这个helper方法是Ryan Bates nifty layout generator的一部分。我们引用的第一个JavaScript文件是jQuery library,你也可以用Prototype如果你更倾向于Prototype;第二个是UsersController的一个action)

<% javascript 'jquery', '/users/current' %>

以上代码生成如下HTML

<script src="/javascripts/jquery.js" type="text/javascript"></script>  
<script src="/users/current.js" type="text/javascript"></script>   

第一行代码载入jQuery库文件,我们必须先下载它然后把它放到/public/javascripts目录中。第二行将调用users controller的show action,现在它传递一个假的id。为了使这个show action能够响应JavaScript的请求,我们还必须先修改它。 这个controller需要有一个show method,但是这里可以像下面这样写。

def show  

end  

下一步我们需要一个新的视图文件,这样controller就可以响应JavaScript请求。在目录/app/views/users中,创建一个名为show.js.erb的文件,在里面写下如下代码

$(document).ready(function () {  
  <% if admin? %>  
  $('.admin').show();  
  <% end %>  
});  

当文件对象模型加载完毕后,代码使用jQuery的$(document).ready()方法来运行JavaScript代码。如果用户不是管理员,相应的代码将为空,但是,如果是管理员,页面上所有元素将显示出来。

更新状态

页面中显示用户状态的部分也要动态地生成,当用户登录或者退出时相应的提示信息也要更新。这两部分的代码都被放入当了application的layout文件中。

<%- flash.each do |name, msg| -%>  
  <%= content_tag :div, msg, :id => "flash_#{name}" %>  
<%- end -%>  
<div id="user_status">  
  <% if current_user %>  
    <p>Welcome <strong><%= current_user.username %></strong>! Not you? <%= link_to "Logout", logout_path %></p>  
  <% else %>  
    <%= link_to "Register", new_user_path %>  
    <%= link_to "Log in", login_path %>  
  <% end %>  
</div>  

如果这部分代码放在一个partial文件中,使用JavaScript更新它们会变得更容易,所有我们把上面的代码放入/app/views/layouts/dynamic_header.html.erb中,同时在原文件中写下下面的代码。

<%= render 'layouts/dynamic_header' %>  

我们需要做的是把partial中的内容从已经缓存的静态主页文件中移除,然后用JavaScript再把它们添加回去。所以我们需要传递信息给partial让它不要再在主页中显示。

在文件/app/views/forums/index.html.erb的顶部,我们需要增加一个变量@hide_dynamic并且把它赋值为true.

<% title "Piano Forums" %>  
<% javascript 'jquery', '/users/current' %>  
<% @hide_dynamic = true %>  
<div id="forums">  
<!-- rest of page... -->  
 

@hide_dynamictruelayout dynamic_header就不会被引用。

<%= render :partial => 'layouts/dynamic_header' unless @hide_dynamic %>  

如果现在刷新页面,我们会看到user的状态而且logout链接也消失了。我们修改我们JavaScript代码,这样就可以把它们重新添加上去

The users status no longer appears.

我们需要修改我们的jQuery方法,之后它用partial来显示用户的当前状态和其他新的信息。我们仅仅增加一行jQuery代码就可以达到目的。

$(document).ready(function () {  
  $('#container').prepend('<%= escape_javascript render("layouts/dynamic_header") %>');  
  <% if admin? %>  
  $('.admin').show();  
  <% end %>  
});  

我们想要给id为container的div添加这个partial作为第一个子元素,所以使用jQuery的prepend()方法。这个方法以HTML字符串作为参数,所以我们可以传递这个partial作为参数,包裹在escape_javascript中来保证partial中的内容不会产生JavaScript错误。

现在我们重新载入页面时用户状态重新出现,页面显示的内容和真实用户登录状态将保持一致。

Re-enabling Caching

既然我们有了JavaScript动态更新的页面部分,现在我们可以加入页面缓存功能和睡眠功能来模仿页面的复杂性。

当我们再次刷新页面,第一次同样花费几秒钟的时间,但是接下的刷新页面几乎是直接显示,因为页面是从缓存中提取出来的。与之前不同的是我们可以动态的看到我们登录或者退出的状态,而且只提供给管理员的链接是隐藏起来的,和我们期待的一样。

The dynamic parts of the page are now set by the JavaScript.

这个方法不会像页面缓存(full-page caching)那样高效,因为JavaScript部分仍然会到达Rails程序。但是对于复杂的文件你可以看到性能的提升,因为大部分文件是作为静态的HTML提供的。