What a variant is
A profile photo usually needs to appear in several shapes: a tiny thumbnail in a list, a medium preview on a card, a full-size crop on the profile itself. The naive fix is to upload three files and manage three records. ActiveStorage's answer is a variant: one uploaded file, and as many derived shapes as you ask for, computed on demand instead of stored up front.
One blob, many variants
course.cover.variant(resize_to_limit: [400, 300])
course.cover.variant(resize_to_limit: [80, 80])
Both lines start from the same uploaded file, the ActiveStorage::Blob, and describe a transformation, not a new upload. Nothing is processed when you write this line. A variant is a recipe, evaluated lazily. The resize happens the first time something requests the URL and the file doesn't exist yet at that size. Ask for the same recipe later and ActiveStorage finds the processed file instead of redoing the work.
Why this app pins a processor
A recipe needs something that can read and resize an image, and Rails doesn't ship one. It delegates to whichever image-processing library is configured. This app's config/environments/development.rb and config/environments/production.rb both set:
config.active_storage.variant_processor = :vips
:vips stands for libvips, used through the ruby-vips gem, and both environments choose it. It isn't a development convenience. The other common option, :mini_magick (a wrapper around ImageMagick), is still supported by Rails, but this app doesn't use it anywhere. vips is the only processor configured, in every environment that configures one.
What "on demand" costs
The first request for a new variant pays for image processing inline. That request waits for vips to resize the source file before a response can go out. Later requests for that recipe skip the resize. They still reach a Rails controller, because this app serves files in proxy mode (lesson 5). ActiveStorage::Representations::ProxyController looks up the processed variant and streams the stored file back. The tradeoff is deliberate: one slow request the first time a shape is needed. In exchange, you never have to predict every size up front, and nothing needs reprocessing when a design changes and only some pages need a new crop.
Key takeaways
- A variant is a transformation recipe attached to an existing blob, not a new upload.
course.cover.variant(resize_to_limit: [400, 300])describes a shape; it doesn't create one. - Variants are computed lazily. The resize happens on the first request for that recipe, and later requests reuse the result.
- This app's
config/environments/development.rbandproduction.rbboth pinconfig.active_storage.variant_processor = :vips.libvips, through theruby-vipsgem, is the only processor configured anywhere in this app. - The first request for a new shape is slower because it pays for the resize. Later requests skip the resize but still go through a Rails controller that finds the processed file and streams it.
Keep going with lesson 2
Attaching one image, or many
How has_one_attached and has_many_attached differ, and why assigning with = replaces images while .attach adds them.
A free account also gives you the quizzes, your progress and notes, and the discussion.