edge
詳細はrubyonrails.orgで: もっとRuby on Rails

Active Storageの概要

このガイドでは、Active Recordモデルにファイルを添付する方法について説明します。

このガイドを読み終えると、以下のことがわかるようになります:

1 Active Storageとは何ですか?

Active Storageは、Amazon S3、Google Cloud Storage、またはMicrosoft Azure Storageなどのクラウドストレージサービスにファイルをアップロードし、それらのファイルをActive Recordオブジェクトに添付することを容易にします。開発およびテスト用のローカルディスクベースのサービスを提供し、バックアップやマイグレーションのためにファイルを従属サービスにミラーリングすることもサポートしています。

Active Storageを使用すると、アプリケーションは画像のアップロードを変換したり、PDFやビデオなどの非画像のアップロードの画像表現を生成したり、任意のファイルからメタデータを抽出したりすることができます。

1.1 必要条件

Active Storageのさまざまな機能には、Railsがインストールしないサードパーティのソフトウェアが必要であり、別途インストールする必要があります:

  • 画像の解析と変換には、libvips v8.6+またはImageMagickが必要です。
  • ビデオのプレビューには、ffmpeg v3.4+が必要であり、ビデオ/オーディオの解析にはffprobeが必要です。
  • PDFのプレビューには、popplerまたはmuPDFが必要です。

画像の解析と変換には、image_processingジェムも必要です。Gemfileでコメントを解除するか、必要に応じて追加してください:

gem "image_processing", ">= 1.2"

libvipsはImageMagickよりもよく知られており、より広く利用できます。ただし、libvipsは最大10倍高速で、1/10のメモリを消費することができます。JPEGファイルの場合、これはlibjpeg-devlibjpeg-turbo-devで置き換えることでさらに改善できます。libjpeg-turbo-dev2-7倍高速です。

サードパーティのソフトウェアをインストールして使用する前に、そのライセンスの影響を理解してください。特に、MuPDFはAGPLの下でライセンスされており、一部の使用には商用ライセンスが必要です。

2 セットアップ

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

これにより、設定がセットアップされ、Active Storageが使用する3つのテーブル、active_storage_blobsactive_storage_attachments、およびactive_storage_variant_recordsが作成されます。

テーブル 目的
active_storage_blobs ファイルのアップロードに関するデータ(ファイル名やコンテンツタイプなど)を格納します。
active_storage_attachments モデルとブロブを接続するポリモーフィックな結合テーブルです。モデルのクラス名が変更された場合、このテーブルの下にあるrecord_typeをモデルの新しいクラス名に更新するためにマイグレーションを実行する必要があります。
active_storage_variant_records バリアントのトラッキングが有効になっている場合、生成された各バリアントのレコードを格納します。

モデルのプライマリキーとして整数ではなくUUIDを使用している場合は、Rails.application.config.generators { |g| g.orm :active_record, primary_key_type: :uuid }を設定する必要があります。

config/storage.ymlでActive Storageサービスを宣言します。アプリケーションが使用する各サービスに名前と必要な設定を提供します。以下の例では、localtest、および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: ""
  bucket: ""
  region: "" # 例:'us-east-1'

Active Storageが使用するサービスを設定するには、Rails.application.config.active_storage.serviceを設定します。各環境で異なるサービスを使用する可能性があるため、環境ごとに設定することをおすすめします。前の例のディスクサービスを開発環境で使用するには、config/environments/development.rbに次の設定を追加します:

# ファイルをローカルに保存します。
config.active_storage.service = :local

本番環境でS3サービスを使用するには、config/environments/production.rbに次の設定を追加します:

# ファイルをAmazon S3に保存します。
config.active_storage.service = :amazon

テスト時にテストサービスを使用するには、config/environments/test.rbに次の設定を追加します:

# アップロードされたファイルを一時ディレクトリに保存します。
config.active_storage.service = :test

環境ごとに異なる設定ファイルが優先されます。たとえば、本番環境では、config/storage/production.ymlファイル(存在する場合)がconfig/storage.ymlファイルより優先されます。

誤って本番データを破壊するリスクをさらに減らすために、バケット名にRails.envを使用することをおすすめします。

amazon:
  service: S3
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

google:
  service: GCS
  # ...
  bucket: your_own_bucket-<%= Rails.env %>

azure:
  service: AzureStorage
  # ...
  container: your_container_name-<%= Rails.env %>

組み込みのサービスアダプタ(例:DiskおよびS3)とそれらが必要とする設定についての詳細情報については、以下をお読みください。

2.1 ディスクサービス

config/storage.ymlでディスクサービスを宣言します:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

2.2 S3サービス(Amazon S3およびS3互換API)

Amazon S3に接続するためには、config/storage.ymlでS3サービスを宣言します:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""

オプションでクライアントとアップロードのオプションを指定できます:

amazon:
  service: S3
  access_key_id: ""
  secret_access_key: ""
  region: ""
  bucket: ""
  http_open_timeout: 0
  http_read_timeout: 0
  retry_limit: 0
  upload:
    server_side_encryption: "" # 'aws:kms'または'AES256'
    cache_control: "private, max-age=<%= 1.day.to_i %>"

アプリケーションに適切なクライアントHTTPタイムアウトとリトライ制限を設定してください。特定の障害シナリオでは、デフォルトのAWSクライアント設定により、接続が数分間保持され、リクエストがキューに入る可能性があります。

aws-sdk-s3 gemをGemfileに追加してください:

gem "aws-sdk-s3", require: false

注意:Active Storageのコア機能には、次のアクセス許可が必要です:s3:ListBuckets3:PutObjects3:GetObject、およびs3:DeleteObject公開アクセスでは、さらにs3:PutObjectAclが必要です。ACLの設定などの追加のアップロードオプションが構成されている場合、追加のアクセス許可が必要になる場合があります。

注意:環境変数、標準のSDK設定ファイル、プロファイル、IAMインスタンスプロファイル、またはタスクロールを使用する場合は、上記の例でaccess_key_idsecret_access_key、およびregionキーを省略できます。S3サービスは、AWS SDKドキュメントで説明されているすべての認証オプションをサポートしています。

DigitalOcean SpacesなどのS3互換オブジェクトストレージAPIに接続するには、endpointを指定します:

digitalocean:
  service: S3
  endpoint: https://nyc3.digitaloceanspaces.com
  access_key_id: ...
  secret_access_key: ...
  # ...およびその他のオプション

他にも多くのオプションがあります。AWS S3 Clientのドキュメントで確認できます。

2.3 Microsoft Azure Storage Service

config/storage.ymlでAzure Storageサービスを宣言します:

azure:
  service: AzureStorage
  storage_account_name: ""
  storage_access_key: ""
  container: ""

azure-storage-blob gemをGemfileに追加してください:

gem "azure-storage-blob", "~> 2.0", require: false

2.4 Google Cloud Storage Service

config/storage.ymlでGoogle Cloud Storageサービスを宣言します:

google:
  service: GCS
  credentials: <%= Rails.root.join("path/to/keyfile.json") %>
  project: ""
  bucket: ""

キーファイルのパスの代わりに、クレデンシャルのハッシュを指定することもできます:

google:
  service: GCS
  credentials:
    type: "service_account"
    project_id: ""
    private_key_id: <%= Rails.application.credentials.dig(:gcs, :private_key_id) %>
    private_key: <%= Rails.application.credentials.dig(:gcs, :private_key).dump %>
    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: ""

アップロードされたアセットに設定するためのCache-Controlメタデータをオプションで指定できます:

google:
  service: GCS
  ...
  cache_control: "public, max-age=3600"

URLに署名する際にcredentialsの代わりにIAMを使用することもできます。これは、GKEアプリケーションをワークロードアイデンティティで認証している場合に便利です。詳細については、このGoogle Cloudブログ記事を参照してください。

google:
  service: GCS
  ...
  iam: true

URLに署名する際に特定のGSAを使用することもできます。IAMを使用する場合、メタデータサーバはGSAのメールアドレスを取得するために連絡されますが、このメタデータサーバは常に存在しない(ローカルテストなど)ため、デフォルト以外のGSAを使用する場合があります。

google:
  service: GCS
  ...
  iam: true
  gsa_email: "[email protected]"

google-cloud-storage gemをGemfileに追加してください:

gem "google-cloud-storage", "~> 1.11", require: false

2.5 ミラーサービス

ミラーサービスを定義することで、複数のサービスを同期させることができます。ミラーサービスは、アップロードと削除を2つ以上の下位サービスに複製します。

ミラーサービスは、本番環境でのサービス間の移行中に一時的に使用することを想定しています。新しいサービスにミラーリングを開始し、古いサービスから事前に存在するファイルを新しいサービスにコピーし、その後新しいサービスに完全に移行します。

注意:ミラーリングはアトミックではありません。アップロードがプライマリサービスで成功し、下位サービスのいずれかで失敗する可能性があります。新しいサービスに完全に移行する前に、すべてのファイルがコピーされていることを確認してください。

上記で説明したように、ミラーリングしたい各サービスを定義してください。ミラーサービスを定義する際に、名前で参照してください:

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

すべてのセカンダリサービスはアップロードを受け取りますが、ダウンロードは常にプライマリサービスで処理されます。

ミラーサービスは直接アップロードと互換性があります。新しいファイルは直接プライマリサービスにアップロードされます。直接アップロードされたファイルがレコードに添付されると、バックグラウンドジョブがエンキューされ、セカンダリサービスにコピーされます。

2.6 パブリックアクセス

デフォルトでは、Active Storageはサービスへのプライベートアクセスを前提としています。これは、ブロブに対して署名付きの一度限りのURLを生成することを意味します。ブロブをパブリックにアクセス可能にする場合は、アプリのconfig/storage.ymlpublic: trueを指定します。

gcs: &gcs
  service: GCS
  project: ""

private_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/private_key.json") %>
  bucket: ""

public_gcs:
  <<: *gcs
  credentials: <%= Rails.root.join("path/to/public_key.json") %>
  bucket: ""
  public: true

バケットがパブリックアクセスに適切に設定されていることを確認してください。Amazon S3Google Cloud Storage、およびMicrosoft Azureのストレージサービスに対してパブリックリード権限を有効にする方法についてのドキュメントを参照してください。Amazon S3では、s3:PutObjectAclの権限が必要です。

public: trueを使用するように既存のアプリケーションを変換する場合は、切り替える前にバケット内のすべての個々のファイルをパブリックリーダブルに更新する必要があります。

3 レコードにファイルを添付する

3.1 has_one_attached

has_one_attachedマクロは、レコードとファイルの1対1のマッピングを設定します。各レコードには1つのファイルが添付されることができます。

たとえば、アプリケーションにUserモデルがあるとします。各ユーザーにアバターを持たせたい場合は、次のようにUserモデルを定義します。

class User < ApplicationRecord
  has_one_attached :avatar
end

または、Rails 6.0+を使用している場合は、次のようにモデルジェネレータコマンドを実行できます。

bin/rails generate model User avatar:attachment

アバターを持つユーザーを作成できます。

<%= form.file_field :avatar %>
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

既存のユーザーにアバターを添付するには、avatar.attachを呼び出します。

user.avatar.attach(params[:avatar])

特定のユーザーがアバターを持っているかどうかを判断するには、avatar.attached?を呼び出します。

user.avatar.attached?

一部の場合では、特定の添付ファイルに対してデフォルトのサービスをオーバーライドしたい場合があります。serviceオプションを使用して、添付ごとに特定のサービスを設定できます。

class User < ApplicationRecord
  has_one_attached :avatar, service: :s3
end

variantメソッドを使用して、特定の添付ファイルごとに特定のバリアントを設定できます。

class User < ApplicationRecord
  has_one_attached :avatar do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

アバターのサムネイルバリアントを取得するには、avatar.variant(:thumb)を呼び出します。

<%= image_tag user.avatar.variant(:thumb) %>

プレビューに特定のバリアントを使用することもできます。

class User < ApplicationRecord
  has_one_attached :video do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end
<%= image_tag user.video.preview(:thumb) %>

3.2 has_many_attached

has_many_attachedマクロは、レコードとファイルの1対多の関係を設定します。各レコードには複数のファイルが添付されることができます。

たとえば、アプリケーションにMessageモデルがあるとします。各メッセージに複数の画像を持たせたい場合は、次のようにMessageモデルを定義します。

class Message < ApplicationRecord
  has_many_attached :images
end

または、Rails 6.0+を使用している場合は、次のようにモデルジェネレータコマンドを実行できます。

bin/rails generate model Message images:attachments

画像を持つメッセージを作成できます。

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を呼び出します。

@message.images.attach(params[:images])

特定のメッセージに画像があるかどうかを判断するには、images.attached?を呼び出します。

@message.images.attached?

デフォルトのサービスをオーバーライドするには、has_one_attachedと同じ方法でserviceオプションを使用します。

class Message < ApplicationRecord
  has_many_attached :images, service: :s3
end

特定のバリアントを設定するには、has_one_attachedと同じ方法で、yieldされたattachableオブジェクトにvariantメソッドを呼び出します。

class Message < ApplicationRecord
  has_many_attached :images do |attachable|
    attachable.variant :thumb, resize_to_limit: [100, 100]
  end
end

3.3 ファイル/IOオブジェクトの添付

HTTPリクエスト経由で到着しないファイルを添付する必要がある場合があります。たとえば、ディスク上で生成したファイルや、ユーザーが提出したURLからダウンロードしたファイルを添付する場合があります。また、モデルテストでフィクスチャファイルを添付する場合もあります。その場合は、少なくともオープンなIOオブジェクトとファイル名を含むハッシュを提供します。

@message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf')

可能な場合は、コンテンツタイプも指定してください。Active Storageはファイルのデータからコンテンツタイプを判断しようとします。それができない場合は、提供されたコンテンツタイプを使用します。 ruby @message.images.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'application/pdf')

content_typeと一緒にidentify: falseを渡すことで、データからのコンテンツタイプの推論をバイパスすることができます。

@message.images.attach(
  io: File.open('/path/to/file'),
  filename: 'file.pdf',
  content_type: 'application/pdf',
  identify: false
)

コンテンツタイプを提供せず、Active Storageがファイルのコンテンツタイプを自動的に判断できない場合、デフォルトでapplication/octet-streamになります。

4 ファイルの削除

モデルから添付ファイルを削除するには、添付ファイルに対してpurgeを呼び出します。アプリケーションがActive Jobを使用するように設定されている場合、削除はpurge_laterを呼び出すことでバックグラウンドで行うこともできます。purgeはブロブとストレージサービスからファイルを削除します。

# アバターと実際のリソースファイルを同期的に破棄します。
user.avatar.purge

# 関連するモデルと実際のリソースファイルを非同期に破棄します(Active Jobを使用)。
user.avatar.purge_later

5 ファイルの提供

Active Storageは2つの方法でファイルを提供することができます:リダイレクトとプロキシ。

警告:すべてのActive Storageコントローラはデフォルトで公開されています。生成されたURLは推測が難しく、意図的に永続的です。ファイルがより高いレベルの保護を必要とする場合は、認証済みコントローラを実装することを検討してください。

5.1 リダイレクトモード

ブロブに対して永続的なURLを生成するには、url_forビューヘルパーにブロブを渡します。これにより、ブロブのsigned_idを使用したURLが生成され、ブロブのRedirectControllerにルーティングされます。

url_for(user.avatar)
# => /rails/active_storage/blobs/:signed_id/my-avatar.png

RedirectControllerは実際のサービスエンドポイントにリダイレクトします。この間接的な方法により、サービスURLと実際のURLが切り離され、例えば高可用性のために異なるサービスで添付ファイルをミラーリングすることができます。リダイレクトはHTTPの有効期限が5分です。

ダウンロードリンクを作成するには、rails_blob_{path|url}ヘルパーを使用します。このヘルパーを使用すると、dispositionを設定することができます。

rails_blob_path(user.avatar, disposition: "attachment")

警告:XSS攻撃を防ぐため、Active Storageは一部の種類のファイルに対してContent-Dispositionヘッダを"attachment"に強制します。この動作を変更するには、Configuring Rails Applicationsの利用可能な設定オプションを参照してください。

コントローラ/ビューコンテキストの外部からリンクを作成する必要がある場合(バックグラウンドジョブ、Cronジョブなど)、次のようにrails_blob_pathにアクセスできます。

Rails.application.routes.url_helpers.rails_blob_path(user.avatar, only_path: true)

5.2 プロキシモード

オプションで、ファイルをプロキシすることもできます。これは、リクエストに応じてアプリケーションサーバーがストレージサービスからファイルデータをダウンロードすることを意味します。これはCDNからファイルを提供するために便利です。

Active Storageをデフォルトでプロキシモードを使用するように設定することができます。

# config/initializers/active_storage.rb
Rails.application.config.active_storage.resolve_model_to_route = :rails_storage_proxy

または、特定の添付ファイルを明示的にプロキシする場合は、rails_storage_proxy_pathおよびrails_storage_proxy_urlという形式のURLヘルパーを使用できます。

<%= image_tag rails_storage_proxy_path(@user.avatar) %>

5.2.1 Active Storageの前にCDNを配置する

さらに、Active Storageの添付ファイルにCDNを使用するには、プロキシモードでURLを生成する必要があります。これにより、CDNが追加の設定なしで添付ファイルをキャッシュできるようになります。これはデフォルトで動作するため、Active StorageのプロキシコントローラーがレスポンスをキャッシュするようにCDNにHTTPヘッダを設定します。

また、生成されたURLがアプリのホストではなくCDNホストを使用するようにする必要もあります。これを実現するための複数の方法がありますが、一般的にはconfig/routes.rbファイルを調整して、添付ファイルとそのバリエーションの正しいURLを生成できるようにします。例えば、次のように追加できます。

# config/routes.rb
direct :cdn_image do |model, options|
  expires_in = options.delete(:expires_in) { ActiveStorage.urls_expire_in }

  if model.respond_to?(:signed_id)
    route_for(
      :rails_service_blob_proxy,
      model.signed_id(expires_in: expires_in),
      model.filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  else
    signed_blob_id = model.blob.signed_id(expires_in: expires_in)
    variation_key  = model.variation.key
    filename       = model.blob.filename

    route_for(
      :rails_blob_representation_proxy,
      signed_blob_id,
      variation_key,
      filename,
      options.merge(host: ENV['CDN_HOST'])
    )
  end
end

そして、次のようにルートを生成します。

<%= cdn_image_url(user.avatar.variant(resize_to_limit: [128, 128])) %>

5.3 認証済みコントローラ

すべてのActive Storageコントローラはデフォルトで公開されています。生成されたURLは単純なsigned_idを使用しており、推測が難しくなっていますが、永続的です。ログインが必要な場合でも、ApplicationControllerbefore_actionで要求される場合でも、ブロブのURLを知っている人はアクセスできます。ファイルがより高いレベルの保護を必要とする場合は、ActiveStorage::Blobs::RedirectController, ActiveStorage::Blobs::ProxyController, ActiveStorage::Representations::RedirectController、およびActiveStorage::Representations::ProxyControllerに基づいて独自の認証済みコントローラを実装することができます。

アカウントが自分のロゴにアクセスできるようにするには、次のようにします: ```ruby

config/routes.rb

resource :account do resource :logo end ```

# app/controllers/logos_controller.rb
class LogosController < ApplicationController
  # ApplicationControllerを介して:
  # include Authenticate, SetCurrentAccount

  def show
    redirect_to Current.account.logo.url
  end
end
<%= image_tag account_logo_path %>

そして、Active Storageのデフォルトのルートを無効にするために、次の設定を行ってください。

config.active_storage.draw_routes = false

これにより、ファイルが公開可能なURLでアクセスされるのを防ぐことができます。

6 ファイルのダウンロード

アップロードされたblobを処理する必要がある場合があります。たとえば、別の形式に変換するためです。blobのバイナリデータをメモリに読み込むために、添付ファイルのdownloadメソッドを使用します。

binary = user.avatar.download

外部プログラム(ウイルススキャナーやメディアトランスコーダなど)がそれに操作を行うために、blobをディスク上のファイルにダウンロードする場合は、添付ファイルのopenメソッドを使用して、blobをディスク上の一時ファイルにダウンロードします。

message.video.open do |file|
  system '/path/to/virus/scanner', file.path
  # ...
end

after_createコールバックではファイルはまだ利用できないことに注意してください。after_create_commitのみで利用できます。

7 ファイルの解析

Active Storageは、ファイルがアップロードされた後にジョブをActive Jobでキューに入れることで、ファイルを解析します。解析されたファイルは、メタデータハッシュに追加の情報(analyzed: trueなど)を保存します。analyzed?を呼び出すことで、blobが解析されたかどうかを確認できます。

画像解析では、widthheight属性が提供されます。ビデオ解析では、これらに加えて、durationangledisplay_aspect_ratiovideoおよびaudioのブール値が提供され、これらのチャンネルの存在を示します。オーディオ解析では、durationbit_rate属性が提供されます。

8 画像、ビデオ、およびPDFの表示

Active Storageはさまざまなファイルの表示をサポートしています。添付ファイルに対してrepresentationを呼び出すことで、画像のバリアントやビデオやPDFのプレビューを表示することができます。representationを呼び出す前に、representable?を呼び出して、添付ファイルが表現可能かどうかを確認します。Active Storageでは、デフォルトでプレビューできないファイル形式(例:Wordドキュメントなど)もあります。representable?がfalseを返す場合は、ファイルへのリンクを作成することもできます。

<ul>
  <% @message.files.each do |file| %>
    <li>
      <% if file.representable? %>
        <%= image_tag file.representation(resize_to_limit: [100, 100]) %>
      <% else %>
        <%= link_to rails_blob_path(file, disposition: "attachment") do %>
          <%= image_tag "placeholder.png", alt: "Download file" %>
        <% end %>
      <% end %>
    </li>
  <% end %>
</ul>

内部的には、representationは画像に対してvariantを呼び出し、プレビュー可能なファイルに対してpreviewを呼び出します。これらのメソッドを直接呼び出すこともできます。

8.1 遅延ロードと即時ロード

デフォルトでは、Active Storageは表現を遅延的に処理します。次のコード:

image_tag file.representation(resize_to_limit: [100, 100])

は、srcActiveStorage::Representations::RedirectControllerを指す<img>タグを生成します。ブラウザはそのコントローラにリクエストを送信し、次の処理を行います。

  1. ファイルを処理し、必要に応じて処理済みのファイルをアップロードします。
  2. ファイルへの302リダイレクトを返します。リダイレクト先は、
    • リモートサービス(例:S3)。
    • またはプロキシモードが有効な場合はActiveStorage::Blobs::ProxyControllerがファイルの内容を返します。

ファイルを遅延的にロードすることで、単一使用URLなどの機能を使用して、初期ページの読み込みを遅くすることなく動作させることができます。

これはほとんどの場合で問題ありません。

画像のURLを即座に生成したい場合は、.processed.urlを呼び出すことができます。

image_tag file.representation(resize_to_limit: [100, 100]).processed.url

Active Storageのバリアントトラッカーは、要求された表現が以前に処理されたかどうかをデータベースに記録することで、このパフォーマンスを向上させます。したがって、上記のコードはリモートサービス(例:S3)へのAPI呼び出しを1回しか行わず、バリアントが保存されるとそれを使用します。バリアントトラッカーは自動的に実行されますが、config.active_storage.track_variantsを介して無効にすることもできます。

ページ上に多くの画像をレンダリングする場合、上記の例ではN+1クエリがすべてのバリアントレコードをロードする可能性があります。これらのN+1クエリを回避するには、ActiveStorage::Attachmentの名前付きスコープを使用します。

message.images.with_all_variant_records.each do |file|
  image_tag file.representation(resize_to_limit: [100, 100]).processed.url
end

8.2 画像の変換

画像の変換を使用すると、画像を任意のサイズで表示することができます。添付ファイルに対してvariantを呼び出すことで、画像プロセッサがサポートする任意の変換をメソッドに渡すことができます。ブラウザがバリアントURLにアクセスすると、Active Storageは元のblobを指定された形式に遅延的に変換し、新しいサービスの場所にリダイレクトします。

<%= image_tag user.avatar.variant(resize_to_limit: [100, 100]) %>

バリアントがリクエストされた場合、Active Storageは画像の形式に応じて自動的に変換を適用します。

  1. config.active_storage.variable_content_typesで指定された可変のコンテンツタイプ(config.active_storage.web_image_content_typesで指定されたウェブ画像とは異なる)は、PNGに変換されます。

  2. qualityが指定されていない場合、バリアントプロセッサのデフォルトの品質が使用されます。

Active Storageは、VipsまたはMiniMagickのいずれかをバリアントプロセッサとして使用できます。 デフォルトはconfig.load_defaultsのターゲットバージョンに依存し、 config.active_storage.variant_processorを設定することでプロセッサを変更できます。

2つのプロセッサは完全に互換性がないため、既存のアプリケーションをMiniMagickからVipsに移行する場合、 形式固有のオプションを使用している場合はいくつかの変更が必要です。

<!-- MiniMagick -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>

<!-- Vips -->
<%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg, saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>

利用可能なパラメータはimage_processing gemによって定義され、使用しているバリアントプロセッサに依存しますが、次のパラメータを両方のプロセッサがサポートしています。

パラメータ 説明
resize_to_limit resize_to_limit: [100, 100] 指定した寸法に収まるように画像のサイズを縮小します。元のアスペクト比を保持します。指定した寸法よりも画像が大きい場合にのみリサイズされます。
resize_to_fit resize_to_fit: [100, 100] 指定した寸法に収まるように画像のサイズを変更します。元のアスペクト比を保持します。指定した寸法よりも画像が大きい場合は縮小し、小さい場合は拡大します。
resize_to_fill resize_to_fill: [100, 100] 指定した寸法に画像のサイズを変更します。元のアスペクト比を保持します。必要な場合、大きい寸法で画像を切り取ります。
resize_and_pad resize_and_pad: [100, 100] 指定した寸法に収まるように画像のサイズを変更します。元のアスペクト比を保持します。ソース画像にアルファチャネルがある場合は、残りの領域を透明な色でパッドします。ない場合は黒でパッドします。
crop crop: [20, 50, 300, 300] 画像から領域を切り出します。最初の2つの引数は切り出す領域の左端と上端で、最後の2つの引数は切り出す領域の幅と高さです。
rotate rotate: 90 指定した角度で画像を回転します。

image_processingには、VipsおよびMiniMagickプロセッサのための独自のドキュメントでさらにオプションがあります。

8.3 ファイルのプレビュー

一部の非画像ファイルはプレビューできます。つまり、画像として表示することができます。 たとえば、ビデオファイルは最初のフレームを抽出してプレビューできます。Active Storageでは、 ビデオとPDFドキュメントのプレビューをサポートしています。遅延生成されたプレビューへのリンクを作成するには、 添付ファイルのpreviewメソッドを使用します。

<%= image_tag message.video.preview(resize_to_limit: [100, 100]) %>

別の形式をサポートするには、独自のプレビューアを追加します。詳細については、 ActiveStorage::Previewのドキュメントを参照してください。

9 直接アップロード

Active Storageは、付属のJavaScriptライブラリを使用して、クライアントからクラウドに直接アップロードすることをサポートしています。

9.1 使用法

  1. アプリケーションのJavaScriptバンドルにactivestorage.jsを含めます。

    アセットパイプラインを使用する場合:

    //= require activestorage
    

    npmパッケージを使用する場合:

    import * as ActiveStorage from "@rails/activestorage"
    ActiveStorage.start()
    
  2. ファイルフィールドdirect_upload: trueを追加します。

    <%= form.file_field :attachments, multiple: true, direct_upload: true %>
    

    もしくは、FormBuilderを使用していない場合は、データ属性を直接追加します。

    <input type="file" data-direct-upload-url="<%= rails_direct_uploads_url %>" />
    
  3. クロスオリジンリソース共有(CORS)を設定して、サードパーティのストレージサービスで直接アップロードリクエストを許可します。

  4. 以上です!フォームの送信時にアップロードが開始されます。

9.2 クロスオリジンリソース共有(CORS)の設定

サードパーティのサービスへの直接アップロードを動作させるためには、サービスを設定してアプリからのクロスオリジンリクエストを許可する必要があります。サービスのCORSドキュメントを参照してください。

以下を許可するように注意してください:

  • アプリにアクセスされるすべてのオリジン
  • PUTリクエストメソッド
  • 次のヘッダー:
    • Origin
    • Content-Type
    • Content-MD5
    • Content-Disposition(Azure Storageを除く)
    • x-ms-blob-content-disposition(Azure Storageのみ)
    • x-ms-blob-type(Azure Storageのみ)
    • Cache-Control(GCSの場合、cache_controlが設定されている場合のみ) ディスクサービスでは、CORSの設定は必要ありません。なぜなら、アプリのオリジンを共有しているからです。

9.2.1 例:S3 CORSの設定

[
  {
    "AllowedHeaders": [
      "*"
    ],
    "AllowedMethods": [
      "PUT"
    ],
    "AllowedOrigins": [
      "https://www.example.com"
    ],
    "ExposeHeaders": [
      "Origin",
      "Content-Type",
      "Content-MD5",
      "Content-Disposition"
    ],
    "MaxAgeSeconds": 3600
  }
]

9.2.2 例:Google Cloud Storage CORSの設定

[
  {
    "origin": ["https://www.example.com"],
    "method": ["PUT"],
    "responseHeader": ["Origin", "Content-Type", "Content-MD5", "Content-Disposition"],
    "maxAgeSeconds": 3600
  }
]

9.2.3 例:Azure Storage CORSの設定

<Cors>
  <CorsRule>
    <AllowedOrigins>https://www.example.com</AllowedOrigins>
    <AllowedMethods>PUT</AllowedMethods>
    <AllowedHeaders>Origin, Content-Type, Content-MD5, x-ms-blob-content-disposition, x-ms-blob-type</AllowedHeaders>
    <MaxAgeInSeconds>3600</MaxAgeInSeconds>
  </CorsRule>
</Cors>

9.3 ファイルの直接アップロードのJavaScriptイベント

イベント名 イベントターゲット イベントデータ (event.detail) 説明
direct-uploads:start <form> なし 直接アップロードフィールドを含むフォームが送信されました。
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> なし すべての直接アップロードが終了しました。

9.4 例

これらのイベントを使用してアップロードの進行状況を表示することができます。

direct-uploads

フォームにアップロードされたファイルを表示するには:

// 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"></span>
    </div>
  `)
  target.previousElementSibling.querySelector(`.direct-upload__filename`).textContent = file.name
})

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;
}

9.5 カスタムのドラッグアンドドロップソリューション

この目的のために、DirectUploadクラスを使用することができます。選択したライブラリからファイルを受け取った場合、DirectUploadをインスタンス化し、そのcreateメソッドを呼び出します。createメソッドには、アップロードが完了したときに呼び出すコールバックを指定します。

import { DirectUpload } from "@rails/activestorage"

const input = document.querySelector('input[type=file]')

// ファイルのドロップにバインド - 親要素のondropを使用するか、
//  Dropzoneのようなライブラリを使用します
const onDrop = (event) => {
  event.preventDefault()
  const files = event.dataTransfer.files;
  Array.from(files).forEach(file => uploadFile(file))
}

// 通常のファイル選択にバインド
input.addEventListener('change', (event) => {
  Array.from(input.files).forEach(file => uploadFile(file))
  // 選択したファイルを入力からクリアするかもしれません
  input.value = null
})

const uploadFile = (file) => {
  // フォームには、file_field direct_upload: trueが必要で、
  //  data-direct-upload-urlが提供されます
  const url = input.dataset.directUploadUrl
  const upload = new DirectUpload(file, url)

  upload.create((error, blob) => {
    if (error) {
      // エラーを処理する
    } else {
      // 適切な名前の非表示の入力をフォームに追加し、
      //  値にblob.signed_idを設定して、通常のアップロードフローで
      //  blobのIDが送信されるようにします
      const hiddenField = document.createElement('input')
      hiddenField.setAttribute("type", "hidden");
      hiddenField.setAttribute("value", blob.signed_id);
      hiddenField.name = input.name
      document.querySelector('form').appendChild(hiddenField)
    }
  })
}

9.6 ファイルのアップロードの進行状況を追跡する

DirectUploadコンストラクタを使用する場合、3番目のパラメータを含めることができます。 これにより、DirectUploadオブジェクトがアップロードプロセス中にdirectUploadWillStoreFileWithXHRメソッドを呼び出すことができます。 その後、必要に応じてXHRに独自の進行状況ハンドラをアタッチすることができます。 ```js import { DirectUpload } from "@rails/activestorage"

class Uploader { constructor(file, url) { this.upload = new DirectUpload(this.file, this.url, this) }

upload(file) { this.upload.create((error, blob) => { if (error) { // エラーを処理する } else { // 適切な名前の非表示の入力をフォームに追加し、値にblob.signed_idを設定する } }) }

directUploadWillStoreFileWithXHR(request) { request.upload.addEventListener("progress", event => this.directUploadDidProgress(event)) }

directUploadDidProgress(event) { // event.loadedとevent.totalを使用して進捗バーを更新する } } ```

9.7 ライブラリやフレームワークとの統合

選択したライブラリからファイルを受け取った後、DirectUploadインスタンスを作成し、 アップロードプロセスを開始するためにその「create」メソッドを使用します。 必要に応じて、追加のヘッダーを指定することもできます。 「create」メソッドは、アップロードが完了した後にトリガーされるコールバック関数も必要です。

import { DirectUpload } from "@rails/activestorage"

class Uploader {
  constructor(file, url, token) {
    const headers = { 'Authentication': `Bearer ${token}` }
    // INFO: ヘッダーを送信することはオプションのパラメータです。
    //       ヘッダーを送信しない場合、認証はクッキーまたはセッションデータを使用して行われます。
    this.upload = new DirectUpload(this.file, this.url, this, headers)
  }

  upload(file) {
    this.upload.create((error, blob) => {
      if (error) {
        // エラーを処理する
      } else {
        // 次のリクエストでblob.signed_idをファイルの参照として使用する
      }
    })
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener("progress",
      event => this.directUploadDidProgress(event))
  }

  directUploadDidProgress(event) {
    // event.loadedとevent.totalを使用して進捗バーを更新する
  }
}

カスタマイズされた認証を実装するには、Railsアプリケーションに次のような新しいコントローラを作成する必要があります。

class DirectUploadsController < ActiveStorage::DirectUploadsController
  skip_forgery_protection
  before_action :authenticate!

  def authenticate!
    @token = request.headers['Authorization']&.split&.last

    return head :unauthorized unless valid_token?(@token)
  end
end

注意: Direct Uploadsを使用すると、アップロードされたファイルがレコードに添付されない場合があります。未添付のアップロードを削除することを検討してください。

10 テスト

統合テストまたはコントローラーテストでファイルをアップロードするには、fixture_file_uploadを使用します。 Railsはファイルを他のパラメータと同様に処理します。

class SignupController < ActionDispatch::IntegrationTest
  test "can sign up" do
    post signup_path, params: {
      name: "David",
      avatar: fixture_file_upload("david.png", "image/png")
    }

    user = User.order(:created_at).last
    assert user.avatar.attached?
  end
end

10.1 テスト中に作成されたファイルの破棄

10.1.1 システムテスト

システムテストでは、トランザクションをロールバックすることでテストデータをクリーンアップします。 destroyがオブジェクトに呼び出されないため、添付されたファイルはクリーンアップされません。 ファイルをクリアするには、after_teardownコールバックで行うことができます。 ここで行うことで、テスト中に作成されたすべての接続が完了し、Active Storageがファイルを見つけられないというエラーが発生しなくなります。

class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
  # ...
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
  # ...
end

10.1.2 統合テスト

システムテストと同様に、統合テスト中にアップロードされたファイルは自動的にクリーンアップされません。 ファイルをクリアするには、teardownコールバックで行うことができます。

class ActionDispatch::IntegrationTest
  def after_teardown
    super
    FileUtils.rm_rf(ActiveStorage::Blob.service.root)
  end
end

10.2 フィクスチャに添付ファイルを追加する

既存のfixturesに添付ファイルを追加することができます。 まず、別のストレージサービスを作成する必要があります。

# config/storage.yml

test_fixtures:
  service: Disk
  root: <%= Rails.root.join("tmp/storage_fixtures") %>

これにより、Active Storageがフィクスチャファイルを「アップロード」する場所が指定されます。 一時ディレクトリである必要があります。通常のtestサービスとは異なるディレクトリにすることで、 テスト中にアップロードされたファイルとフィクスチャファイルを分離することができます。 次に、Active Storageクラスのためのフィクスチャファイルを作成します。

# active_storage/attachments.yml
david_avatar:
  name: avatar
  record: david (User)
  blob: david_avatar_blob
# active_storage/blobs.yml
david_avatar_blob: <%= ActiveStorage::FixtureSet.blob filename: "david.png", service_name: "test_fixtures" %>

次に、対応するファイルをフィクスチャディレクトリ(デフォルトパスはtest/fixtures/filesです)に配置します。 詳細については、ActiveStorage::FixtureSetのドキュメントを参照してください。

設定が完了したら、テストで添付ファイルにアクセスできるようになります。

class UserTest < ActiveSupport::TestCase
  def test_avatar
    avatar = users(:david).avatar

    assert avatar.attached?
    assert_not_nil avatar.download
    assert_equal 1000, avatar.byte_size
  end
end

10.2.1 フィクスチャのクリーンアップ

テストでアップロードされたファイルは、各テストの終了時にクリーンアップされますが、 フィクスチャファイルはすべてのテストが完了したときに一度だけクリーンアップする必要があります。

並列テストを使用している場合は、parallelize_teardownを呼び出します。

class ActiveSupport::TestCase
  # ...
  parallelize_teardown do |i|
    FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
  end
  # ...
end

並列テストを実行していない場合は、Minitest.after_runまたはテストフレームワークに応じた同等の方法(例:RSpecのafter(:suite))を使用します。

# test_helper.rb

Minitest.after_run do
  FileUtils.rm_rf(ActiveStorage::Blob.services.fetch(:test_fixtures).root)
end

10.3 サービスの設定

テスト環境で使用するサービスを設定するには、config/storage/test.ymlを追加できます。 これは、serviceオプションが使用されている場合に便利です。

class User < ApplicationRecord
  has_one_attached :avatar, service: :s3
end

config/storage/test.ymlがない場合、config/storage.ymlで設定されたs3サービスが使用されます - テストを実行している場合でも。

デフォルトの設定が使用され、ファイルはconfig/storage.ymlで設定されたサービスプロバイダにアップロードされます。

この場合、config/storage/test.ymlを追加し、s3サービスに対してDiskサービスを使用してリクエストを送信しないようにすることができます。

test:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

s3:
  service: Disk
  root: <%= Rails.root.join("tmp/storage") %>

11 他のクラウドサービスのサポートを実装する

これら以外のクラウドサービスをサポートする必要がある場合は、Serviceを実装する必要があります。 各サービスは、クラウドへのファイルのアップロードとダウンロードに必要なメソッドを実装することで、 ActiveStorage::Serviceを拡張します。

12 添付されていないアップロードの削除

Direct Uploadsを使用してファイルがアップロードされたが、レコードに添付されていない場合があります。 unattachedスコープを使用して、添付されていないレコードをクエリできます。 以下は、カスタムのrakeタスクを使用した例です。

namespace :active_storage do
  desc "Purges unattached Active Storage blobs. Run regularly."
  task purge_unattached: :environment do
    ActiveStorage::Blob.unattached.where(created_at: ..2.days.ago).find_each(&:purge_later)
  end
end

警告:ActiveStorage::Blob.unattachedによって生成されるクエリは、データベースのサイズが大きいアプリケーションでは遅く、潜在的に混乱を引き起こす可能性があります。

フィードバック

このガイドの品質向上にご協力ください。

タイポや事実の誤りを見つけた場合は、ぜひ貢献してください。 開始するには、ドキュメントへの貢献セクションを読んでください。

不完全なコンテンツや最新でない情報も見つかるかもしれません。 メインのドキュメントに不足しているドキュメントを追加してください。 修正済みかどうかは、まずEdge Guidesを確認してください。 スタイルと規約については、Ruby on Rails Guides Guidelinesを確認してください。

修正すべき点を見つけたが、自分で修正できない場合は、 問題を報告してください

そして最後に、Ruby on Railsのドキュメントに関するあらゆる議論は、公式のRuby on Railsフォーラムで大歓迎です。