遅ればせながら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_attachments
、active_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を使ってみましたが、仕組み自体はシンプルなのでこれからも使っていこうと思います。ただ現時点では至れり尽くせりという状態ではない部分もありますので、その点はご注意ください。
コードを読んだり、図を書いたりしていい勉強になりました。