Active Storage + S3 による添付ファイル機能を試してみる

はじめに

この記事では Rails 5.2 の目玉機能である Active Storage を試してみます。具体的にはベータ版の Rails 5.2 を使って、添付ファイルを AWS S3 にアップロードしてみます。

なお JavaScript を使って直接ファイルを Web クライアントから直接アップロードできる Direct uploads という機能については触れませんので悪しからず。

準備

Rails のバージョン 5.2.0.beta2 を使って rails new します。

Gemfile
gem 'rails', '5.2.0.beta2'

追加で次の Gem をインストールします。

Gemfile
gem 'mini_magick', '4.8.0'
gem 'aws-sdk', '3.0.1'

次のコマンドを実行します。

$ bin/rails active_storage:install

マイグレーションを実行すると active_storage_blobs (メタデータ用) と active_storage_attachments (ポリモーフィック関連用) という 2 つのテーブルが作成されます。

$ bin/rails db:migrate

AWS アクセスキーを設定します。

$ EDITOR=vim bin/rails credentials:edit
vim
aws:
  access_key_id: REPLACE_WITH_ACCESS_KEY_ID
  secret_access_key: REPLACE_WITH_SECRET_ACCESS_KEY

# Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies.
secret_key_base: # 略

その後 config/storage.ymlamazon の設定を入力します。

config/storage.yml
amazon:
  service: S3
  access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
  secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
  region: ap-northeast-1
  bucket: hidamari

最後に Active Storage が AWS S3 を使うように設定を更新します。

config/environments/development.rb
config.active_storage.service = :amazon # development 環境の初期値は :local になっている。

動作検証

次の User モデルを使って動作を検証します。なおデータベースには PostgreSQL を使いました。

モデル : 添付ファイル = 1 : 1 の場合

app/models/user.rb
class User < ApplicationRecord
  has_one_attached :avatar
end

まだ画像が添付されていない状態です。

yuno = User.find_by(name: 'ゆの')
yuno.avatar.attached? #=> false

ではファイルをアップロードしてみます。

filepath = File.join(Dir.home, 'Downloads/yuno.jpg')
yuno.avatar.attach(io: File.open(filepath), filename: File.basename(filepath), content_type: 'image/jpg')
# Enqueued ActiveStorage::AnalyzeJob (Job ID: ca6acf25-2971-4fea-a4a7-7c1103b5ebf5) to Async(default) with arguments: #<GlobalID:0x00007fc30dc37bc0 @uri=#<URI::GID gid://activestorage-sandbox/ActiveStorage::Blob/1>>
#=> #<ActiveStorage::Attachment id: 1, name: "avatar", record_type: "User", record_id: 1, blob_id: 1, created_at: "2017-12-04 06:03:44">

attach メソッドを実行すると ActiveStorage::AnalyzeJob というバックグラウンドジョブが登録されます。登録後してすぐにジョブが実行されました。

Performing ActiveStorage::PurgeJob (Job ID: ca6acf25-2971-4fea-a4a7-7c1103b5ebf5) from Async(default) with arguments: #<GlobalID:0x00007fc30dc37bc0 @uri=#<URI::GID gid://activestorage-sandbox/ActiveStorage::Blob/1>>
# 略
Performed ActiveStorage::PurgeJob (Job ID: ca6acf25-2971-4fea-a4a7-7c1103b5ebf5) from Async(default) in 152.65ms
# 略
Performed ActiveStorage::AnalyzeJob (Job ID: 36223552-3459-4325-943a-28de6d1d45f0) from Async(default) in 322.1ms

アップロード後に url_for を使うと期限付き URL (デフォルトでは 5 分) を発行できます。

yuno.avatar.attached? #=> true

# Rails console で url_for を使う。
# 設定ファイルで default_url_options を設定済であれば app.url_for でも動くかもしれない。
include(Rails.application.routes.url_helpers)
default_url_options[:host] = 'localhost:3000'

url_for(yuno.avatar)
#=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBEUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--726ebe29a44d2ee4214513f49e4a9c55b4af76f5/yuno.jpg"

Rails server を起動してこの URL にアクセスすると、S3 上の画像ファイルをダウンロードするための期限付き URL にリダイレクトされます。

モデル : 添付ファイル = 1 : 多 の場合

app/models/user.rb
class User < ApplicationRecord
  has_many_attached :files
end

ひとつのモデルに複数のファイルを添付することも可能です。

yuno = User.find_by(name: 'ゆの')
yuno.files.attached? #=> false

filepath = File.join(Dir.home, 'Downloads/yuno.pdf')
yuno.files.attach(io: File.open(filepath), filename: File.basename(filepath), content_type: 'application/pdf')

filepath = File.join(Dir.home, 'Downloads/yuno.mp4')
yuno.files.attach(io: File.open(filepath), filename: File.basename(filepath), content_type: 'video/mp4')

yuno.files.attached? #=> true
yuno.files.count #=> 2

ちなみに動画ファイルや PDF ファイルの場合、プレビュー画像 (サムネイル用?) を生成することも可能なようです。README.md には説明がまだ書かれていませんが、単体テストを読んでいて知りました。

video_file = yuno.files.last
preview = video_file.blob.preview(resize: '640x280').processed # 実行時に ffmpeg のログが出力された。

url_for(preview.image)
#=> "http://localhost:3000/rails/active_storage/blobs/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaHBFUT09IiwiZXhwIjpudWxsLCJwdXIiOiJibG9iX2lkIn19--262878ebb566bf1aa31ad7a8355147a25422cd37/yuno.png"

感想

従来は添付ファイルのアップロードに Paperclip を使っていましたが、同様のことを外部の Gem なしに実現できるのはすごく楽ですね。正式版が待ち遠しいです :innocent: