Action View 缓存助手
实例公有方法
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
此缓存键是稳定的,但它与从项目记录派生的缓存版本相结合。当 project updated_at 被触碰时,cache_version 会发生变化,即使键保持稳定。这意味着,与传统的基于键的缓存过期方法不同,您不会生成缓存垃圾,未使用的键,仅仅因为依赖记录被更新了。
如果您的模板缓存依赖于多个源(尝试避免这种情况以保持简单),您可以将所有这些依赖项命名为数组的一部分
<% cache [ project, current_user ] do %>
<b>All the topics on this project</b>
<%= render project.topics %>
<% end %>
这将包括两个记录作为缓存键的一部分,更新其中任何一个将使缓存过期。
模板摘要
添加到缓存键的模板摘要是通过对整个模板文件的内容进行 MD5 计算得到的。这确保了当您更改模板文件时,您的缓存将自动过期。
请注意,MD5 是对整个模板文件进行的,而不仅仅是 cache 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 "maintenance_tasks/runs/info/#{run.status}"
因为传递给 render 的值以插值结束,Action View 将标记“maintenance_tasks/runs/info”文件夹中的所有部分作为依赖项。
显式依赖项
有时您会有一些根本无法推断出的模板依赖项。这通常发生在您在助手中有模板渲染的情况下。这是一个例子
<%= 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_path
或 append_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 ] } %>
这将包括两个记录作为缓存键的一部分,更新其中任何一个将使缓存过期。
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 176 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 时一样。
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 248 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 %>
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 223 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 %>
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 239 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 %>
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 196 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 %>
来源: 显示 | 在 GitHub 上
# File actionview/lib/action_view/helpers/cache_helper.rb, line 213 def uncacheable! raise UncacheableFragmentError, "can't be fragment cached" if caching? end