# 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 }