Rails way なファイルアップローダActiveStorageを調べてみた

こんにちは。スタメンエンジニアの松谷です。

2018/1/27(土)に、弊社で Rails5.2 の新機能 ActiveStorage を題材にした開発イベントを開催しました。 (【開発イベントレポート】ActiveStorageを使ってWikiアプリを作ろう!) そのときに調べてわかったことを書いていきたいと思います。

ActiveStorageとは

ActiveStorageとは、Rails5.2から利用可能なファイルアップローダです。クラウドストレージサービスへのファイルのアップロードとそれらのファイルをActive Recordオブジェクトに添付することを簡単に行えます。

今のところ、Amazon S3Google Cloud Storage、Microsoft Azure Storage のクラウドストレージが標準で対応されています。またダイレクトアップロードという、ブラウザから直接クラウドストレージへアップロードする機能や、複数のストレージサービスを同期できるミラーリング機能を備えています。

ActiveStorageを使う準備

rails newRailsアプリケーションを作成します。

$ bin/rails new --skip-turbolinks --skip-test .

run  bundle exec spring binstub --all
* bin/rake: spring inserted
* bin/rails: spring inserted
       rails  active_storage:install
Copied migration 20180128024947_create_active_storage_tables.active_storage.rb from active_storage

この時 active_storage:installが実行され、20180128024947_create_active_storage_tables.active_storage.rbというマイグレーションファイルが作成されています。 このマイグレーションファイルから、active_storage_blobsactive_storage_attachments というテーブルを作成することがわかります。 データベースの作成とマイグレーションを実行します。

$ bin/rails db:create
...
$ bin/rails db:migrate
== 20180128024947 CreateActiveStorageTables: migrating ========================
-- create_table(:active_storage_blobs)
   -> 0.0011s
-- create_table(:active_storage_attachments)
   -> 0.0012s
== 20180128024947 CreateActiveStorageTables: migrated (0.0024s) ===============

このテーブルに対応するモデルが以下の2つのモデルになります。 * ActiveStorage::Blob * ActiveStorage::Attachment

ActiveStorageの内部実装を覗いてみる

ActiveStorage::Blob

ActiveStorage::Blobレコードには、ファイルに関するメタデータ、Content-Type、サイズ、クラウドストレージの識別用のkeyを含まれます。 ActiveStorage::Blobのレコードは、次の2つのメソッドで作成できます。

  • `create_after_upload!`

ファイルがクラウドストレージにアップロードされた後に作成します。

  • `create_before_direct_upload!`

クライアント側からファイルを直接クラウドサービスにアップロードする前に作成します。 この方法では、ActiveStorage付属のJavaScriptの実装が必要になります。ブラウザから直接クラウドストレージにアップロードするため、ファイルサイズが大きくても、アプリケーションサーバーに負担をかけることはなく、また高速です。

ActiveStorage::Attachment

ActiveStorage::Attachment は ActiveStorage::Blobと他のモデルを関連づけるための中間テーブルです。これを利用すると例えば、同じActiveStorage::Blobオブジェクトに異なるレコードを関連づけることも可能です。(その場合は、has_many_attached:users、dependent:falseを宣言して、1つのレコードを削除してもActiveStorage::Blobレコードは削除しないようにしたほうが良いでしょう。)

例として、Userというモデルに複数のファイルをアップロードする場合を示します。 has_many_attachedを記述することで、Userのレコードとファイルの間に1対多の関係を設定します。

class User < ApplicationRecord
  has_many_attached :images
end

以下のようにコントローラに記述することで、imagesと一緒にuserを作成することができます。

class UsersController < ApplicationController
  def create
    user = User.create!(user_params)
    redirect_to user
  end
 
  private
    def user_params
      params.require(:message).permit(:title, :content, images: [])
    end
end

viewでの画像の表示は以下のようにします。

...省略...
<% @user.images.each do |image| %>
  <%= image_tag url_for(image) %>
<% end %>
...省略...

また、user.images.attach(*attachables)を呼び出して、新しいファイルを既存のuserに追加できます。引数 *attachables にはActiveStorage::Blobオブジェクト、File オブジェクト、ActionDispatch::Http::UploadedFileオブジェクトなどを渡すことができます。用途としては、例えばUserのレコードを作成する前に画像を先にアップロードして、Userのレコード作成時にファイルと関連付けることができます。

このように、Userモデルのカラムにファイル用のカラムを追加する必要がなく、has_many_attachedを一行書くだけここまでの機能が使えるようになるのはとても便利です。

各モデルの関係性

ここで各モデルの関係性を整理してみます。実際にRails(ver 5.2.0.beta2)のソースコードを読んでみるとモデル間の関係性がわかると思います。

class ActiveStorage::Attachment < ActiveRecord::Base
...略...
  belongs_to :record, polymorphic: true, touch: true  # この例だとrecordは User のレコードを表しています
  belongs_to :blob, class_name: "ActiveStorage::Blob"
...略...
end
class ActiveStorage::Blob < ActiveRecord::Base
...略...
  has_many :attachments
...略...
end

上記のコードからわかるように、ActiveStorageはポリモーフィック関連を使って、ファイルの処理を全てActiveStorage::Attachment、ActiveStorage::Blobに任せる実装になっています。

終わりに

今回は、Rails5.2の新機能 ActiveStorage の機能の一部を紹介しました。まだ正式リリース前なので、今回紹介した内容とは異なってくる可能性はありますが、先取りして機能が開発されていく過程を追うことはとても楽しいです。

今まではRailsアプリでファイルアップロード周りは、それぞれのプロジェクトに合わせたライブラリを選択していたと思いますが、ActiveStorageという Rails way に乗って素早く開発できるようになればいいなと思っています。

弊社は今まで、Active Recordオブジェクトにファイルを関連付ける場合は、新しくファイル用のモデルを作成して、そのモデルに、ファイル情報を格納していました。今回それがActivieStorage::Blob、ActiveStorage::Attachmentに統一されることで、見通しがよくなりそうです。ですが、バリデーションなどファイルの種類毎に異なる実装もActiveStorage::Blob、ActiveStorage::Attachmentに集約してしまうと、柔軟に要件に対応できません。

ここの解決作は未だ見えていないので、引き続き、ファイルアップロード周りをActiveStorageに置き換えられるかどうかを検証していきます。

  • スタメンでは一緒に働くメンバーも募集しております!是非ご覧ください。

スタメンの採用情報