[Rails5.2]ActiveStorageの仕組み(図あり)と使ってみてわかったこと


遅ればせながらRails5.2で導入されたActiveStorageを使ってみました。

GitHubのReadmeやRailsGuidesの説明だけでなくソースコードも(一部)読んで調べたので、大まかな仕組みを図を使って説明してみます。

ActiveStorageとは?

Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageのような クラウドストレージサービスへのファイルのアップロードとそれらのファイルをActive Recordオブジェクトに添付することを容易にします。 開発およびテスト用のローカルディスクベースのサービスが付属しており、ファイルをバックアップおよび移行用の従属サービスにミラーリングすることができます。

https://railsguides.jp/active_storage_overview.html より引用)

ということでRails純正のアップローダーになります。

具体的な使い方

RailsGuidesのセットアップをそのままやって設定しましょう。
https://railsguides.jp/active_storage_overview.html

設定に使うファイルは以下のとおりです。

  • config/storage.yml
  • config/environments/development.rb
  • config/environments/production.rb

Amazon S3やGoogle Cloud Storageなどのクラウドストレージサービスを使わずに、まずローカルで試してみるだけならデフォルト設定のままで良いです。

デフォルト設定なら添付ファイルは、

  • development/production環境: /storage
  • test環境: /tmp/storage

に置きます。

設定後にささっと必要なことだけやるには、GitHubのReadmeのExamplesが簡潔でわかりやすいです。
https://github.com/rails/rails/tree/master/activestorage

# Userモデルにファイルを添付するためにavatarを関連付けする
class User < ApplicationRecord
  has_one_attached :avatar
end

# ファイルを添付する
user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")

# 添付の有無を確認する
user.avatar.attached? # => true

# 添付ファイルを外す
user.avatar.purge

後ほど説明しますが、基本的には関連付けの仕組みを使ってモデルと添付したいファイルとを紐づけしています。
attachメソッドで添付して、purgeメソッドで添付を外すことができます。

ActiveStorageにおけるアップロードの仕組みについて

ActiveStorageにおけるアップロードは、モデルと添付したいファイルをActiveStorageにてあらかじめ用意されているAttachmentとBlobという2つのモデルを介して関連付けすることで実現されています。

Userモデルでhas_one_attached :avatarと設定すると、実際には

  • has_one :avatar_attachment
  • has_one :avatar_blob, through: :avatar_attachment

のようなhas_one :through関連付けが行われます。

これによって、図のようにUserモデルがAttachmentモデルを介してBlobモデルへ関連付けされます。

ちなみにhas_one_attachedに渡したavatarはAttachmentオブジェクトのname属性の値となります。

以下のようにattachメソッドを使ってファイル添付を実行すると、Blobオブジェクトが新規に生成されて各属性(filename, byte_sizeなど)に添付ファイルに関する値が設定されます。

user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")

以下に添付ファイルも加えた全体像を図示します。

ActiveRecordによってUserオブジェクトがデータベース上のデータにマッピングされているように、Blobオブジェクトがローカルまたはクラウドストレージ上のファイルにマッピングされます。

こうして前述のUser, Attachment, Blobモデル間の関連付けにより、Userオブジェクトと添付ファイルを紐付けることができるようになります。

使ってみてわかったこと

CarrierWaveもfogも特に詳しいわけではないので軽い気持ちでActiveStorageを使ってみることにしましたが、仕組みもシンプルなものであることがわかりましたし、すんなりと使うことができました。

ただ、いくつか注意点があったので記載しておきます。

Rails 5.2でもrails active_storage:installが必要だった

RailsGuidesには以下のように不要と書いてありましたが、実際には必要となったので実行しました。

新しいRails 5.2アプリケーションでrails active_storage:installを実行する必要はありません。マイグレーションは自動的に生成されます。

rails active_storage:install自体は大したことはやっていません。
ActiveStorageの中にある、AttachmentモデルとBlobモデルの属性を定義したマイグレーションファイルのコピーをdb/migrateに生成するだけです。

rails db:migrateを実行すれば、db/schema.rbにactive_storage_attachmentsactive_storage_blobsというテーブル(を生成するためのコード)が追加されます。

rails consoleでattachメソッドを実行すると余計なログが出力されたままになる

これはなんだかよくわかりません。
以下のように添付を実行したとすると、一度nilが返ってきてirb(main):002:0>が表示されるのですが、すぐに次のログが出力されて、ログが出たままになってしまいます。

irb(main):001:0> user.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
.
.
Enqueued ActiveStorage::AnalyzeJob (Job ID: 0be98741-e05a-4d95-a6da-5ae2b2662cdb) to Async(default) with arguments: #<GlobalID:0x00007ff2ab2519a8 @uri=#<URI::GID gid://foo/ActiveStorage::Blob/1>>
=> nil
irb(main):002:0>   ActiveStorage::Blob Load (1.0ms)  SELECT  "active_storage_blobs".* FROM "active_storage_blobs" WHERE "active_storage_blobs"."id" = $1 LIMIT $2  [["id", 1], ["LIMIT", 1]]
.
.
Performed ActiveStorage::AnalyzeJob (Job ID: 0be98741-e05a-4d95-a6da-5ae2b2662cdb) from Async(default) in 36.57ms

戻り値を返すタイミングとログを吐き出すタイミングがずれてしまっているということでしょうか?
次のコマンドを打つためにいちいちCtrl-cとかやっているのでちょっと面倒です。

添付の有無はattach?を使う(nil?blank?present?では例外が発生してしまう)

ファイルが添付されているかどうかを確認してから次の処理を実行させるという条件分岐を書くときにハマりました。

GitHubのActiveStorageのReadmeにも、RailsGuidesにもさらっとattached?が説明してあるのですが、それに気が付かずにnil?blank?present?を使うと例外が発生して、うまくいきませんでした。

そんなときはattached?メソッドを使いましょう。

# 添付の有無を確認する
user.avatar.attached? # => true

テストにMinitestのfixuturesを使うとかなり大変

RSpecを使っている方には無縁かもしれませんが、Minitestのfixturesを使って添付に関連した箇所のテストをやろうとしてハマりました。

テスト用データとして、

  • users.ymlにUserモデルのテスト用オブジェクトを記載する
  • tmp/storageに添付用のファイルを置く

ところまでは問題ないのですが、このままではAttachmentモデルやBlobモデルのテストデータがないためにエラーとなってしまいます。

ActiveStorageの仕組みを考慮すれば、以下のstackoverflowの回答に説明してあるように無理やりfixturesを使ってテストすることも可能ではありますが、現実的ではないと思います。

仕方ないので、テストの中でいちいちattachメソッドで添付し、テストが終わったあとにpurgeメソッドで添付を外すということをやりました。
purgeで外さないと次にテストしたときにすでに添付状態になっていました)

ここはActiveStorageの改善を待ちつつ、なにかいい方法を考えていかないといけませんね。

HerokuではRailsアプリのstorageの下に置いた添付画像ファイルを表示できない

Heroku公式にもありますが、HerokuのEphemeral Diskという仕組みの影響でRailsアプリのstorageディレクトリに置いたファイルの添付がうまくいかず、画像ファイルをページに表示できませんでした。

これはクラウドストレージを使うしかないようです。

まとめ

初めてActiveStorageを使ってみましたが、仕組み自体はシンプルなのでこれからも使っていこうと思います。ただ現時点では至れり尽くせりという状態ではない部分もありますので、その点はご注意ください。

コードを読んだり、図を書いたりしていい勉強になりました。