active-storage について
Active Storage の設定と、Rails でのアップロード、添付ファイル、画像バリアント、ストレージサービスの実装を支援するスキルです。安全な添付ファイル処理、N+1 クエリを防ぐ事前読み込み、ダイレクトアップロード、テストを扱います。
主な機能
- 01Active Storage とストレージサービスの設定
- 02単一・複数添付ファイルの宣言
- 03名前付き画像バリアントの定義と使用
- 04添付ファイルの有無確認と削除
- 05N+1 クエリ回避のための事前読み込み
- 06アップロードのテストと並列ストレージの分離
活用シーン
- 01Rails モデルへのアバターや画像アップロードの追加
- 02ローカルまたはクラウドのファイル保存先の設定
- 03添付画像のサイズ変更と表示
- 04添付ファイルの読み込みやクエリの問題の調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ thinkoodle/ rails-skills --skill active-storageインストール後、Claude Code で/active-storageを入力して読み込まれたか確認します。
同梱ファイルreference.md
詳細
Rails Active Storage エキスパート
Active Storage を使用して、Rails アプリケーションにファイルのアップロード、添付、画像処理、クラウドストレージを実装します。
基本方針
基本原則:
- 必ずインストールジェネレーターを実行する — Active Storage にはマイグレーションと設定が必要です
- ImageMagick より libvips を使用する — 10 倍高速で、メモリ使用量は 1/10 です
- N+1 クエリを防ぐ — 常に
with_attached_<name>スコープを使用します - 添付ファイルがない場合に対処する — 添付ファイルが存在すると決めつけないでください
- 本番環境ではプロキシ、開発環境ではリダイレクトを使用する — プロキシモードは CDN と連携できます
- 名前付きバリアントを使用する — ビューではなくモデルでバリアントを定義します
- テナント対応の SQLite アプリ —
activerecord-tenantedでは、active-storage-multitenantに従わないと保存後の Blob が 404 になります
このスキルを使用する場面
- Rails モデルにファイルアップロードを追加するとき
- クラウドストレージ(S3、GCS、Azure)を設定するとき
- 画像のサイズ変更や変換(バリアント)を実装するとき
- ブラウザーからのダイレクトアップロードを設定するとき
- 添付ファイルの問題(ファイルの欠落、バリアントの破損)をデバッグするとき
- ストレージサービス間を移行するとき
- ファイルアップロードをテストするとき
activerecord-tenantedアプリで保存済み画像の 404 をデバッグするとき(active-storage-multitenantに切り替えます)
手順
ステップ 1:セットアップを確認する
まず、Active Storage がインストールされているか必ず確認します:
# Check for Active Storage tables
bin/rails runner "puts ActiveStorage::Blob.table_exists?"
# Check for storage.yml
ls config/storage.yml
# Check for Active Storage migrations
ls db/migrate/*active_storage*
インストールされていない場合:
bin/rails active_storage:install
bin/rails db:migrate
次の 3 つのテーブルが作成されます:
active_storage_blobs— ファイルのメタデータ(ファイル名、content_type、byte_size、checksum)active_storage_attachments— モデルと Blob を関連付けるポリモーフィックな結合テーブルactive_storage_variant_records— 生成されたバリアントを追跡します
画像処理が利用可能か確認します:
# Check for libvips (preferred)
vips --version
# Or ImageMagick (fallback)
convert --version
必要に応じて Gemfile に追加します:
gem "image_processing", "~> 1.2" # Required for variants
ステップ 2:ストレージサービスを設定する
config/storage.yml — 利用可能なサービスを定義します:
local:
service: Disk
root: <%= Rails.root.join("storage") %>
test:
service: Disk
root: <%= Rails.root.join("tmp/storage") %>
環境ごとに有効なサービスを設定します:
# config/environments/development.rb
config.active_storage.service = :local
# config/environments/test.rb
config.active_storage.service = :test
# config/environments/production.rb
config.active_storage.service = :amazon # or :google, :azure
クラウドサービスについては、S3、GCS、Azure の設定方法を reference.md で確認してください。
ステップ 3:モデルに添付ファイルを宣言する
単一ファイルの添付:
class User < ApplicationRecord
has_one_attached :avatar
end
複数ファイルの添付:
class Message < ApplicationRecord
has_many_attached :images
end
名前付きバリアントを使用する場合(必ず設定してください):
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
特定のストレージサービスを使用する場合:
class User < ApplicationRecord
has_one_attached :avatar, service: :s3
end
ステップ 4:コントローラーで添付ファイルを扱う
ストロングパラメーター — 添付ファイルのパラメーターを許可します:
# has_one_attached
def user_params
params.expect(user: [:name, :email, :avatar])
end
# has_many_attached
def message_params
params.expect(message: [:title, :content, images: []])
end
プログラムでファイルを添付する場合:
# From an upload
user.avatar.attach(params[:avatar])
# From an IO object
user.avatar.attach(
io: File.open("/path/to/file.jpg"),
filename: "avatar.jpg",
content_type: "image/jpeg"
)
# From a URL (download first)
user.avatar.attach(
io: URI.open("https://example.com/photo.jpg"),
filename: "photo.jpg"
)
ステップ 5:ビューに添付ファイルを表示する
描画する前に、添付ファイルがあるか必ず確認します:
<%# WRONG — will raise if no avatar %>
<%= image_tag user.avatar %>
<%# CORRECT — guard against missing attachment %>
<%= image_tag user.avatar if user.avatar.attached? %>
<%# BEST — use variant with guard %>
<% if user.avatar.attached? %>
<%= image_tag user.avatar.variant(:thumb) %>
<% end %>
インライン変換ではなく、名前付きバリアントを使用します:
<%# WRONG — variant defined in view %>
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>
<%# CORRECT — named variant from model %>
<%= image_tag user.avatar.variant(:thumb) %>
ステップ 6:N+1 クエリを防ぐ
Active Storage に関するパフォーマンス上の最大のミスです。
# WRONG — N+1 query for each user's avatar
@users = User.all
# In view: user.avatar triggers a query per user
# CORRECT — eager load attachments
@users = User.with_attached_avatar
# For has_many_attached
@messages = Message.with_attached_images
バリアントを描画する場合は、バリアントレコードも読み込みます:
# In view, after eager loading attachments:
message.images.with_all_variant_records.each do |image|
image_tag image.variant(:thumb).processed.url
end
ステップ 7:フォームを設定する
単純なファイルアップロード:
<%= form_with model: @user do |form| %>
<%= form.file_field :avatar %>
<%= form.submit %>
<% end %>
複数ファイル:
<%= form_with model: @message do |form| %>
<%= form.file_field :images, multiple: true %>
<%= form.submit %>
<% end %>
更新時に既存の has_many_attached ファイルを保持する:
<%# Without this, updating replaces ALL existing attachments %>
<% @message.images.each do |image| %>
<%= form.hidden_field :images, multiple: true, value: image.signed_id %>
<% end %>
<%= form.file_field :images, multiple: true %>
ダイレクトアップロード(フォーム送信前にクラウドへアップロード):
<%= form.file_field :avatar, direct_upload: true %>
Active Storage JavaScript が必要です。設定方法は reference.md を参照してください。
ステップ 8:添付ファイルを削除する
# Synchronous — deletes blob + file from storage immediately
user.avatar.purge
# Async — deletes via background job (preferred in controllers)
user.avatar.purge_later
チェックボックスでユーザーが添付ファイルを削除できるようにします:
<% if @user.avatar.attached? %>
<%= image_tag @user.avatar.variant(:thumb) %>
<%= form.check_box :remove_avatar, label: "Remove avatar" %>
<% end %>
# In controller or model
after_save :purge_avatar, if: -> { saved_change_to_attribute?(:remove_avatar) }
# Or handle in controller:
def update
@user.update(user_params)
@user.avatar.purge_later if params[:user][:remove_avatar] == "1"
end
ステップ 9:テストする
結合テストでは file_fixture_upload を使用します:
class UsersControllerTest < ActionDispatch::IntegrationTest
test "creates user with avatar" do
post users_path, params: {
user: {
name: "Test",
avatar: file_fixture_upload("avatar.png", "image/png")
}
}
user = User.order(:created_at).last
assert user.avatar.attached?
end
end
テストファイルは test/fixtures/files/ に配置します。
テスト後にアップロードしたファイルをクリーンアップします:
# test/test_helper.rb or application_system_test_case.rb
class ActionDispatch::IntegrationTest
def after_teardown
super
FileUtils.rm_rf(ActiveStorage::Blob.service.root)
end
end
並列テストでは、プロセスごとにストレージを分離します:
class ActiveSupport::TestCase
parallelize_setup do |i|
ActiveStorage::Blob.service.root = "#{ActiveStorage::Blob.service.root}-#{i}"
end
end
フィクスチャを使った添付ファイルのテストについては、reference.md を参照してください。
エージェントがよく犯すミス
1. インストールジェネレーターを実行し忘れる
マイグレーションがなければ Active Storage は動作しません。必ず bin/rails active_storage:install と bin/rails db:migrate を実行してください。
2. 画像処理の依存関係が不足している
バリアントには gem "image_processing" と、libvips または ImageMagick のいずれかをシステムにインストールする必要があります。これらがないと、variant() の呼び出しで例外が発生します。
3. バリアントプロセッサーの構文が間違っている
# WRONG — MiniMagick syntax with Vips processor
variant(resize: "100x100")
# CORRECT — Vips syntax (Rails 7+ default)
variant(resize_to_limit: [100, 100])
4. 添付ファイルがない場合に対処していない
# WILL RAISE if no avatar attached
user.avatar.variant(:thumb)
# SAFE
user.avatar.variant(:thumb) if user.avatar.attached?
5. 添付ファイルによる N+1 クエリ
コレクションを読み込むときは、常に with_attached_<name> スコープを使用してください。これは最もよくあるパフォーマンスの問題です。
6. モデルではなくビューでバリアントを定義する
ビュー内でインラインに定義したバリアントは、メンテナンスが難しく、事前処理もできません。ブロック構文を使用して、モデルで定義してください。
7. has_many_attached で追加ではなく置き換えてしまう
デフォルトでは、has_many_attached に新しいファイルを添付すると、既存の添付ファイルがすべて置き換えられます。既存の添付ファイルを保持するには、signed_id を使った非表示フィールドを使用してください。
クイックリファレンス
バリアントの変換(Vips — Rails 7+ のデフォルト)
| 変換 | 構文 | 説明 |
|---|---|---|
| 枠内に収めるようにリサイズ | resize_to_limit: [w, h] | 縦横比を維持して、指定範囲内に収まるよう縮小します |
| 枠いっぱいにリサイズ | resize_to_fill: [w, h] | はみ出した部分を切り抜き、指定範囲いっぱいにします |
| 収まるようにリサイズ | resize_to_fit: [w, h] | 指定範囲内に収めます。サイズが小さくなる場合があります |
| 指定サイズにリサイズ | resize_and_pad: [w, h] | 指定範囲内に収め、余白を加えて正確なサイズにします |
| 形式を変換 | format: :webp | 別の形式に変換します |
| 画質 | saver: { quality: 80 } | 出力画質を設定します |
| 回転 | rotate: 90 | 指定した角度だけ回転します |
主なメソッド
# Attachment status
record.avatar.attached? # => true/false
record.avatar.blank? # => true/false (opposite of attached?)
# File metadata
record.avatar.filename # => "avatar.png"
record.avatar.content_type # => "image/png"
record.avatar.byte_size # => 12345
record.avatar.blob.metadata # => {"identified"=>true, "width"=>100, "height"=>100, "analyzed"=>true}
# URLs
url_for(record.avatar) # Redirect URL
rails_blob_path(record.avatar) # Path helper
rails_storage_proxy_path(record.avatar) # Proxy URL
# Downloading
record.avatar.download # => binary string
record.avatar.open { |f| ... } # => yields Tempfile
# Purging
record.avatar.purge # Sync delete
record.avatar.purge_later # Async delete
設定オプション
# config/application.rb or environment files
config.active_storage.variant_processor = :vips # :vips (default) or :mini_magick
config.active_storage.resolve_model_to_route = :rails_storage_proxy # Enable proxy mode
config.active_storage.draw_routes = false # Disable default routes (for custom auth)
config.active_storage.track_variants = true # Track variant records (default)
config.active_storage.service = :local # Set storage service