🚀

【Rails】Active Storageについて

2023/11/02に公開

はじめに

Rails学習時に調べたことや、遭遇したエラーなどについて書いております。
間違いなどありましたら、優しくご指摘いただけましたら幸いです。

対象読者

  • Rails初学者の方
  • Active Storageの概要を理解したい方

前提

当記事は、Rails7.0以降のバージョンを使用している前提の記事となります。

Active Storageとは?

Active StorageとはRailsが提供する公式のファイルアップロード機能のことです。
ローカルだけではなく、Amazon S3、Microsoft Azure Storage、Google Cloud Storageの3種類のクラウドストレージにも対応しています。

Active Storageは、主に以下の2つのモデルで構成されます。

  • ActiveStorage::Blob :アップロードファイルのメタ情報を保持する
  • ActiveStorage::Attachment :モデルと ActiveStorage::Blob の中間テーブルの役割を担う

    UserモデルもPostモデルも、ファイル保存にはAttachmentとBlobを使用するような構造になっています。
    つまり、usersテーブルにもpostsテーブルにも新しくファイル保存用のカラムを追加する必要がないのです。

AttachmentとBlobには何が入っているのか?

それぞれのテーブルにはどんな情報を保持しているのか、schema.rbを見てみることで、より詳しく理解してみましょう。
(後述するActive Storageの導入方法を実行すると作成されます)

db/schema.rb
  create_table "active_storage_blobs", charset: "utf8mb4", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.bigint "byte_size", null: false
    t.string "checksum"
    t.datetime "created_at", precision: nil, null: false
    t.string "service_name", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
  end

  create_table "active_storage_attachments", charset: "utf8mb4", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", precision: nil, null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
  end

Blobの方にはファイル名やファイルの種類、サイズなど、アップロードされたファイルについてのメタ情報が格納されることがわかります。
また、Attachmentは、 まずrecord_typerecord_id でファイルがアタッチされているモデルの情報がわかります。Postモデルであれば、record_typePostrecord_idにPostのIDが入ります。さらに blob_id でそのモデルに紐づくBlobの情報がわかるようになっているので、まさに中間テーブルのような役割をしていることがわかりますね。

Active Storageの導入方法

以下のコマンドを実行することでActive Storageを使用できるようになります。

bin/rails active_storage:install
bin/rails db:migrate

ファイルをレコードにアタッチする

すでに存在するモデルにActive Storage属性を追加したい場合

モデルにレコードとファイルのマッピングを設定します。
以下の例では、 has_one_attached を記載することで、Postモデルに画像が1つマッピングされています。 :imageはファイルの名称に応じて名前を変更できます。
画像が複数ある場合は、 has_many_attached を使用します。

app/models/post.rb
class Post < ApplicationRecord
  has_one_attached :image
end

Active Storage属性を含んだモデルを生成したい場合

以下のようにコマンドを実行することで実現できます。(Rails6.0以降)

bin/rails g model Post image:attachment

画像加工できるようにする

アップロードした画像をそのまま使うのではなく、リサイズしたいなど、画像加工を施すこともできます。

画像加工に必要なサードパーティソフトウェア・gem

画像加工を行うために必要となるのは、image_processing gemlibvipsです。libvipsの代わりにImageMagickを使うこともできます。libvipsはImageMagickほど多くの画像形式をサポートしていませんが、メモリ消費量が少なく実行速度が速いことが特徴です。

image_processing gemのインストール

Gemfileに以下を追加して、 bundle install を実行しましょう。

Gemfile
gem "image_processing", ">= 1.2"

実行すると、Gemfile.lockに以下が追加されています。

Gemfile.lock
image_processing (1.12.2)
  mini_magick (>= 4.9.5, < 5)
  ruby-vips (>= 2.0.17, < 3)

MiniMagickは、RubyでImageMagickをより簡単に使えるようにしているものです。
もう一方のruby-vipsも同様に、libvipsの機能を使用するための接続部分となるものです。

libvipsのインストール

MacOSであれば、 brew install vips を実行します。
Dockerを使用している場合は、Dockerfileに以下を記載します。
※ Debian系LinuxOS(Ubuntuなど)のコンテナイメージを作成する場合の例です。

Dockerfile
RUN apt-get install -y libvips

Rails7.0以降は、画像加工・解析に使用するプロセッサのデフォルトが vips になっているため、ImageMagickを使用する場合、libvipsと同様の方法でインストールした後、以下の設定が必要です。

cofig/application.rb
config.active_storage.variant_processor = :mini_magick

https://railsguides.jp/configuring.html#active-storageを設定する

画像のリサイズ

variantメソッドを使用することで、サイズ違いの画像が作成できます。

<%= image_tag @post.image.variant(resize_to_limit: [100, 100]) %>

resize_to_limit は、指定の寸法より大きい場合のみ、元の縦横比を維持したまま、指定の寸法に収まるように画像を縮小します。パラメータとして指定できるものは他にもありますので、詳しくは以下をご確認ください。
https://railsguides.jp/v7.1/active_storage_overview.html#画像を変形する

ちなみに、調べていた中で <%= image_tag @post.image.variant(resize: '100x100') %>のような書き方をしているのを見かけることがありました。
Railsガイドも過去のバージョン(v6.1)で、このように resize を使用した書き方をしています。
https://railsguides.jp/v6.1/active_storage_overview.html#画像を変換する
手元で動かしている環境はRails7.0.4ですが、 resize を使って書き換えても動作しています。
使い分けがあるのか、 resize は使うべきではないのかよくわからずかなり調べた結果、英語版のRailsガイドの修正点をまとめたようなファイルに、 resize だとvipsを使った際にエラーになるために、resize_to_limit のような書き方に修正すると書いてあるのを見つけました。
https://github.com/rails/rails/blob/6f6c211f4781e7bda63e5f47089f9d2612dc0a52/guides/source/upgrading_ruby_on_rails.md?plain=1#L569-L576
自分が書き換えて動かしてみた中ではエラーは生じなかったのですが、resize_to_limit などの書き方が推奨されている様子が伺えますので、こちらを使っていけばいいのかなと思います。

画像アップロードライブラリの比較

Active Storageのように標準で使えるものだけではなく、画像アップロード機能にはCarrierWaveShrineのようなGemもあります。
Active StorageはこれらのGemと比較してどのような点が異なるのでしょうか?

加工画像の生成タイミング

CarrierWave、Shrine、Active Storageは以下のようにすべて加工画像の生成タイミングが異なります。

画像アップロード機能 加工画像生成タイミング
CarrierWave アップロード時
Shrine アップロード後に非同期
Active Storage 画像URLへのアクセス時

バリデーションヘルパーについて

画像をアップロードして欲しいフィールドにおいて、動画などの想定していないファイルのアップロードを防ぎたいというような状況があるかと思います。そのような場合、受け付けるファイルの種類を指定することで、アップロードできるファイルに制限をつけたいですよね。
CarrierWave、Shrine gemにはそのようなバリデーションの機能が備わっています。一方、Active Storageにはデフォルトでそのようなバリデーションヘルパーはなく、自力で実装するか、Active Storage Validations gemを利用する必要があります。

キャッシュ機能

フォーム入力時に、画像をアップロードしたあと、他のフィールドにおいてバリデーションエラーが起きてしまう場面を想定します。キャッシュ機能があれば、アップロードした画像はキャッシュから取得できるため、再度アップロードしなくてもよくなります。CarrierWave、Shrine gemにはこのようなキャッシュ機能がありますが、Active Storageには備わっていないため、バリデーションエラーでフォーム入力データが送信できなかった場合、再度ファイルをアップロードしなければなりません。

参考

Discussion