このガイドはActive Recordモデルにファイルを添付する方法について説明します。
このガイドを読むと下記の内容が理解できるでしょう。
Active StorageとはAmazon S3、Google Cloud Storage、Microsoft Azure Storageのような クラウドストレージサービスへのファイルのアップロードとそれらのファイルをActive Recordオブジェクトに添付することを容易にします。 開発およびテスト用のローカルディスクベースのサービスが付属しており、ファイルをバックアップおよび移行用の従属サービスにミラーリングすることができます。
Active Storageを使用すると、アプリケーションはImageMagickで画像のアップロードを変換し、 PDFやビデオなどの非画像アップロードの画像表現を生成し、任意のファイルからメタデータを抽出することができます。
Active Storageは、アプリケーションのデータベースで active_storage_blobs
とactive_storage_attachments
という名前の2つのテーブルを使用します。
新規アプリケーション作成後または既存のアプリケーションをRails 5.2にアップグレードした後に、rails active_storage:install
を実行して、これらのテーブルを作成するmigrationファイルを作成します。
migrationファイルを実行するにはrails db:migrate
を使用してください。
Active Storageのサービスをconfig/storage.yml
で宣言してください。 アプリケーションが使用するサービスごとに、名前と必要な構成を指定します。
次の例では、local
、test
、amazon
という3つのサービスを宣言しています。
local: service: Disk root: <%= Rails.root.join("storage") %> test: service: Disk root: <%= Rails.root.join("tmp/storage") %> amazon: service: S3 access_key_id: "" secret_access_key: ""
Rails.application.config.active_storage.service
を設定することによって、どのサービスを使うべきかをActive Storageに教えてください。
それぞれの環境では異なるサービスが使用される可能性が高いため、これを環境ごとに行うことをお勧めします。 開発環境の前の例のディスクサービスを使用するには、config/environments/development.rb
に以下を追加します。
# Store files locally. config.active_storage.service = :local
本番環境でAmazon S3を利用するにはconfig/environments/production.rb
に以下を追加します。
# Store files on Amazon S3. config.active_storage.service = :amazon
内蔵のサービスアダプタ(Disk
やS3
など)とそれに必要な設定の詳細については、引き続きお読みください。
config/storage.yml
にDiskサービスを宣言してください。
local: service: Disk root: <%= Rails.root.join("storage") %>
config/storage.yml
にS3サービスを宣言してください。
amazon: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: ""
また、Gemfile
にS3クライアントのgemを追加してください。
gem "aws-sdk-s3", require: false
config/storage.yml
にAzure Storageサービスを宣言してください。
azure: service: AzureStorage path: "" storage_account_name: "" storage_access_key: "" container: ""
また、Gemfile
にMicrosoft Azure Storageクライアントのgemを追加してください。
gem "azure-storage", require: false
config/storage.yml
にGoogle Cloud Storageサービスを宣言してください。
google: service: GCS keyfile: { type: "service_account", project_id: "", private_key_id: "", private_key: "", client_email: "", client_id: "", auth_uri: "https://accounts.google.com/o/oauth2/auth", token_uri: "https://accounts.google.com/o/oauth2/token", auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs", client_x509_cert_url: "" } project: "" bucket: ""
また、Gemfile
にGoogle Cloud Storageクライアントのgemを追加してください。
gem "google-cloud-storage", "~> 1.3", require: false
ミラーサービスを定義することで、複数のサービスを同期させることができます。ファイルがアップロードまたは削除されると、 ミラー化されたすべてのサービスで実行されます。ミラーリングされたサービスを使用して、プロダクション内のサービス間の移行を容易にすることができます。 新しいサービスへのミラーリングを開始したり、既存のファイルを古いサービスから新しいものにコピーしたり、新しいサービスにオールインすることができます。 上記のように使用する各サービスを定義し、ミラー化されたサービスから参照します。
s3_west_coast: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" s3_east_coast: service: S3 access_key_id: "" secret_access_key: "" region: "" bucket: "" production: service: Mirror primary: s3_east_coast mirrors: - s3_west_coast
ファイルはプライマリサービスから提供されます。
has_one_attached
has_one_attached
マクロは、レコードとファイルの間に1対1のマッピングを設定します。各レコードには1つのファイルを添付できます。
たとえば、アプリケーションにUser
モデルがあるとします。各userにavatarを持たせたい場合は、以下のようにUser
モデルを定義してください。
class User < ApplicationRecord has_one_attached :avatar end
avatarと一緒にuserを作成することができます。
class SignupController < ApplicationController def create user = User.create!(user_params) session[:user_id] = user.id redirect_to root_path end private def user_params params.require(:user).permit(:email_address, :password, :avatar) end end
既存のuserにavatarを添付するにはavatar.attach
を呼び出します。
Current.user.avatar.attach(params[:avatar])
avatar.attached?
で特定のuserがavatarを持っているかどうかを判断します。
Current.user.avatar.attached?
has_many_attached
has_many_attached
マクロは、レコードとファイルの間に1対多の関係を設定します。各レコードには、多数のファイルを添付することができます。
たとえば、アプリケーションにMessage
モデルがあるとします。それぞれのメッセージに多くのイメージを含めるには、次のようにMessage
モデルを定義します.
class Message < ApplicationRecord has_many_attached :images end
imagesと一緒にmessageを作成することができます。
class MessagesController < ApplicationController def create message = Message.create!(message_params) redirect_to message end private def message_params params.require(:message).permit(:title, :content, images: []) end end
images.attach
を呼び出して、新しいimageを既存のmessageに追加します。
@message.images.attach(params[:images])
image.attached?
で特定のmessageがimageを持っているかどうかを判断します。
@message.images.attached?
モデルから添付ファイルを削除するには、添付ファイルに対して purge
を呼び出します。
Active Jobを使用するようにアプリケーションが設定されている場合は、バックグラウンドで削除を実行できます。消去すると、BLOBとファイルがストレージサービスから削除されます。
# avatarと実際のリソースファイルを同期的に破棄します。 user.avatar.purge # Active Jobを介して、関連付けられているモデルと実際のリソースファイルを非同期で破棄します。 user.avatar.purge_later
アプリケーションを指すblobの永続URLを生成します。アクセス時には、実際のサービスエンドポイントへのリダイレクトが返されます。 このインダイレクションはパブリックURLを実際のURLと切り離し、たとえば、高可用性のために異なるサービスの添付ファイルをミラーリングすることを可能にします。 リダイレクトのHTTPの有効期限は5分です。
url_for(user.avatar)
ダウンロードリンクを作成するには、rails_blob_ {path | url}
ヘルパーを使用してください。このヘルパーを使用すると、処理を設定できます。
rails_blob_path(user.avatar, disposition: "attachment")
画像のバリエーションを作成するには、Blobでvariant
を呼び出します。
MiniMagick でサポートされている変換をメソッドに渡すことができます。
バリアントを有効にするには、mini_magick
をGemfile
に追加してください:
gem 'mini_magick'
ブラウザがバリアントURLにヒットすると、Active Storageは元のBLOBを指定したフォーマットに遅延変換し、新しいサービスロケーションにリダイレクトします。
<%= image_tag user.avatar.variant(resize: "100x100") %>
非画像ファイルの一部はプレビューすることができます。つまり、画像として表示することができます。 たとえば、最初のフレームを抽出してビデオファイルをプレビューすることができます。アウトオブボックスで、Active StorageはビデオとPDFドキュメントのプレビューをサポートしています。
<ul> <% @message.files.each do |file| %> <li> <%= image_tag file.preview(resize: "100x100>") %> </li> <% end %> </ul>
プレビューを抽出するにはサードパーティのアプリケーション、ビデオの場合はffmpeg
、PDFの場合はmutool
が必要です。
これらのライブラリはRailsでは提供されていません。組み込みのプレビューアを使用するには、それらを自分でインストールする必要があります。
サードパーティのソフトウェアをインストールして使用する前に、ライセンスの影響を理解していることを確認してください。
Active Storageは、付属のJavaScriptライブラリを使用して、クライアントからクラウドへの直接アップロードをサポートします。
アプリケーションのJavaScriptバンドルにactivestorage.js
を含めます。
アセットパイプラインを使います。
//= require activestorage
npmパッケージを使います。
import * as ActiveStorage from "activestorage" ActiveStorage.start()
ダイレクトアップロードURLでファイル入力に注釈を付けます。
<%= form.file_field :attachments, multiple: true, direct_upload: true %>
それだけです! アップロードはフォーム提出時に開始されます。
Event name | Event target | Event data (event.detail ) |
Description |
---|---|---|---|
direct-uploads:start |
<form> |
None | ダイレクトアップロードフィールドのファイルを含むフォームが送信された。 |
direct-upload:initialize |
<input> |
{id, file} |
フォーム提出後のすべてのファイルにディスパッチされる。 |
direct-upload:start |
<input> |
{id, file} |
直接アップロードが開始されている。 |
direct-upload:before-blob-request |
<input> |
{id, file, xhr} |
アプリケーションにダイレクトアップロードメタデータを要求する前。 |
direct-upload:before-storage-request |
<input> |
{id, file, xhr} |
ファイルを保存するリクエストを出す前。 |
direct-upload:progress |
<input> |
{id, file, progress} |
ファイルを保存する要求が進行中。 |
direct-upload:error |
<input> |
{id, file, error} |
エラーが発生した。 このイベントがキャンセルされない限り、alert が表示される。 |
direct-upload:end |
<input> |
{id, file} |
ダイレクトアップロードが終了した。 |
direct-uploads:end |
<form> |
None | すべてのダイレクトアップロードが終了した。 |
これらのイベントを使用して、アップロードの進行状況を表示できます。
アップロードされたファイルをフォームに表示するには
// direct_uploads.js addEventListener("direct-upload:initialize", event => { const { target, detail } = event const { id, file } = detail target.insertAdjacentHTML("beforebegin", ` <div id="direct-upload-${id}" class="direct-upload direct-upload--pending"> <div id="direct-upload-progress-${id}" class="direct-upload__progress" style="width: 0%"></div> <span class="direct-upload__filename">${file.name}</span> </div> `) }) addEventListener("direct-upload:start", event => { const { id } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.remove("direct-upload--pending") }) addEventListener("direct-upload:progress", event => { const { id, progress } = event.detail const progressElement = document.getElementById(`direct-upload-progress-${id}`) progressElement.style.width = `${progress}%` }) addEventListener("direct-upload:error", event => { event.preventDefault() const { id, error } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.add("direct-upload--error") element.setAttribute("title", error) }) addEventListener("direct-upload:end", event => { const { id } = event.detail const element = document.getElementById(`direct-upload-${id}`) element.classList.add("direct-upload--complete") })
スタイルを追加
/* direct_uploads.css */ .direct-upload { display: inline-block; position: relative; padding: 2px 4px; margin: 0 3px 3px 0; border: 1px solid rgba(0, 0, 0, 0.3); border-radius: 3px; font-size: 11px; line-height: 13px; } .direct-upload--pending { opacity: 0.6; } .direct-upload__progress { position: absolute; top: 0; left: 0; bottom: 0; opacity: 0.2; background: #0076ff; transition: width 120ms ease-out, opacity 60ms 60ms ease-in; transform: translate3d(0, 0, 0); } .direct-upload--complete .direct-upload__progress { opacity: 0.4; } .direct-upload--error { border-color: red; } input[type=file][data-direct-upload-url][disabled] { display: none; }
システムテストでは、トランザクションをロールバックしてテストデータをクリーンアップします。
destroyはオブジェクトに対して呼び出されないため、添付ファイルは決してクリーンアップされません。
ファイルを消去したい場合は、after_teardown
コールバックで行うことができます。
ここでは、テスト中に作成されたすべての接続が確実に行われ、アクティブストレージからファイルを見つけることができないというエラーは表示されません。
class ApplicationSystemTestCase < ActionDispatch::SystemTestCase driven_by :selenium, using: :chrome, screen_size: [1400, 1400] def remove_uploaded_files FileUtils.rm_rf("#{Rails.root}/storage_test") end def after_teardown super remove_uploaded_files end end
システムが添付ファイルを含むモデルの削除を検証し、アクティブジョブを使用している場合は、インラインキューアダプタを使用するようにテスト環境を設定して、未知の時間ではなく即時にパージジョブを実行します。
また、テスト環境で別のサービス定義を使用して、開発中に作成したファイルをテストで削除しないようにすることもできます。
# Use inline job processing to make things happen immediately config.active_job.queue_adapter = :inline # Separate file storage in the test environment config.active_storage.service = :local_test
これら以外のクラウドサービスをサポートする必要がある場合は、サービスを実装する必要があります。 各サービスは、ファイルをアップロードしてクラウドにダウンロードするのに必要なメソッドを実装することで、ActiveStorage::Serviceを拡張します 。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。