fix(tasks): pre-generate thumbnails for active_storage - #4037
Merged
Conversation
tvdeyen
force-pushed
the
ingredient-thumbnail-crops
branch
from
July 2, 2026 12:58
eb81251 to
516b415
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4037 +/- ##
=======================================
Coverage 98.26% 98.27%
=======================================
Files 350 351 +1
Lines 9184 9219 +35
=======================================
+ Hits 9025 9060 +35
Misses 159 159 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
tvdeyen
force-pushed
the
ingredient-thumbnail-crops
branch
2 times, most recently
from
July 2, 2026 13:32
0ff3cfc to
1bf2c67
Compare
Member
Author
|
Let's use ActiveJob and add a async argument to the rake task, so if needed we can background the thumbnail generation |
tvdeyen
marked this pull request as draft
July 6, 2026 10:20
tvdeyen
force-pushed
the
ingredient-thumbnail-crops
branch
from
July 29, 2026 17:08
9477d81 to
732429e
Compare
tvdeyen
marked this pull request as ready for review
July 29, 2026 17:09
The alchemy:generate:thumbnails task was written for Dragonfly: it called PictureThumb.generate_thumbs! directly and warmed ingredient variants by requesting their urls, both of which only persist a thumbnail as a Dragonfly side effect. Under the now-default active_storage adapter it generated nothing and its :thumbs preload referenced an association that does not exist there. Move the logic into an Alchemy::GenerateThumbnails service that branches on the configured storage adapter and materializes each variant inline, so existing pictures and their ingredient crop and srcset variants are warmed on both adapters. The service yields each processed record so the rake task can report progress during a long running generation run.
Generating thumbnails for every picture and ingredient can take a long time on large sites, blocking the process that runs the rake task. Add an ASYNC=true option to the alchemy:generate:thumbnails tasks that enqueues the active_storage variants as ActiveStorage::TransformJobs instead of processing them inline, so the work can be spread across background workers. The dragonfly adapter has no background processor, so ASYNC is a no-op there and generation runs inline.
tvdeyen
force-pushed
the
ingredient-thumbnail-crops
branch
from
August 1, 2026 08:07
732429e to
dbbd3d6
Compare
Alchemy::GenerateThumbnails is only used by the alchemy:generate:thumbnails rake task, so it does not belong in the eager/autoloaded app directory. Move it to lib/alchemy/tasks and require it explicitly from the task and its spec, matching the existing Alchemy::Tidy task helper.
The active_storage variant blob content_type is derived from Marcel and turned out to be an unreliable, environment-dependent signal for the rendered format (a webp-encoded variant can be labelled image/png). Assert on the output filename extension instead, which directly reflects the format the variant was rendered in.
Address review feedback: replace the bare change matchers with explicit expected counts. Use a realistically sized fixture so the ingredient's thumbnail crop is valid on every libvips build - the 1x1 factory image makes cropping fail on stricter builds such as CI - and reset the persisted Dragonfly archive thumbnails only in the srcset context, where they would otherwise make the count ambiguous.
tvdeyen
force-pushed
the
ingredient-thumbnail-crops
branch
from
August 1, 2026 09:04
d961037 to
3fc2622
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What is this pull request for?
The
alchemy:generate:thumbnailsrake task was written for Dragonfly: it calledPictureThumb.generate_thumbs!directly and warmed ingredient variants by requesting their urls — both of which only persist a thumbnail as a Dragonfly side effect. Under the now-defaultactive_storageadapter it generated nothing, and its:thumbspreload references a Dragonfly-only association that does not exist there, so the ingredient task broke outright.The logic now lives in an
Alchemy::GenerateThumbnailsservice that branches on the configured storage adapter and materializes each variant, so existing pictures and their ingredient crop and srcset variants are warmed on both adapters. The service is pure and reports progress through a block, so the rake task can log progress without the service depending on the shell.Notable changes
ASYNC=true). Warming every thumbnail inline can take a long time on large sites. PassingASYNC=trueto the tasks enqueues the active_storage variants as nativeActiveStorage::TransformJobs instead of processing them inline. Dragonfly has no background processor, soASYNCis a no-op there and generation runs inline.TypeError; it was never hit because no element configuredsrcset. It now builds the variant the same way the frontend does (picture_url(size: ...)), so the pre-generated variant matches what the browser requests.Checklist