跳至内容 跳至搜索

Action View 缓存助手

命名空间
方法
C
U

实例公共方法

cache(name = {}, options = {}, &block)

此助手提供了一种方法来缓存视图片段,而不是整个操作或页面。此技术对于缓存菜单、新主题列表、静态 HTML 片段等内容非常有用。此方法接受一个包含要缓存内容的块。

最佳做法是在 Memcached 或 Redis 等缓存存储之上使用可回收的基于键的缓存过期,这些存储会自动剔除旧条目。

使用此方法时,将缓存依赖项列为缓存名称,如下所示

<% cache project do %>
  <b>All the topics on this project</b>
  <%= render project.topics %>
<% end %>

此方法将假设在添加新主题时,您将触碰项目。从此调用生成的缓存键将类似于

views/template/action:7a1156131a6928cb0026877f8b749ac9/projects/123
      ^template path  ^template tree digest            ^class   ^id

此缓存键是稳定的,但它与从项目记录派生的缓存版本相结合。当项目 updated_at 被触碰时,即使键保持稳定,缓存版本也会发生变化。这意味着与传统的基于键的缓存过期方法不同,您不会生成缓存垃圾,即未使用的键,仅仅因为依赖记录已更新。

如果您的模板缓存依赖于多个来源(尽量避免这种情况以保持简单),您可以将所有这些依赖项命名为数组的一部分

<% cache [ project, current_user ] do %>
  <b>All the topics on this project</b>
  <%= render project.topics %>
<% end %>

这将包括这两个记录作为缓存键的一部分,更新其中任何一个都会使缓存过期。

模板摘要

添加到缓存键的模板摘要是通过对整个模板文件内容进行 MD5 计算得出的。这确保了当您更改模板文件时,您的缓存会自动过期。

请注意,MD5 是针对整个模板文件生成的,而不仅仅是缓存 do/end 调用中的内容。因此,更改该调用之外的内容也可能会使缓存失效。

此外,摘要器会自动在您的模板文件中查找显式和隐式依赖项,并将它们包含在摘要中。

可以通过将 skip_digest: true 作为选项传递给缓存调用来绕过摘要器。

<% cache project, skip_digest: true do %>
  <b>All the topics on this project</b>
  <%= render project.topics %>
<% end %>

隐式依赖项

大多数模板依赖项可以从模板本身对 render 的调用中推断出来。以下是一些 Cache Digests 知道如何解码的 render 调用示例。

render partial: "comments/comment", collection: commentable.comments
render "comments/comments"
render 'comments/comments'
render('comments/comments')

render "header" translates to render("comments/header")

render(@topic)         translates to render("topics/topic")
render(topics)         translates to render("topics/topic")
render(message.topics) translates to render("topics/topic")

但是,无法推断出所有 render 调用。以下是一些无法推断出的示例。

render group_of_attachments
render @project.documents.where(published: true).order('created_at')

您需要将它们重写为显式形式。

render partial: 'attachments/attachment', collection: group_of_attachments
render partial: 'documents/document', collection: @project.documents.where(published: true).order('created_at')

显式依赖项

有时您会遇到无法推断的模板依赖项。这通常发生在辅助程序中进行模板渲染时。以下是一个示例。

<%= render_sortable_todolists @project.todolists %>

您需要使用特殊的注释格式来调用它们。

<%# Template Dependency: todolists/todolist %>
<%= render_sortable_todolists @project.todolists %>

在某些情况下,例如单表继承设置,您可能有一堆显式依赖项。与其编写每个模板,不如使用通配符来匹配目录中的任何模板。

<%# Template Dependency: events/* %>
<%= render_categorizable_events @person.events %>

这将标记目录中的每个模板为依赖项。要查找这些模板,通配符路径必须从 app/views 或使用 prepend_view_pathappend_view_path 添加的其他路径绝对定义。这样,app/views/recordings/events 的通配符将是 recordings/events/* 等。

用于匹配显式依赖项的模式是 /# Template Dependency: (\S+)/,因此您必须按原样输入。每行只能声明一个模板依赖项。

外部依赖项

如果您在缓存块中使用辅助方法,例如,然后更新该辅助方法,您也需要更新缓存。具体方法并不重要,但模板文件的 MD5 必须更改。建议您在注释中明确说明,例如

<%# Helper Dependency Updated: May 6, 2012 at 6pm %>
<%= some_helper_method(person) %>

现在您只需要在辅助方法更改时更改该时间戳。

集合缓存

渲染一组对象时,每个对象都使用相同的局部视图,可以传递 :cached 选项。

对于这样渲染的集合

<%= render partial: 'projects/project', collection: @projects, cached: true %>

cached: true 将使 Action View 的渲染一次从缓存中读取多个模板,而不是每个模板调用一次。

集合中尚未缓存的模板将写入缓存。

与单个模板片段缓存配合使用效果很好。例如,如果集合渲染的模板被缓存,例如

# projects/_project.html.erb
<% cache project do %>
  <%# ... %>
<% end %>

任何集合渲染在尝试一次读取多个模板时都会找到这些缓存的模板。

如果您的集合缓存依赖于多个来源(尽量避免这种情况以保持简单),您可以将所有这些依赖项命名为返回数组的块的一部分

<%= render partial: 'projects/project', collection: @projects, cached: -> project { [ project, current_user ] } %>

这将包括这两个记录作为缓存键的一部分,更新其中任何一个都会使缓存过期。

# File actionview/lib/action_view/helpers/cache_helper.rb, line 168
def cache(name = {}, options = {}, &block)
  if controller.respond_to?(:perform_caching) && controller.perform_caching
    CachingRegistry.track_caching do
      name_options = options.slice(:skip_digest)
      safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
    end
  else
    yield
  end

  nil
end

cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)

此辅助方法返回给定片段缓存调用的缓存键的名称。通过向缓存提供 skip_digest: true,可以手动绕过缓存片段的摘要。当使用 memcached 时,这在无法手动过期缓存片段的情况下非常有用,因为您需要知道确切的键。

# File actionview/lib/action_view/helpers/cache_helper.rb, line 240
def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
  if skip_digest
    name
  else
    fragment_name_with_digest(name, digest_path)
  end
end

cache_if(condition, name = {}, options = {}, &block)

如果condition为真,则缓存视图片段

<% cache_if admin?, project do %>
  <b>All the topics on this project</b>
  <%= render project.topics %>
<% end %>
# File actionview/lib/action_view/helpers/cache_helper.rb, line 215
def cache_if(condition, name = {}, options = {}, &block)
  if condition
    cache(name, options, &block)
  else
    yield
  end

  nil
end

cache_unless(condition, name = {}, options = {}, &block)

除非condition为真,否则缓存视图片段

<% cache_unless admin?, project do %>
  <b>All the topics on this project</b>
  <%= render project.topics %>
<% end %>
# File actionview/lib/action_view/helpers/cache_helper.rb, line 231
def cache_unless(condition, name = {}, options = {}, &block)
  cache_if !condition, name, options, &block
end

caching?()

返回当前视图片段是否在cache块内。

当某些片段不可缓存时很有用

<% cache project do %>
  <% raise StandardError, "Caching private data!" if caching? %>
<% end %>
# File actionview/lib/action_view/helpers/cache_helper.rb, line 188
def caching?
  CachingRegistry.caching?
end

uncacheable!()

cache块中调用时,会引发UncacheableFragmentError

用于表示不能参与片段缓存的辅助方法

def project_name_with_time(project)
  uncacheable!
  "#{project.name} - #{Time.now}"
end

# Which will then raise if used within a +cache+ block:
<% cache project do %>
  <%= project_name_with_time(project) %>
<% end %>
# File actionview/lib/action_view/helpers/cache_helper.rb, line 205
def uncacheable!
  raise UncacheableFragmentError, "can't be fragment cached" if caching?
end