Skip to content

Commit 8994f09

Browse files
authored
Merge pull request rails#44616 from ghiculescu/preload-all-if-proc
Always preload if using proc with multifetch cache
2 parents 82bab92 + 3f1f4c7 commit 8994f09

File tree

5 files changed

+100
-21
lines changed

5 files changed

+100
-21
lines changed

actionview/lib/action_view/renderer/collection_renderer.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ def size
5151
def length
5252
@collection.respond_to?(:length) ? @collection.length : size
5353
end
54+
55+
def preload!
56+
# no-op
57+
end
5458
end
5559

5660
class SameCollectionIterator < CollectionIterator # :nodoc:
@@ -84,9 +88,13 @@ def from_collection(collection)
8488

8589
def each_with_info
8690
return super unless block_given?
87-
@relation.preload_associations(@collection)
91+
preload!
8892
super
8993
end
94+
95+
def preload!
96+
@relation.preload_associations(@collection)
97+
end
9098
end
9199

92100
class MixedCollectionIterator < CollectionIterator # :nodoc:

actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def collection_by_cache_keys(view, template, collection)
5959
seed = callable_cache_key? ? @options[:cached] : ->(i) { i }
6060

6161
digest_path = view.digest_path_from_template(template)
62+
collection.preload! if callable_cache_key?
6263

6364
collection.each_with_object([{}, []]) do |item, (hash, ordered_keys)|
6465
key = expanded_cache_key(seed.call(item), view, template, digest_path)

actionview/test/active_record_unit.rb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,32 @@ def self.fixtures(*args)
105105
def run(*args)
106106
super if ActiveRecordTestConnector.connected
107107
end
108+
109+
def capture_sql
110+
ActiveRecord::Base.connection.materialize_transactions
111+
SQLCounter.clear_log
112+
yield
113+
SQLCounter.log.dup
114+
end
115+
116+
class SQLCounter
117+
class << self
118+
attr_accessor :log, :log_all
119+
def clear_log; self.log = []; self.log_all = []; end
120+
end
121+
122+
clear_log
123+
124+
def call(name, start, finish, message_id, values)
125+
return if values[:cached]
126+
127+
sql = values[:sql]
128+
self.class.log_all << sql
129+
self.class.log << sql unless ["SCHEMA", "TRANSACTION"].include? values[:name]
130+
end
131+
end
132+
133+
ActiveSupport::Notifications.subscribe("sql.active_record", SQLCounter.new)
108134
end
109135

110136
ActiveRecordTestConnector.setup

actionview/test/activerecord/multifetch_cache_test.rb

Lines changed: 63 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,79 @@
22

33
require "active_record_unit"
44

5+
class MultifetchController < ActionController::Base
6+
ROUTES = test_routes do
7+
get :cached_true, to: "multifetch#cached_true"
8+
get :cached_proc, to: "multifetch#cached_proc"
9+
end
10+
11+
def cached_true
12+
load_all_topics
13+
render partial: "test/multifetch", collection: @topics, as: :topic, cached: true
14+
end
15+
16+
def cached_proc
17+
load_all_topics
18+
render partial: "test/multifetch", collection: @topics, as: :topic, cached: proc { |topic| [topic] }
19+
end
20+
21+
private
22+
def load_all_topics
23+
@topics = Topic.preload(:replies)
24+
end
25+
end
26+
527
class MultifetchCacheTest < ActiveRecordTestCase
28+
tests MultifetchController
629
fixtures :topics, :replies
730

8-
def setup
9-
view_paths = ActionController::Base.view_paths
10-
view_paths.each(&:clear_cache)
31+
setup do
32+
Topic.update_all(updated_at: Time.now)
33+
end
34+
35+
def test_only_preloading_for_records_that_miss_the_cache
36+
Topic.update_all(title: "title")
1137

12-
@view = Class.new(ActionView::Base.with_empty_template_cache) do
13-
def view_cache_dependencies
14-
[]
15-
end
38+
# The first request will not have anything cached, so we need to render all the records
39+
first_req = capture_sql do
40+
get :cached_true
41+
end
42+
assert_equal 3, @response.body.scan(/title/).length
43+
assert_equal 2, first_req.length
44+
assert_includes first_req.last, %(WHERE "replies"."topic_id" IN (?, ?, ?))
1645

17-
def combined_fragment_cache_key(key)
18-
[ :views, key ]
19-
end
20-
end.with_view_paths(view_paths, {})
46+
Topic.first.update(updated_at: 1.hour.ago) # reset the cache key for this object
2147

22-
controller = ActionController::Base.new
23-
controller.perform_caching = true
24-
@view.controller = controller
48+
# Now we only load the one record that we don't have a cached view for.
49+
second_req = capture_sql do
50+
get :cached_true
51+
end
52+
assert_equal 3, @response.body.scan(/title/).length
53+
assert_equal 2, second_req.length
54+
assert_equal first_req.first, second_req.first
55+
assert_includes second_req.last, %(WHERE "replies"."topic_id" = ?)
2556
end
2657

27-
def test_only_preloading_for_records_that_miss_the_cache
28-
@view.render partial: "test/partial", collection: [topics(:rails)], cached: true
58+
def test_preloads_all_records_if_using_cached_proc
59+
Topic.update_all(title: "title")
2960

30-
@topics = Topic.preload(:replies)
61+
# The first request will not have anything cached, so we need to render all the records
62+
first_req = capture_sql do
63+
get :cached_proc
64+
end
65+
assert_equal 3, @response.body.scan(/title/).length
66+
assert_equal 2, first_req.length
67+
assert_includes first_req.last, %(WHERE "replies"."topic_id" IN (?, ?, ?))
3168

32-
@view.render partial: "test/partial", collection: @topics, cached: true
69+
Topic.first.update(updated_at: 1.hour.ago) # reset the cache key for this object
3370

34-
assert_not @topics.detect { |topic| topic.id == topics(:rails).id }.replies.loaded?
35-
assert @topics.detect { |topic| topic.id != topics(:rails).id }.replies.loaded?
71+
# Since we are using a proc, we will preload the entire association.
72+
second_req = capture_sql do
73+
get :cached_proc
74+
end
75+
assert_equal 3, @response.body.scan(/title/).length
76+
assert_equal 2, second_req.length
77+
assert_equal first_req.first, second_req.first
78+
assert_includes second_req.last, %(WHERE "replies"."topic_id" IN (?, ?, ?))
3679
end
3780
end
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<%= topic.title %>, rendered: <%= topic.updated_at %>

0 commit comments

Comments
 (0)