How to implement file upload with Rails Active Storage | has_one_attached & S3 support
Active Storage, built into Rails 5.2 by default, is a framework for file uploads. It's the official solution replacing <code>CarrierWave</code> and <code>Paperclip</code>, enabling seamless switching between local disk, AWS S3, Google Cloud Storage, and Microsoft Azure storage. This article systematically covers Active Storage setup, <code>has_one_attached</code> definition, validation, S3 configuration (config/storage.yml), and image resizing (Variant).
Active Storage Setup
To start using Active Storage, generate migrations with <code>rails active_storage:install</code> and apply them with <code>rails db:migrate</code>. This creates three tables: <code>active_storage_blobs</code>, <code>active_storage_attachments</code>, and <code>active_storage_variant_records</code>.
# Active Storage のマイグレーションを生成
rails active_storage:install
# マイグレーションを実行
rails db:migrate
# 生成されるテーブル:
# active_storage_blobs — ファイルのメタデータ(ファイル名、サイズ、MIMEタイプなど)
# active_storage_attachments — モデルとblobの紐付け(ポリモーフィック関連)
# active_storage_variant_records — 変換済み画像のキャッシュ
# Gemfile
gem 'image_processing', '>= 1.2' # Variant(画像リサイズ)に必要
# gem 'mini_magick' # ImageMagick を使う場合
# gem 'ruby-vips' # libvips を使う場合(高速)
bundle install
# ImageMagick のインストール(Ubuntu/Debian)
sudo apt-get install imagemagick
# macOS(Homebrew)
brew install imagemagick vips
Defining has_one_attached and has_many_attached
<code>has_one_attached</code> defines one-to-one file attachments, and <code>has_many_attached</code> defines one-to-many attachments. Just add them to the model to attach, retrieve, and delete files.
# app/models/user.rb
class User < ApplicationRecord
# 1つのアバター画像
has_one_attached :avatar
# 複数の添付ファイル
has_many_attached :documents
end
# app/models/article.rb
class Article < ApplicationRecord
has_one_attached :cover_image
has_many_attached :attachments
# バリデーション(active_storage_validations gem を使用)
validates :cover_image,
content_type: { in: %w[image/jpeg image/png image/webp], message: 'はJPEG・PNG・WebPのみ有効です' },
size: { less_than: 5.megabytes, message: 'は5MB以下にしてください' }
validates :attachments,
content_type: %w[application/pdf image/jpeg image/png],
size: { less_than: 20.megabytes }
end
# コントローラーでの操作例
# app/controllers/users_controller.rb
class UsersController < ApplicationController
def update
@user = current_user
if @user.update(user_params)
redirect_to @user, notice: 'プロフィールを更新しました。'
else
render :edit, status: :unprocessable_entity
end
end
private
def user_params
params.require(:user).permit(:name, :avatar, documents: [])
end
end
<!-- app/views/users/edit.html.erb -->
<%= form_with model: @user do |f| %>
<div>
<%= f.label :avatar, 'プロフィール画像' %>
<!-- enctype は form_with が自動設定 -->
<%= f.file_field :avatar, accept: 'image/jpeg,image/png,image/webp' %>
<%# 現在の画像を表示 %>
<% if @user.avatar.attached? %>
<%= image_tag @user.avatar, width: 100 %>
<% end %>
</div>
<div>
<%= f.label :documents, '添付ファイル(複数可)' %>
<%= f.file_field :documents, multiple: true %>
</div>
<%= f.submit '保存' %>
<% end %>
Validation with active_storage_validations Gem
Since Active Storage's standard model validation is weak, using the <code>active_storage_validations</code> gem allows you to declaratively validate Content-Type, size, width and height in pixels, and more.
# Gemfile
gem 'active_storage_validations'
# app/models/product.rb
class Product < ApplicationRecord
has_one_attached :main_image
has_many_attached :gallery_images
# content_type バリデーション
validates :main_image,
attached: true,
content_type: {
in: %w[image/jpeg image/png image/webp image/gif],
message: 'は画像ファイル(JPEG/PNG/WebP/GIF)を選択してください'
},
size: {
between: 1.kilobyte..10.megabytes,
message: 'は1KB〜10MBの範囲で指定してください'
},
dimension: {
width: { min: 200, max: 4096 },
height: { min: 200, max: 4096 },
message: '縦横ともに200〜4096pxの範囲で指定してください'
}
validates :gallery_images,
content_type: %w[image/jpeg image/png image/webp],
size: { less_than: 5.megabytes },
limit: { max: 10, message: '画像は最大10枚まで添付できます' }
end
Configuration of config/storage.yml
Active Storage's storage services are defined in <code>config/storage.yml</code>, and you switch which service to use via environment-specific settings in <code>config/environments/</code>.
# config/storage.yml
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
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: <%= ENV['AWS_REGION'] %>
bucket: <%= ENV['AWS_BUCKET'] %>
# CloudFront を使う場合
# upload: { cache_control: 'max-age=3600' }
google:
service: GCS
project: <%= ENV['GOOGLE_CLOUD_PROJECT'] %>
credentials: <%= ENV['GOOGLE_CLOUD_CREDENTIALS'] %>
bucket: <%= ENV['GOOGLE_CLOUD_BUCKET'] %>
azure:
service: AzureStorage
storage_account_name: <%= ENV['AZURE_STORAGE_ACCOUNT_NAME'] %>
storage_access_key: <%= ENV['AZURE_STORAGE_ACCESS_KEY'] %>
container: <%= ENV['AZURE_STORAGE_CONTAINER'] %>
# config/environments/production.rb
Rails.application.configure do
# S3 を使用
config.active_storage.service = :amazon
end
# config/environments/development.rb
Rails.application.configure do
# ローカルディスクを使用
config.active_storage.service = :local
end
# Gemfile(S3を使う場合)
gem 'aws-sdk-s3', require: false
# Google Cloud Storage を使う場合
gem 'google-cloud-storage', '~> 1.11', require: false
# Azure を使う場合
gem 'azure-storage-blob', require: false
Image resizing with Variant
Using Active Storage's Variant feature, you can dynamically resize and transform saved images for display. Conversion results are cached, so they are delivered quickly from the second time onwards.
# ビューでの Variant 使用例
# app/views/users/show.html.erb
<%# サムネイル表示(100x100にリサイズ)%>
<%= image_tag @user.avatar.variant(resize_to_limit: [100, 100]) %>
<%# アスペクト比を保ちながら幅300pxに収める %>
<%= image_tag @user.avatar.variant(resize_to_limit: [300, nil]) %>
<%# 中央でトリミングして正方形に(400x400)%>
<%= image_tag @user.avatar.variant(resize_to_fill: [400, 400]) %>
<%# WebP に変換(高画質・小容量)%>
<%= image_tag @user.avatar.variant(convert: 'webp', resize_to_limit: [800, 600]) %>
<%# 画質を下げてファイルサイズを削減 %>
<%= image_tag @user.avatar.variant(
resize_to_limit: [1200, 900],
saver: { quality: 80 }
) %>
# モデルでよく使う Variant を定義する(ヘルパーメソッド)
# app/models/user.rb
class User < ApplicationRecord
has_one_attached :avatar
def avatar_thumbnail
avatar.variant(resize_to_fill: [80, 80], convert: 'webp')
end
def avatar_medium
avatar.variant(resize_to_limit: [400, 400], convert: 'webp', saver: { quality: 85 })
end
end
# ビューで使用
# <%= image_tag @user.avatar_thumbnail if @user.avatar.attached? %>
Test files for this article (free)
- → <a href="/ja/files/images/png/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PNG Test Image (1MB)</a> — for testing <code>has_one_attached</code> and <code>Variant</code>
- → <a href="/ja/files/pdf/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">PDF Test File (1MB)</a> — For verifying has_many_attached and S3 upload
- → <a href="/ja/files/zip/1mb/" class="text-primary-600 dark:text-primary-400 hover:underline">ZIP test file (1MB)</a> — for validating content_type validation
Related articles
- → <a href="/ja/blog/s3-upload-limit/" class="text-primary-600 dark:text-primary-400 hover:underline">Summary of File Upload Limits for AWS S3 and CloudFront</a>
- → <a href="/ja/blog/laravel-file-upload/" class="text-primary-600 dark:text-primary-400 hover:underline">Laravel File Upload Implementation Guide | Validation, Storage, and S3 Support</a>
- → <a href="/ja/blog/file-validation-checklist/" class="text-primary-600 dark:text-primary-400 hover:underline">Web Form File Validation Implementation Checklist</a>