active-storage

bastos's avatarfrom bastos

This skill should be used when the user asks about "file uploads", "Active Storage", "attachments", "has_one_attached", "has_many_attached", "image variants", "S3", "cloud storage", "direct uploads", "file processing", "image transformations", or needs guidance on handling file uploads in Rails applications.

0stars🔀0forks📁View on GitHub🕐Updated Jan 10, 2026

When & Why to Use This Skill

This Claude skill provides a comprehensive guide and ready-to-use code snippets for implementing Active Storage in Ruby on Rails applications. It covers the entire lifecycle of file management, including model configuration with 'has_one_attached' and 'has_many_attached', cloud storage integration (AWS S3, GCS, Azure), direct uploads, and advanced image transformations. It is designed to help developers streamline file upload workflows and optimize cloud storage configurations.

Use Cases

  • Implementing user profile picture uploads with automatic thumbnail generation and resizing variants.
  • Configuring production-ready cloud storage services like Amazon S3 or Google Cloud Storage for Rails applications.
  • Setting up direct-to-cloud uploads using JavaScript to improve application performance and reduce server load.
  • Managing complex file attachments for document management systems, including PDF previews and multi-file uploads.
  • Migrating or mirroring file storage between different cloud providers using the Active Storage Mirror service.
nameactive-storage
descriptionThis skill should be used when the user asks about "file uploads", "Active Storage", "attachments", "has_one_attached", "has_many_attached", "image variants", "S3", "cloud storage", "direct uploads", "file processing", "image transformations", or needs guidance on handling file uploads in Rails applications.
version1.0.0

Active Storage

Comprehensive guide to file uploads and cloud storage in Rails.

Setup

rails active_storage:install
rails db:migrate

This creates:

  • active_storage_blobs - File metadata
  • active_storage_attachments - Polymorphic join table
  • active_storage_variant_records - Cached variant info

Model Configuration

Single Attachment

class User < ApplicationRecord
  has_one_attached :avatar
end

Multiple Attachments

class Article < ApplicationRecord
  has_many_attached :images
end

With Service Selection

class Document < ApplicationRecord
  has_one_attached :file, service: :amazon
end

With Variants

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]
  end
end

Attaching Files

From Form Upload

# Controller
def create
  @user = User.new(user_params)
  @user.save
end

def user_params
  params.require(:user).permit(:name, :avatar, images: [])
end
<%# Form %>
<%= form_with model: @user do |form| %>
  <%= form.file_field :avatar %>
  <%= form.file_field :images, multiple: true %>
  <%= form.submit %>
<% end %>

Programmatically

# From file
user.avatar.attach(io: File.open("/path/to/photo.jpg"), filename: "photo.jpg")

# From uploaded file
user.avatar.attach(params[:avatar])

# From URL (downloaded)
user.avatar.attach(
  io: URI.open("https://example.com/photo.jpg"),
  filename: "downloaded.jpg",
  content_type: "image/jpeg"
)

# From string content
user.avatar.attach(
  io: StringIO.new(pdf_content),
  filename: "document.pdf",
  content_type: "application/pdf"
)

Check Attachment Status

user.avatar.attached?  # true/false
user.images.attached?  # true/false
user.images.any?       # true/false
user.images.count      # number of attachments

Displaying Files

URLs

<%# Direct URL %>
<%= url_for(@user.avatar) %>

<%# Download URL %>
<%= rails_blob_path(@user.avatar, disposition: "attachment") %>

<%# Image tag %>
<%= image_tag @user.avatar %>

<%# With fallback %>
<%= image_tag(@user.avatar.attached? ? @user.avatar : "default_avatar.png") %>

Variants (Image Transformations)

<%# On-the-fly transformation %>
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>

<%# Named variant %>
<%= image_tag @user.avatar.variant(:thumb) %>

<%# Complex transformations %>
<%= image_tag @user.avatar.variant(
  resize_to_fill: [200, 200],
  format: :webp,
  saver: { quality: 80 }
) %>

Common Transformations

Method Description
resize_to_limit: [w, h] Resize to fit within bounds
resize_to_fill: [w, h] Resize and crop to fill
resize_to_fit: [w, h] Resize to fit exactly
resize_and_pad: [w, h] Resize and pad to fill
crop: "100x100+10+10" Crop specific area
rotate: 90 Rotate degrees
format: :webp Convert format

Preview (PDFs, Videos)

<%# PDF preview (first page) %>
<%= image_tag @document.file.preview(resize_to_limit: [200, 200]) %>

<%# Video preview (frame) %>
<%= image_tag @article.video.preview(resize_to_limit: [300, 200]) %>

Direct Uploads

Setup

<%# Include JS %>
<%= javascript_include_tag "activestorage" %>

<%# Form with direct upload %>
<%= form_with model: @user do |form| %>
  <%= form.file_field :avatar, direct_upload: true %>
<% end %>

JavaScript Events

// Listen for upload events
addEventListener("direct-upload:initialize", event => {
  const { target, detail } = event
  const { id, file } = detail
  // Show upload UI
})

addEventListener("direct-upload:progress", event => {
  const { id, progress } = event.detail
  // Update progress bar
})

addEventListener("direct-upload:error", event => {
  const { id, error } = event.detail
  // Handle error
})

addEventListener("direct-uploads:end", event => {
  // All uploads complete
})

Storage Services

Configuration

# config/storage.yml
local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: my-app-<%= Rails.env %>

google:
  service: GCS
  project: my-project
  credentials: <%= Rails.root.join("path/to/keyfile.json") %>
  bucket: my-app-<%= Rails.env %>

azure:
  service: AzureStorage
  storage_account_name: myaccount
  storage_access_key: <%= Rails.application.credentials.dig(:azure, :storage_access_key) %>
  container: my-container

mirror:
  service: Mirror
  primary: amazon
  mirrors: [ google, azure ]

Environment Selection

# config/environments/development.rb
config.active_storage.service = :local

# config/environments/production.rb
config.active_storage.service = :amazon

Public Files

# config/storage.yml
amazon_public:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: us-east-1
  bucket: my-public-bucket
  public: true
class Article < ApplicationRecord
  has_many_attached :images, service: :amazon_public
end

Validations

Using active_storage_validations Gem

# Gemfile
gem "active_storage_validations"

# Model
class User < ApplicationRecord
  has_one_attached :avatar

  validates :avatar,
    attached: true,
    content_type: ["image/png", "image/jpeg"],
    size: { less_than: 5.megabytes }
end

class Article < ApplicationRecord
  has_many_attached :images

  validates :images,
    content_type: /\Aimage\/.*\z/,
    size: { less_than: 10.megabytes },
    limit: { max: 10 }
end

Custom Validation

class User < ApplicationRecord
  has_one_attached :avatar

  validate :acceptable_avatar

  private

  def acceptable_avatar
    return unless avatar.attached?

    unless avatar.blob.byte_size <= 5.megabyte
      errors.add(:avatar, "is too large (max 5MB)")
    end

    acceptable_types = ["image/jpeg", "image/png", "image/gif"]
    unless acceptable_types.include?(avatar.content_type)
      errors.add(:avatar, "must be JPEG, PNG, or GIF")
    end
  end
end

Downloading and Processing

# Download content
binary = user.avatar.download

# Open as tempfile
user.avatar.open do |file|
  # file is a Tempfile
  ImageProcessor.process(file.path)
end

# Access metadata
user.avatar.blob.byte_size
user.avatar.blob.content_type
user.avatar.blob.filename
user.avatar.blob.created_at

Removing Attachments

# Remove single attachment
user.avatar.purge        # Sync
user.avatar.purge_later  # Async (recommended)

# Remove specific from many
user.images.find(attachment_id).purge_later

# Remove all
user.images.purge_later

Eager Loading

# Avoid N+1 queries
@users = User.with_attached_avatar
@articles = Article.with_attached_images

# Named scope automatically created
User.with_attached_avatar.where(active: true)

Testing

# test/models/user_test.rb
require "test_helper"

class UserTest < ActiveSupport::TestCase
  test "can attach avatar" do
    user = users(:one)

    user.avatar.attach(
      io: file_fixture("avatar.png").open,
      filename: "avatar.png",
      content_type: "image/png"
    )

    assert user.avatar.attached?
  end
end