ActiveStorage for file uploads and attachments
Sarah Mitchell
Feb 2026
2 tabs
# Installation
# rails active_storage:install
# rails db:migrate
# config/storage.yml
local:
service: Disk
root: <%= Rails.root.join("storage") %>
amazon:
service: S3
access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
region: us-east-1
bucket: my-app-bucket
google:
service: GCS
project: my-project
credentials: <%= Rails.root.join("config/gcs.keyfile") %>
bucket: my-app-bucket
# config/environments/production.rb
config.active_storage.service = :amazon
# Model with attachments
class User < ApplicationRecord
has_one_attached :avatar
has_many_attached :documents
validates :avatar, content_type: ['image/png', 'image/jpg', 'image/jpeg'],
size: { less_than: 5.megabytes }
end
class Post < ApplicationRecord
has_one_attached :featured_image
has_many_attached :gallery_images
# Validation using custom validator
validates :featured_image, attached: true,
content_type: /Aimage/.*z/,
size: { less_than: 10.megabytes }
end
# Attaching files
user = User.create!(name: 'John')
# From file upload
user.avatar.attach(params[:avatar])
# From file path
user.avatar.attach(
io: File.open('/path/to/avatar.jpg'),
filename: 'avatar.jpg',
content_type: 'image/jpeg'
)
# Multiple files
post.gallery_images.attach(params[:images])
# Accessing attachments
user.avatar.attached? # => true
user.avatar.filename # => "avatar.jpg"
user.avatar.byte_size # => 123456
user.avatar.content_type # => "image/jpeg"
# URL for download
url_for(user.avatar)
# Removing attachments
user.avatar.purge # Remove file synchronously
user.avatar.purge_later # Remove file via background job
# Gemfile - image processing
gem 'image_processing'
# Creating variants (on-demand thumbnails)
class User < ApplicationRecord
has_one_attached :avatar
# Named variant
def avatar_thumbnail
avatar.variant(resize_to_limit: [100, 100])
end
def avatar_large
avatar.variant(resize_to_limit: [500, 500])
end
end
# In views
<%= image_tag user.avatar.variant(resize_to_limit: [200, 200]) %>
<%= image_tag user.avatar_thumbnail %>
# Variant options
avatar.variant(
resize_to_limit: [800, 600],
quality: 90,
format: :jpg
)
# Crop and resize
avatar.variant(
resize_to_fill: [400, 300],
crop: "400x300+0+0"
)
# Convert to different format
avatar.variant(format: :webp, quality: 80)
# Processing variants in background
class User < ApplicationRecord
has_one_attached :avatar do |attachable|
attachable.variant :thumb, resize_to_limit: [100, 100]
attachable.variant :medium, resize_to_limit: [300, 300]
attachable.variant :large, resize_to_limit: [800, 800]
end
end
# Rails 7+ variant processing
<%= image_tag user.avatar.representation(resize_to_limit: [200, 200]) %>
# Generating previews for videos/PDFs
class Document < ApplicationRecord
has_one_attached :file
def preview_image
file.preview(resize_to_limit: [400, 400])
end
end
# Check if preview is available
if document.file.previewable?
image_tag document.preview_image
end
# Direct uploads (JavaScript)
# app/views/users/_form.html.erb
<%= form.file_field :avatar, direct_upload: true %>
# This uploads directly to cloud storage,
# then submits signed blob ID to Rails
# Custom validator
class AttachedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless value.attached?
if options[:content_type]
validate_content_type(record, attribute, value)
end
if options[:size]
validate_size(record, attribute, value)
end
end
private
def validate_content_type(record, attribute, value)
return if value.content_type.match?(options[:content_type])
record.errors.add(
attribute,
:content_type,
message: "must be a valid format"
)
end
def validate_size(record, attribute, value)
max_size = options[:size][:less_than]
return if value.byte_size <= max_size
record.errors.add(
attribute,
:file_size,
message: "must be less than #{max_size / 1.megabyte}MB"
)
end
end
# Usage:
validates :avatar, attached: true,
content_type: /Aimage/(png|jpeg|jpg)z/,
size: { less_than: 5.megabytes }
2 files · ruby
Explain with highlit
ActiveStorage handles file uploads with cloud storage integration. It supports local disk, S3, Google Cloud Storage, Azure. Files attach to models via has_one_attached and has_many_attached. I use ActiveStorage for avatars, documents, images. Image variants create on-demand thumbnails—crop, resize, format conversion. Direct uploads send files straight to cloud storage, reducing server load. Previews generate thumbnails for videos and PDFs. ActiveStorage integrates with Active Job for async processing. Testing uses fixture files and attachment stubs. Understanding blob storage vs. attachments vs. variants is key. ActiveStorage simplifies file handling compared to CarrierWave/Paperclip.