mcpField
A
active-storage

active-storage

github.com/thinkoodle開発7176 回インストール8 週間前に更新
リポジトリ

active-storage について

Active Storage の設定と、Rails でのアップロード、添付ファイル、画像バリアント、ストレージサービスの実装を支援するスキルです。安全な添付ファイル処理、N+1 クエリを防ぐ事前読み込み、ダイレクトアップロード、テストを扱います。

主な機能

  1. 01Active Storage とストレージサービスの設定
  2. 02単一・複数添付ファイルの宣言
  3. 03名前付き画像バリアントの定義と使用
  4. 04添付ファイルの有無確認と削除
  5. 05N+1 クエリ回避のための事前読み込み
  6. 06アップロードのテストと並列ストレージの分離

活用シーン

  1. 01Rails モデルへのアバターや画像アップロードの追加
  2. 02ローカルまたはクラウドのファイル保存先の設定
  3. 03添付画像のサイズ変更と表示
  4. 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 アプリケーションにファイルのアップロード、添付、画像処理、クラウドストレージを実装します。

基本方針

基本原則:

  1. 必ずインストールジェネレーターを実行する — Active Storage にはマイグレーションと設定が必要です
  2. ImageMagick より libvips を使用する — 10 倍高速で、メモリ使用量は 1/10 です
  3. N+1 クエリを防ぐ — 常に with_attached_<name> スコープを使用します
  4. 添付ファイルがない場合に対処する — 添付ファイルが存在すると決めつけないでください
  5. 本番環境ではプロキシ、開発環境ではリダイレクトを使用する — プロキシモードは CDN と連携できます
  6. 名前付きバリアントを使用する — ビューではなくモデルでバリアントを定義します
  7. テナント対応の 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

コメント