Skip to content

Commit eccaddb

Browse files
authored
Merge pull request rails#50758 from rails/fix-video-preview-nplus1
Eagerly load preview images (N+1)
2 parents cb035bd + 7e9b588 commit eccaddb

File tree

5 files changed

+83
-12
lines changed

5 files changed

+83
-12
lines changed

activestorage/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
* Fix N+1 query when fetching preview images for non-image assets
2+
3+
*Aaron Patterson & Justin Searls*
4+
15
* Fix all Active Storage database related models to respect
26
`ActiveRecord::Base.table_name_prefix` configuration.
37

activestorage/app/models/active_storage/attachment.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ class ActiveStorage::Attachment < ActiveStorage::Record
4242
# Eager load all variant records on an attachment at once.
4343
#
4444
# User.first.avatars.with_all_variant_records
45-
scope :with_all_variant_records, -> { includes(blob: { variant_records: { image_attachment: :blob } }) }
45+
scope :with_all_variant_records, -> { includes(blob: {
46+
variant_records: { image_attachment: :blob },
47+
preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
48+
}) }
4649

4750
# Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
4851
def purge

activestorage/lib/active_storage/attached/model.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,10 @@ def #{name}=(attachable)
126126

127127
scope :"with_attached_#{name}", -> {
128128
if ActiveStorage.track_variants
129-
includes("#{name}_attachment": { blob: { variant_records: { image_attachment: :blob } } })
129+
includes("#{name}_attachment": { blob: {
130+
variant_records: { image_attachment: :blob },
131+
preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
132+
} })
130133
else
131134
includes("#{name}_attachment": :blob)
132135
end
@@ -223,7 +226,10 @@ def #{name}=(attachables)
223226

224227
scope :"with_attached_#{name}", -> {
225228
if ActiveStorage.track_variants
226-
includes("#{name}_attachments": { blob: { variant_records: { image_attachment: :blob } } })
229+
includes("#{name}_attachments": { blob: {
230+
variant_records: { image_attachment: :blob },
231+
preview_image_attachment: { blob: { variant_records: { image_attachment: :blob } } }
232+
} })
227233
else
228234
includes("#{name}_attachments": :blob)
229235
end

activestorage/test/models/variant_with_record_test.rb

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,12 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
105105
users.reset
106106

107107
assert_no_difference -> { ActiveStorage::VariantRecord.count } do
108-
assert_queries_count(5) do
109-
# 5 queries:
108+
assert_queries_count(6) do
109+
# 6 queries:
110110
# attachment (cover photos) x 1
111111
# blob for the cover photo x 1
112112
# variant record x 1
113+
# preview_image_attachments for non-images
113114
# attachment x 1
114115
# variant record x 1
115116
users.with_attached_cover_photo.each do |u|
@@ -122,6 +123,60 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
122123
end
123124
end
124125

126+
def find_keys_for_representation(filename)
127+
user = User.create!(name: "Justin")
128+
129+
10.times do
130+
blob = directly_upload_file_blob(filename: filename)
131+
user.highlights_with_variants.attach(blob)
132+
end
133+
134+
# Force the processing
135+
user.highlights_with_variants.each do |highlight|
136+
highlight.representation(:thumb).processed.key
137+
end
138+
139+
highlights = User.with_attached_highlights_with_variants.find_by(name: "Justin").highlights_with_variants.to_a
140+
141+
assert_queries_count(0) do
142+
highlights.each do |highlight|
143+
highlight.representation(:thumb).key
144+
end
145+
end
146+
end
147+
148+
def test_with_attached_image_variant_no_n_plus_1
149+
find_keys_for_representation "racecar.jpg"
150+
end
151+
152+
def test_with_attached_video_variant_no_n_plus_1
153+
find_keys_for_representation "video.mp4"
154+
end
155+
156+
def test_no_n_plus_1_with_all_variant_records_on_attached_video
157+
user = User.create!(name: "Justin")
158+
159+
10.times do
160+
blob = directly_upload_file_blob(filename: "video.mp4")
161+
user.highlights_with_variants.attach(blob)
162+
end
163+
164+
# Force the processing
165+
user.highlights_with_variants.each do |highlight|
166+
highlight.representation(:thumb).processed.key
167+
end
168+
169+
user.reload
170+
171+
highlights = user.highlights_with_variants.with_all_variant_records.to_a
172+
173+
assert_queries_count(0) do
174+
highlights.each do |highlight|
175+
highlight.representation(:thumb).key
176+
end
177+
end
178+
end
179+
125180
test "eager loading has_many_attached records" do
126181
user = User.create!(name: "Josh")
127182

@@ -199,11 +254,12 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
199254
user.reload
200255

201256
assert_no_difference -> { ActiveStorage::VariantRecord.count } do
202-
assert_queries_count(5) do
203-
# 5 queries:
257+
assert_queries_count(6) do
258+
# 6 queries:
204259
# attachments (vlogs) x 1
205260
# blobs for the vlogs x 1
206261
# variant records for the blobs x 1
262+
# preview_image_attachments for non-images
207263
# attachments for the variant records x 1
208264
# blobs for the attachments for the variant records x 1
209265
user.vlogs.with_all_variant_records.each do |vlog|
@@ -218,12 +274,13 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
218274
user.reload
219275

220276
assert_no_difference -> { ActiveStorage::VariantRecord.count } do
221-
assert_queries_count(6) do
222-
# 6 queries:
277+
assert_queries_count(7) do
278+
# 7 queries:
223279
# user x 1
224280
# attachments (vlogs) x 1
225281
# blobs for the vlogs x 1
226282
# variant records for the blobs x 1
283+
# preview_image_attachments for non-images
227284
# attachments for the variant records x 1
228285
# blobs for the attachments for the variant records x 1
229286
User.where(id: user.id).with_attached_vlogs.each do |u|
@@ -243,11 +300,12 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
243300
# More queries here because we are creating a different variant.
244301
# The second time we load this variant, we are back down to just 3 queries.
245302

246-
assert_queries_match(/SELECT/i, count: 9) do
303+
assert_queries_match(/SELECT/i, count: 10) do
247304
# 9 queries:
248305
# attachments (vlogs) initial load x 1
249306
# blob x 1 (gets both records)
250307
# variant record x 1 (gets both records)
308+
# preview_image_attachments for non-images
251309
# 2x get blob, attachment, variant records again, this happens when loading the new blob inside `VariantWithRecord#key`
252310
user.vlogs.with_all_variant_records.each do |vlog|
253311
rep = vlog.representation(resize_to_limit: [200, 200])
@@ -259,7 +317,7 @@ class ActiveStorage::VariantWithRecordTest < ActiveSupport::TestCase
259317

260318
user.reload
261319

262-
assert_queries_count(5) do
320+
assert_queries_count(6) do
263321
user.vlogs.with_all_variant_records.each do |vlog|
264322
rep = vlog.representation(resize_to_limit: [200, 200])
265323
rep.processed

railties/test/application/test_runner_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -799,7 +799,7 @@ def test_verify_fail_fast
799799

800800
assert_match %r{Interrupt}, @error_output
801801
assert_equal 1, matches[3].to_i
802-
assert matches[1].to_i < 11
802+
assert_operator matches[1].to_i, :<, 11
803803
end
804804

805805
def test_run_in_parallel_with_processes

0 commit comments

Comments
 (0)