Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
68a2a671
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
68a2a671
编写于
2月 25, 2015
作者:
R
Rafael Mendonça França
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #18948 from kaspth/automatic-collection-caching
Merge multi_fetch_fragments.
上级
5a6868b6
ca6aba7f
变更
17
隐藏空白更改
内联
并排
Showing
17 changed file
with
305 addition
and
22 deletion
+305
-22
actionpack/test/controller/caching_test.rb
actionpack/test/controller/caching_test.rb
+58
-0
actionpack/test/fixtures/collection_cache/index.html.erb
actionpack/test/fixtures/collection_cache/index.html.erb
+1
-0
actionpack/test/fixtures/customers/_commented_customer.html.erb
...pack/test/fixtures/customers/_commented_customer.html.erb
+4
-0
actionpack/test/fixtures/customers/_customer.html.erb
actionpack/test/fixtures/customers/_customer.html.erb
+3
-0
actionview/lib/action_view/helpers/cache_helper.rb
actionview/lib/action_view/helpers/cache_helper.rb
+31
-0
actionview/lib/action_view/railtie.rb
actionview/lib/action_view/railtie.rb
+6
-0
actionview/lib/action_view/renderer/partial_renderer.rb
actionview/lib/action_view/renderer/partial_renderer.rb
+6
-2
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
...tion_view/renderer/partial_renderer/collection_caching.rb
+70
-0
actionview/lib/action_view/template.rb
actionview/lib/action_view/template.rb
+14
-0
actionview/lib/action_view/template/handlers/erb.rb
actionview/lib/action_view/template/handlers/erb.rb
+18
-0
actionview/test/actionpack/controller/render_test.rb
actionview/test/actionpack/controller/render_test.rb
+4
-0
actionview/test/fixtures/test/_cached_customer.erb
actionview/test/fixtures/test/_cached_customer.erb
+3
-0
actionview/test/fixtures/test/_cached_customer_as.erb
actionview/test/fixtures/test/_cached_customer_as.erb
+3
-0
actionview/test/template/render_test.rb
actionview/test/template/render_test.rb
+38
-0
activesupport/lib/active_support/cache.rb
activesupport/lib/active_support/cache.rb
+27
-13
activesupport/lib/active_support/cache/mem_cache_store.rb
activesupport/lib/active_support/cache/mem_cache_store.rb
+10
-7
activesupport/test/caching_test.rb
activesupport/test/caching_test.rb
+9
-0
未找到文件。
actionpack/test/controller/caching_test.rb
浏览文件 @
68a2a671
require
'fileutils'
require
'abstract_unit'
require
'lib/controller/fake_models'
CACHE_DIR
=
'test_cache'
# Don't change '/../temp/' cavalierly or you might hose something you don't want hosed
...
...
@@ -349,3 +350,60 @@ def test_view_cache_dependencies_are_listed_in_declaration_order
assert_equal
%w(trombone flute)
,
HasDependenciesController
.
new
.
view_cache_dependencies
end
end
class
CollectionCacheController
<
ActionController
::
Base
def
index
@customers
=
[
Customer
.
new
(
'david'
,
params
[
:id
]
||
1
)]
end
def
index_ordered
@customers
=
[
Customer
.
new
(
'david'
,
1
),
Customer
.
new
(
'david'
,
2
),
Customer
.
new
(
'david'
,
3
)]
render
'index'
end
def
index_explicit_render
@customers
=
[
Customer
.
new
(
'david'
,
1
)]
render
partial:
'customers/customer'
,
collection:
@customers
end
def
index_with_comment
@customers
=
[
Customer
.
new
(
'david'
,
1
)]
render
partial:
'customers/commented_customer'
,
collection:
@customers
,
as: :customer
end
end
class
AutomaticCollectionCacheTest
<
ActionController
::
TestCase
def
setup
super
@controller
=
CollectionCacheController
.
new
@controller
.
perform_caching
=
true
@controller
.
cache_store
=
ActiveSupport
::
Cache
::
MemoryStore
.
new
end
def
test_collection_fetches_cached_views
get
:index
ActionView
::
PartialRenderer
.
expects
(
:collection_with_template
).
never
get
:index
end
def
test_preserves_order_when_reading_from_cache_plus_rendering
get
:index
,
params:
{
id:
2
}
get
:index_ordered
assert_select
':root'
,
"david, 1
\n
david, 2
\n
david, 3"
end
def
test_explicit_render_call_with_options
get
:index_explicit_render
assert_select
':root'
,
"david, 1"
end
def
test_caching_works_with_beginning_comment
get
:index_with_comment
ActionView
::
PartialRenderer
.
expects
(
:collection_with_template
).
never
get
:index_with_comment
end
end
actionpack/test/fixtures/collection_cache/index.html.erb
0 → 100644
浏览文件 @
68a2a671
<%=
render
@customers
%>
\ No newline at end of file
actionpack/test/fixtures/customers/_commented_customer.html.erb
0 → 100644
浏览文件 @
68a2a671
<%# I'm a comment %>
<%
cache
customer
do
%>
<%=
customer
.
name
%>
,
<%=
customer
.
id
%>
<%
end
%>
\ No newline at end of file
actionpack/test/fixtures/customers/_customer.html.erb
0 → 100644
浏览文件 @
68a2a671
<%
cache
customer
do
%>
<%=
customer
.
name
%>
,
<%=
customer
.
id
%>
<%
end
%>
\ No newline at end of file
actionview/lib/action_view/helpers/cache_helper.rb
浏览文件 @
68a2a671
...
...
@@ -110,6 +110,29 @@ module CacheHelper
# <%= some_helper_method(person) %>
#
# Now all you'll have to do is change that timestamp when the helper method changes.
#
# === Automatic Collection Caching
#
# When rendering collections such as:
#
# <%= render @notifications %>
# <%= render partial: 'notifications/notification', collection: @notifications %>
#
# If the notifications/_notification partial starts with a cache call like so:
#
# <% cache notification do %>
# <%= notification.name %>
# <% end %>
#
# The collection can then automatically use any cached renders for that
# template by reading them at once instead of one by one.
#
# See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for more
# information on what cache calls make a template eligible for this collection caching.
#
# The automatic cache multi read can be turned off like so:
#
# <%= render @notifications, cache: false %>
def
cache
(
name
=
{},
options
=
nil
,
&
block
)
if
controller
.
perform_caching
safe_concat
(
fragment_for
(
cache_fragment_name
(
name
,
options
),
options
,
&
block
))
...
...
@@ -161,6 +184,14 @@ def cache_fragment_name(name = {}, options = nil)
end
end
# Given a key (as described in ActionController::Caching::Fragments.expire_fragment),
# returns a key suitable for use in reading, writing, or expiring a
# cached fragment. All keys are prefixed with <tt>views/</tt> and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def
fragment_cache_key
(
key
)
ActiveSupport
::
Cache
.
expand_cache_key
(
key
.
is_a?
(
Hash
)
?
url_for
(
key
).
split
(
"://"
).
last
:
key
,
:views
)
end
private
def
fragment_name_with_digest
(
name
)
#:nodoc:
...
...
actionview/lib/action_view/railtie.rb
浏览文件 @
68a2a671
...
...
@@ -36,6 +36,12 @@ class Railtie < Rails::Railtie # :nodoc:
end
end
initializer
"action_view.collection_caching"
do
|
app
|
ActiveSupport
.
on_load
(
:action_controller
)
do
PartialRenderer
.
collection_cache
=
app
.
config
.
action_controller
.
cache_store
end
end
initializer
"action_view.setup_action_pack"
do
|
app
|
ActiveSupport
.
on_load
(
:action_controller
)
do
ActionView
::
RoutingUrlFor
.
include
(
ActionDispatch
::
Routing
::
UrlFor
)
...
...
actionview/lib/action_view/renderer/partial_renderer.rb
浏览文件 @
68a2a671
require
'action_view/renderer/partial_renderer/collection_caching'
require
'thread_safe'
module
ActionView
...
...
@@ -280,6 +281,8 @@ def iterate! # :nodoc:
# <%- end -%>
# <% end %>
class
PartialRenderer
<
AbstractRenderer
include
CollectionCaching
PREFIXED_PARTIAL_NAMES
=
ThreadSafe
::
Cache
.
new
do
|
h
,
k
|
h
[
k
]
=
ThreadSafe
::
Cache
.
new
end
...
...
@@ -321,8 +324,9 @@ def render_collection
spacer
=
find_template
(
@options
[
:spacer_template
],
@locals
.
keys
).
render
(
@view
,
@locals
)
end
result
=
@template
?
collection_with_template
:
collection_without_template
result
.
join
(
spacer
).
html_safe
cache_collection_render
do
@template
?
collection_with_template
:
collection_without_template
end
.
join
(
spacer
).
html_safe
end
def
render_partial
...
...
actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
0 → 100644
浏览文件 @
68a2a671
require
'active_support/core_ext/object/try'
module
ActionView
module
CollectionCaching
# :nodoc:
extend
ActiveSupport
::
Concern
included
do
# Fallback cache store if Action View is used without Rails.
# Otherwise overriden in Railtie to use Rails.cache.
mattr_accessor
(
:collection_cache
)
{
ActiveSupport
::
Cache
::
MemoryStore
.
new
}
end
private
def
cache_collection_render
return
yield
unless
cache_collection?
keyed_collection
=
collection_by_cache_keys
partial_cache
=
collection_cache
.
read_multi
(
*
keyed_collection
.
keys
)
@collection
=
keyed_collection
.
reject
{
|
key
,
_
|
partial_cache
.
key?
(
key
)
}.
values
rendered_partials
=
@collection
.
any?
?
yield
.
dup
:
[]
fetch_or_cache_partial
(
partial_cache
,
order_by:
keyed_collection
.
each_key
)
do
rendered_partials
.
shift
end
end
def
cache_collection?
@options
.
fetch
(
:cache
,
automatic_cache_eligible?
)
end
def
automatic_cache_eligible?
single_template_render?
&&
!
callable_cache_key?
&&
@template
.
eligible_for_collection_caching?
(
as:
@options
[
:as
])
end
def
single_template_render?
@template
# Template is only set when a collection renders one template.
end
def
callable_cache_key?
@options
[
:cache
].
respond_to?
(
:call
)
end
def
collection_by_cache_keys
seed
=
callable_cache_key?
?
@options
[
:cache
]
:
->
(
i
)
{
i
}
@collection
.
each_with_object
({})
do
|
item
,
hash
|
hash
[
expanded_cache_key
(
seed
.
call
(
item
))]
=
item
end
end
def
expanded_cache_key
(
key
)
key
=
@view
.
fragment_cache_key
(
@view
.
cache_fragment_name
(
key
))
key
.
frozen?
?
key
.
dup
:
key
# #read_multi & #write may require mutability, Dalli 2.6.0.
end
def
fetch_or_cache_partial
(
cached_partials
,
order_by
:)
cache_options
=
@options
[
:cache_options
]
||
@locals
[
:cache_options
]
||
{}
order_by
.
map
do
|
key
|
cached_partials
.
fetch
(
key
)
do
yield
.
tap
do
|
rendered_partial
|
collection_cache
.
write
(
key
,
rendered_partial
,
cache_options
)
end
end
end
end
end
end
actionview/lib/action_view/template.rb
浏览文件 @
68a2a671
...
...
@@ -130,6 +130,7 @@ def initialize(source, identifier, handler, details)
@source
=
source
@identifier
=
identifier
@handler
=
handler
@cache_name
=
extract_resource_cache_call_name
@compiled
=
false
@original_encoding
=
nil
@locals
=
details
[
:locals
]
||
[]
...
...
@@ -165,6 +166,10 @@ def type
@type
||=
Types
[
@formats
.
first
]
if
@formats
.
first
end
def
eligible_for_collection_caching?
(
as:
nil
)
@cache_name
==
(
as
||
inferred_cache_name
).
to_s
end
# Receives a view object and return a template similar to self by using @virtual_path.
#
# This method is useful if you have a template object but it does not contain its source
...
...
@@ -345,5 +350,14 @@ def instrument(action, &block)
payload
=
{
virtual_path:
@virtual_path
,
identifier:
@identifier
}
ActiveSupport
::
Notifications
.
instrument
(
"
#{
action
}
.action_view"
,
payload
,
&
block
)
end
def
extract_resource_cache_call_name
$1
if
@handler
.
respond_to?
(
:resource_cache_call_pattern
)
&&
@source
=~
@handler
.
resource_cache_call_pattern
end
def
inferred_cache_name
@inferred_cache_name
||=
@virtual_path
.
split
(
'/'
).
last
.
sub
(
'_'
,
''
)
end
end
end
actionview/lib/action_view/template/handlers/erb.rb
浏览文件 @
68a2a671
...
...
@@ -123,6 +123,24 @@ def call(template)
).
src
end
# Returns Regexp to extract a cached resource's name from a cache call at the
# first line of a template.
# The extracted cache name is expected in $1.
#
# <% cache notification do %> # => notification
#
# The pattern should support templates with a beginning comment:
#
# <%# Still extractable even though there's a comment %>
# <% cache notification do %> # => notification
#
# But fail to extract a name if a resource association is cached.
#
# <% cache notification.event do %> # => nil
def
resource_cache_call_pattern
/\A(?:<%#.*%>\n?)?<% cache\(?\s*(\w+\.?)/
end
private
def
valid_encoding
(
string
,
encoding
)
...
...
actionview/test/actionpack/controller/render_test.rb
浏览文件 @
68a2a671
...
...
@@ -31,6 +31,10 @@ def errors
def
persisted?
id
.
present?
end
def
cache_key
name
.
to_s
end
end
module
Quiz
...
...
actionview/test/fixtures/test/_cached_customer.erb
0 → 100644
浏览文件 @
68a2a671
<%
cache
cached_customer
do
%>
Hello:
<%=
cached_customer
.
name
%>
<%
end
%>
\ No newline at end of file
actionview/test/fixtures/test/_cached_customer_as.erb
0 → 100644
浏览文件 @
68a2a671
<%
cache
buyer
do
%>
<%=
greeting
%>
:
<%=
customer
.
name
%>
<%
end
%>
\ No newline at end of file
actionview/test/template/render_test.rb
浏览文件 @
68a2a671
...
...
@@ -598,3 +598,41 @@ def with_external_encoding(encoding)
silence_warnings
{
Encoding
.
default_external
=
old
}
end
end
class
CachedCollectionViewRenderTest
<
CachedViewRenderTest
class
CachedCustomer
<
Customer
;
end
teardown
do
ActionView
::
PartialRenderer
.
collection_cache
.
clear
end
test
"with custom key"
do
customer
=
Customer
.
new
(
"david"
)
key
=
ActionController
::
Base
.
new
.
fragment_cache_key
([
customer
,
'key'
])
ActionView
::
PartialRenderer
.
collection_cache
.
write
(
key
,
'Hello'
)
assert_equal
"Hello"
,
@view
.
render
(
partial:
"test/customer"
,
collection:
[
customer
],
cache:
->
(
item
)
{
[
item
,
'key'
]
})
end
test
"automatic caching with inferred cache name"
do
customer
=
CachedCustomer
.
new
(
"david"
)
key
=
ActionController
::
Base
.
new
.
fragment_cache_key
(
customer
)
ActionView
::
PartialRenderer
.
collection_cache
.
write
(
key
,
'Cached'
)
assert_equal
"Cached"
,
@view
.
render
(
partial:
"test/cached_customer"
,
collection:
[
customer
])
end
test
"automatic caching with as name"
do
customer
=
CachedCustomer
.
new
(
"david"
)
key
=
ActionController
::
Base
.
new
.
fragment_cache_key
(
customer
)
ActionView
::
PartialRenderer
.
collection_cache
.
write
(
key
,
'Cached'
)
assert_equal
"Cached"
,
@view
.
render
(
partial:
"test/cached_customer_as"
,
collection:
[
customer
],
as: :buyer
)
end
end
activesupport/lib/active_support/cache.rb
浏览文件 @
68a2a671
...
...
@@ -325,19 +325,22 @@ def read(name, options = nil)
def
read_multi
(
*
names
)
options
=
names
.
extract_options!
options
=
merged_options
(
options
)
results
=
{}
names
.
each
do
|
name
|
key
=
namespaced_key
(
name
,
options
)
entry
=
read_entry
(
key
,
options
)
if
entry
if
entry
.
expired?
delete_entry
(
key
,
options
)
else
results
[
name
]
=
entry
.
value
instrument_multi
(
:read
,
names
,
options
)
do
|
payload
|
results
=
{}
names
.
each
do
|
name
|
key
=
namespaced_key
(
name
,
options
)
entry
=
read_entry
(
key
,
options
)
if
entry
if
entry
.
expired?
delete_entry
(
key
,
options
)
else
results
[
name
]
=
entry
.
value
end
end
end
results
end
results
end
# Fetches data from the cache, using the given keys. If there is data in
...
...
@@ -527,16 +530,27 @@ def namespaced_key(key, options)
end
def
instrument
(
operation
,
key
,
options
=
nil
)
log
(
operation
,
key
,
options
)
log
{
"Cache
#{
operation
}
:
#{
key
}#{
options
.
blank?
?
""
:
" (
#{
options
.
inspect
}
)"
}
"
}
payload
=
{
:key
=>
key
}
payload
.
merge!
(
options
)
if
options
.
is_a?
(
Hash
)
ActiveSupport
::
Notifications
.
instrument
(
"cache_
#{
operation
}
.active_support"
,
payload
){
yield
(
payload
)
}
end
def
log
(
operation
,
key
,
options
=
nil
)
def
instrument_multi
(
operation
,
keys
,
options
=
nil
)
log
do
formatted_keys
=
keys
.
map
{
|
k
|
"-
#{
k
}
"
}.
join
(
"
\n
"
)
"Caches multi
#{
operation
}
:
\n
#{
formatted_keys
}#{
options
.
blank?
?
""
:
" (
#{
options
.
inspect
}
)"
}
"
end
payload
=
{
key:
keys
}
payload
.
merge!
(
options
)
if
options
.
is_a?
(
Hash
)
ActiveSupport
::
Notifications
.
instrument
(
"cache_
#{
operation
}
_multi.active_support"
,
payload
)
{
yield
(
payload
)
}
end
def
log
return
unless
logger
&&
logger
.
debug?
&&
!
silence?
logger
.
debug
(
"Cache
#{
operation
}
:
#{
key
}#{
options
.
blank?
?
""
:
" (
#{
options
.
inspect
}
)"
}
"
)
logger
.
debug
(
yield
)
end
def
find_cached_entry
(
key
,
name
,
options
)
...
...
activesupport/lib/active_support/cache/mem_cache_store.rb
浏览文件 @
68a2a671
...
...
@@ -66,14 +66,17 @@ def initialize(*addresses)
def
read_multi
(
*
names
)
options
=
names
.
extract_options!
options
=
merged_options
(
options
)
keys_to_names
=
Hash
[
names
.
map
{
|
name
|
[
escape_key
(
namespaced_key
(
name
,
options
)),
name
]}]
raw_values
=
@data
.
get_multi
(
keys_to_names
.
keys
,
:raw
=>
true
)
values
=
{}
raw_values
.
each
do
|
key
,
value
|
entry
=
deserialize_entry
(
value
)
values
[
keys_to_names
[
key
]]
=
entry
.
value
unless
entry
.
expired?
instrument_multi
(
:read
,
names
,
options
)
do
keys_to_names
=
Hash
[
names
.
map
{
|
name
|
[
escape_key
(
namespaced_key
(
name
,
options
)),
name
]}]
raw_values
=
@data
.
get_multi
(
keys_to_names
.
keys
,
:raw
=>
true
)
values
=
{}
raw_values
.
each
do
|
key
,
value
|
entry
=
deserialize_entry
(
value
)
values
[
keys_to_names
[
key
]]
=
entry
.
value
unless
entry
.
expired?
end
values
end
values
end
# Increment a cached value. This method uses the memcached incr atomic
...
...
activesupport/test/caching_test.rb
浏览文件 @
68a2a671
...
...
@@ -1021,6 +1021,15 @@ def test_mute_logging
@cache
.
mute
{
@cache
.
fetch
(
'foo'
)
{
'bar'
}
}
assert
@buffer
.
string
.
blank?
end
def
test_multi_read_loggin
@cache
.
write
'hello'
,
'goodbye'
@cache
.
write
'world'
,
'earth'
@cache
.
read_multi
(
'hello'
,
'world'
)
assert_match
"Caches multi read:
\n
- hello
\n
- world"
,
@buffer
.
string
.
tap
{
|
l
|
p
l
}
end
end
class
CacheEntryTest
<
ActiveSupport
::
TestCase
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录