Skip
Arish's avatar

44. Image Variants and Processing


Image Processing with Active Storage

Active Storage can transform images on-the-fly using variants. This is perfect for creating thumbnails, resizing images, and applying filters.

Setup

Add image processing gem:

ruby
1# Gemfile
2gem 'image_processing', '~> 1.2'

Install ImageMagick or libvips:

bash
1# macOS
2brew install vips
3
4# Ubuntu
5sudo apt install libvips

Creating Variants

Basic Resize

erb
1<!-- Resize to fit within dimensions -->
2<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
3
4<!-- Resize to exact dimensions (crops if needed) -->
5<%= image_tag @user.avatar.variant(resize_to_fill: [100, 100]) %>
6
7<!-- Resize to fill, with gravity -->
8<%= image_tag @user.avatar.variant(resize_to_fill: [100, 100, { gravity: "Center" }]) %>
9
10<!-- Resize width only (height auto) -->
11<%= image_tag @user.avatar.variant(resize: "300x") %>
12
13<!-- Resize height only (width auto) -->
14<%= image_tag @user.avatar.variant(resize: "x200") %>

Common Variant Options

ruby
1# Thumbnail (resize and crop)
2avatar.variant(resize_to_fill: [150, 150])
3
4# Limit size (maintain aspect ratio)
5avatar.variant(resize_to_limit: [800, 600])
6
7# Convert format
8avatar.variant(format: :webp)
9
10# Rotate
11avatar.variant(rotate: 90)
12
13# Quality (for JPEG)
14avatar.variant(saver: { quality: 80 })
15
16# Multiple transformations
17avatar.variant(
18  resize_to_fill: [200, 200],
19  format: :webp,
20  saver: { quality: 80 }
21)

Named Variants

Define variants in your model for reuse:

ruby
1class User < ApplicationRecord
2  has_one_attached :avatar do |attachable|
3    attachable.variant :thumb, resize_to_fill: [100, 100]
4    attachable.variant :medium, resize_to_limit: [300, 300]
5    attachable.variant :large, resize_to_limit: [800, 800]
6  end
7end
erb
1<%= image_tag @user.avatar.variant(:thumb) %>
2<%= image_tag @user.avatar.variant(:medium) %>
3<%= image_tag @user.avatar.variant(:large) %>

Preprocessing Variants

Generate variants immediately on upload:

ruby
1class User < ApplicationRecord
2  has_one_attached :avatar do |attachable|
3    attachable.variant :thumb, resize_to_fill: [100, 100], preprocessed: true
4  end
5end

Or use a job:

ruby
1class ProcessImageJob < ApplicationJob
2  def perform(user)
3    user.avatar.variant(:thumb).processed
4    user.avatar.variant(:medium).processed
5  end
6end
7
8# After upload
9class User < ApplicationRecord
10  after_commit :process_avatar_variants, on: [:create, :update]
11  
12  private
13  
14  def process_avatar_variants
15    ProcessImageJob.perform_later(self) if avatar.attached?
16  end
17end

Checking Variant Status

ruby
1# Check if variant is processed
2user.avatar.variant(:thumb).processed?
3
4# Process synchronously
5variant = user.avatar.variant(:thumb).processed
6
7# Get variant URL
8url_for(user.avatar.variant(:thumb))

Representation (for non-images)

For PDFs and videos, use representations:

ruby
1class Document < ApplicationRecord
2  has_one_attached :file
3end
erb
1<% if @document.file.representable? %>
2  <%= image_tag @document.file.representation(resize_to_limit: [100, 100]) %>
3<% end %>

Preview (for videos)

ruby
1class Video < ApplicationRecord
2  has_one_attached :file
3end
erb
1<% if @video.file.previewable? %>
2  <%= image_tag @video.file.preview(resize_to_limit: [300, 300]) %>
3<% end %>

Validations

Validate file types and sizes:

ruby
1class User < ApplicationRecord
2  has_one_attached :avatar
3  
4  validates :avatar, content_type: ['image/png', 'image/jpeg', 'image/gif'],
5                     size: { less_than: 5.megabytes }
6end
7
8# Or with active_storage_validations gem
9# Gemfile: gem 'active_storage_validations'
10
11class User < ApplicationRecord
12  has_one_attached :avatar
13  
14  validates :avatar, attached: true,
15                     content_type: [:png, :jpg, :jpeg],
16                     size: { less_than: 5.megabytes },
17                     dimension: { width: { min: 100, max: 2000 },
18                                  height: { min: 100, max: 2000 } }
19end

Custom Transformations

ruby
1# Using ImageMagick/libvips options directly
2avatar.variant(
3  resize_to_fill: [200, 200],
4  monochrome: true,
5  rotate: 45
6)
7
8# Complex transformation
9avatar.variant(
10  resize_to_fill: [400, 400],
11  format: :jpg,
12  saver: { 
13    quality: 85,
14    strip: true  # Remove metadata
15  }
16)

Responsive Images

erb
1<picture>
2  <source 
3    srcset="<%= url_for(@article.image.variant(resize_to_limit: [1200, 1200])) %>"
4    media="(min-width: 1200px)">
5  <source 
6    srcset="<%= url_for(@article.image.variant(resize_to_limit: [800, 800])) %>"
7    media="(min-width: 800px)">
8  <source 
9    srcset="<%= url_for(@article.image.variant(resize_to_limit: [400, 400])) %>"
10    media="(min-width: 400px)">
11  <%= image_tag @article.image.variant(resize_to_limit: [400, 400]), 
12      alt: @article.title,
13      loading: "lazy" %>
14</picture>

Using with CDN

ruby
1# config/environments/production.rb
2config.active_storage.service = :amazon
3
4# Use CDN for serving
5config.active_storage.resolve_model_to_route = :rails_storage_proxy
6
7# Or custom CDN host
8Rails.application.routes.default_url_options[:host] = 'cdn.example.com'

Image variants make it easy to serve optimized images for any use case!