皆さんはRailsからS3にファイルアップロードする際なにをつかってますでしょうか?
Railsに標準で組み込まれているActiveStorageでしょうか?
それともプラグインが豊富でカスタマイズもしやすいCarrierWaveでしょうか?
私はシンプルな機能ならActiveStorage派なのですが、
今回はRailsのAPIモードかつ将来的に画像処理など
カスタマイズも求められる可能性があったためCarrierWaveを使用しました
今回の記事では、CarrierWaveをつかってS3に複数のファイルアップロードをする流れについてご紹介します
※なおRails APIモードですので、ファイルアップロードについてはapp/viewsなどフロントのフォームを用意せずPostmanを利用した方法の紹介になります
CarrierWaveの導入について
Gem のインストール
まずはGemfileに以下のGemを追加してbundle install
をします
# Gemfile
gem 'carrierwave', '~> 3.0' // 画像のアップロード処理
gem 'fog-aws' // s3と接続するアダプタ
gem 'fog'
とgem 'fog-aws'
の違いですが、gem 'fog'
が多くのクラウドサービスプロバイダーをサポートする包括的なライブラリでgem 'fog-aws'
が 'fog'
から派生したものでAWSのサービスに特化した軽量なライブラリというイメージです
今回は本番環境でS3、開発環境ではS3互換のMinIOというストレージを使用するので'fog-aws'
を使用します
Uploaderの生成
CarrierWaveのUploaderクラスを生成します
$ rails generate uploader File
これにより、app/uploaders/file_uploader.rb
というファイルが生成されるので
ここでファイルのアップロード方法を定義します
Uploaderの設定
下記以外にも設定できる項目は色々ありますが、最低限以下があればいいかと思います
# app/uploaders/file_uploader.rb
class FileUploader < CarrierWave::Uploader::Base
storage :fog
// アップロード先パス
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
// 許可する拡張子のホワイトリストを設定
def extension_allowlist
%w[jpg jpeg gif png]
end
end
storage は保存先がローカルなら storage :file
、外部サービスなら storage :fog
としますstorage :file
とした場合、デフォルトでpublic/配下に保存されます
もしも開発環境(ローカル)はMinIOじゃなくていいよ!
public/配下にとりあえず保存したいよ、という人は以下のようにもできます
if Rails.env.local?
storage :file // ローカルファイルシステムに保存
else
storage :fog // 外部サービス(MinIOやS3)に保存
end
ここで Rails.env.local?
は Rails.env.development? || Rails.env.test?
と同じ意味です
Rails7.1から追加された新メソッドのため、使えない方はRails.env.development? || Rails.env.test?
としてください
https://github.com/rails/rails/pull/46786
CarrierWaveの設定
CarrierWaveとfog-awsを設定するための初期化ファイルを作成します
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
config.storage = :fog
config.cache_storage = :fog
config.fog_provider = 'fog/aws'
config.asset_host = Rails.application.credentials.dig(:uploader, :asset_host)
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: Rails.application.credentials.dig(:aws, :access_key_id),
aws_secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key),
region: Rails.application.credentials.dig(:aws, :region) || 'ap-northeast-1',
host: Rails.application.credentials.dig(:uploader, :host),
endpoint: Rails.application.credentials.dig(:uploader, :endpoint),
path_style: Rails.application.credentials.dig(:uploader, :path_style)
}
config.fog_directory = Rails.application.credentials.dig(:uploader, :bucket)
config.fog_public = false
end
上記はMinIO/S3どちらにも対応できるようにまとめた書き方になってますが、MinIOとS3で設定を分けて書くと以下のようになります
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
config.storage = :fog
config.cache_storage = :fog
config.fog_provider = 'fog/aws'
region = Rails.application.credentials.dig(:aws, :region) || 'ap-northeast-1'
// 開発環境ではMinIO、それ以外ではS3を利用
if Rails.env.local?
config.asset_host = 'http://localhost:9001/test-dummy-bucket' // ブラウザからのアクセスURL
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: 'minio_root', // docker-compose.ymlの定義と合わせる
aws_secret_access_key: 'minio_password', // docker-compose.ymlの定義と合わせる
region:,
host: 's3',
endpoint: 'http://s3:9000', // コンテナからのアクセスURL
path_style: true
}
config.fog_directory = 'test-dummy-bucket' // minioのバケット名
else
config.fog_credentials = {
provider: 'AWS',
aws_access_key_id: Rails.application.credentials.dig(:aws, :access_key_id),
aws_secret_access_key: Rails.application.credentials.dig(:aws, :secret_access_key),
region:,
path_style: true
}
config.fog_directory = "rei-backend-api-#{Rails.env}" // S3のバケット名
config.fog_public = false
end
end
Rails.application.credentials.dig(:aws, :access_key_id)
は credentials
という秘匿情報が記載されたファイルの内容を読み取るものです(Rails5.2から追加)
開発環境用の credentials にはMinIO用の情報を、
本番環境用の credentials には S3 用の情報を記載しておきます。
credentialsの編集には各ファイルに対応するkeyを用意したうえで、EDITOR=vim bin/rails credentials:edit -e development
のようにエディタや環境を指定して開く必要があります。
keyは credentials ファイルの作成者が持ってるため、持っていない場合はgit管理せずに共有してもらいましょう
# config/credentials/development.yml.enc
aws:
region: 'ap-northeast-1'
access_key_id: 'minio_root'
secret_access_key: 'minio_password'
uploader:
asset_host: 'http://localhost:9001/test-dummy-bucket'
host: 's3'
endpoint: 'http://s3:9000'
bucket: 'test-dummy-bucket'
path_style: true
# config/credentials/production.yml.enc
aws:
region: 'ap-northeast-1'
access_key_id: 'xxx' // IAMユーザーの認証情報
secret_access_key: 'xxx' // IAMユーザーの認証情報
uploader:
bucket: 'production-bucket'
Rails.application.credentials.dig(:uploader, :endpoint)
などキーが存在しない場合には nil
が返るので
最初に示したとおり、MinIO/S3どちらにも対応できるようにまとめた書き方が使えます
MinIOの各種情報については docker-compose.yml
と合わせるようにご注意ください
# docker-compose.yml
services:
s3:
container_name: app-name
image: minio/minio:latest
volumes:
- s3_data:/app-name/minio/data
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: "minio_root"
MINIO_ROOT_PASSWORD: "minio_password"
command: server --console-address ":9001" /app-name/minio/data
volumes:
s3_data:
モデルにUploaderをマウント
次に、モデルにUploaderをマウントします。
まずはUploaderをマウントするための新規カラムを追加しましょう
$ rails generate migration add_file_to_users file:string
$ rails db:migrate
User
モデルにファイルをアップロードする場合は以下のようにします
# app/models/user.rb
class User < ApplicationRecord
# CarrierWaveのファイルアップローダーを :file カラムにマウント
mount_uploader :file, FileUploader
# バリデーションやその他のロジック
end
ファイルを1点のみ登録する場合はこれでよいのですが、複数ファイルを files カラムに追加する場合は以下のようにします。
$ rails generate migration add_files_to_users files:json // json型にする
$ rails db:migrate
# app/models/user.rb
class User < ApplicationRecord
# CarrierWaveのファイルアップローダーを :files カラムにマウント
mount_uploaders :files, FileUploader // mount_uploadersと 's' がつくことに注意
# バリデーションやその他のロジック
end
マウント設定については単一ファイルの場合と、複数ファイルの場合で細々した違いがあるので要注意です
mount_uploaders
と 's' を付けることを忘れて私は少し詰まりました・・
コントローラの設定
登録APIの処理での注意点が1か所だけあります。
ストロングパラメーター(user_params)の files: []
です
filesカラムをjson型にしたのと、Postman などから配列で送ってくるためこのような指定にする必要があります。
# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
def create
user = User.new(user_params)
if user.save
render json: { message: 'User created successfully' }, status: :created
else
render json: { errors: user.errors.full_messages }, status: :bad_request
end
end
private
def user_params
params.require(:user).permit(:name, files: []) // files: [] に注意
end
end
end
end
ここまででMinIO/S3にCarrierwaveを使ってファイルをアップロードするための設定については終了です
複数ファイルを送るAPIをPostmanから実行する
では実際にPostmanからAPIをたたいてファイルアップロードを確かめてみましょう
POSTで登録用APIのURLを指定したら、ボディでは form-data
を指定します。
①キーには files[]
と{カラム名}[]
の形にしてください
②キーの種別は「ファイル」を選択し、
③値入力欄では「+ New file from local machine」をクリックしてローカルPCの画像などを複数選択
して「送信」をクリックします。
※Postmanですが、デスクトップアプリを使用してください!!
以前はブラウザ版でもChrome拡張を入れれば複数ファイル送信ができていたようですが、現在Chrome拡張は廃止されデスクトップアプリ経由でしか送れないようですのでご注意ください
設定が問題なければ http://localhost:9000 などでMinIOにログインして
下記のようにPostmanから送信したファイルがMinIOの指定バケットのstore_dirパスに登録されているのが確認できます。
本番環境時のS3の場合も以下のように登録成功することが確認できると思います
最後に
いかがだったでしょうか
今回はRailsのAPIモードでCarrierwaveをつかったMinIOやS3への複数ファイルアップロードの流れについて見てきました
Carrierwaveの設定(config/initializers/carrierwave.rb)ではストレージのエンジン(MinIOかS3)かを意識しなくても済むように
環境ごとの credentials を利用する形にして設定を行いました
Postmanから複数ファイルをパラメーターに含める方法や
Carrierwaveでファイルアップローダーをmountする際のつまづきポイントについても紹介してきました。
これで少しでもCarrierwaveをもちいたファイルアップロードがより素早く実装できる人が増えればいいなと思います