gem「CarrierWave」とは
Railsアプリから簡単にファイルをアップロードでき、またアップロードしたファイルを扱うための便利なメソッドが使えるgem。
利用イメージ
gemをインストールした上で、以下の仕込みを行う。
- アップロード対象ファイル(以降fileと呼ぶ)のuploaderを作成
- fileを扱うmodelにuploaderを連携、アップロード情報を扱うカラム(以降upload_infoと呼ぶ)を設定
- fileを扱うテーブルにカラムupload_infoを設定(string型)
すると、gemによって拡張されたインスタンスメソッドupload_infoを使い、
model.upload_info = file
とすると、
uploaderによってfileがアップロードされ、
テーブルのupload_infoカラムにアップロード先のファイルパスが書き込まれる。
また、その他のgemで拡張されたインスタンスメソッドで色々便利にfileを扱えるようになる。
導入
前提: Rails6系の場合
gemのインストール
# Gemfileへ以下を記述
gem 'carrierwave', '~> 2.0'
利用手順
①前提
あらかじめ、以下を決定しておく必要がある。
- アップロード情報を扱うテーブルとカラム名
- アップロード先
ここでは、犬の成長記録を管理するテーブルdog_records
があり、
その中に犬の画像ファイルのアップロード情報を扱うカラムdog_image
があるものとする。
また、アップロード先は、Railsアプリケーションが動作するマシン上の/dog_images/
であるものとする。
以降の実装するものと、その動作イメージを以下に示す。
②アップローダーの生成
# アップローダーファイルの生成
rails generate uploader dog_image
=> app/uploders/dog_image_uploader.rb が生成される
③アップローダーの設定
class DogImageUploader < CarrierWave::Uploader::Base
# アップロード先を記述(ローカルのパスでもS3でも可らしい)
def store_dir
# 相対パスで記載すると、"public/uploads/dog_images" にアップロードされる。
'uploads/dog_images'
end
end
④アップロード情報のカラムとアップローダーとの連携
class DogRecord < ApplicationRecord
# 「dog_image」カラムに「DogImageUploader」を連携
mount_uploader :dog_image, DogImageUploader
end
⑤テーブルにアップロード情報のカラムを追加
dog_records
テーブルにdog_image
カラムをstring型で追加する。
⑥ファイルのアップロードをするAPIのアクションメソッドの設定
前提
APIでのファイルの送信方法は、主に以下2つがあるらしい。
(A) multipart/form-data を利用して送信
(B) Base64化してJSONに文字列としてセットして送信
いろいろな情報があり、実装コスト、ファイルサイズのコスト、処理スピードなどいろいろな意見があったが、
イマイチ反対のことをいっている部分があり、これらについてよくわからなかった。
・・・が、(B)の方法を取らないと、そもそもJSONでデータを送れないので、
JSONでやり取りする体系をとっているAPI群をサービスとして提供する場合、ファイル送信を扱うAPIだけ特異点になってしまう
画像ファイル以外にもデータを送りたいときにJSONでないとやりづらそう
(B)でやる場合のサーバー側の実装を見てみたらすごく単純だったが、(A)の方はいまいちよくわからなかった
という3点から(B)を採用することにした。
本題
※クライアント側からPOSTメソッドでリクエストボディに「Base64で文字列化した画像データ」を挿入してリクエスト
→サーバー側で受け取って、Base64画像データをデコードして画像ファイルを取り出し、アップローダーに紐付いているカラムにセットする
という流れになる。
また、ここではdog_records_controller.rb
のupload
アクションメソッドで、ファイルをアップロードするものを例とする。
また、リクエストボディで送信するパラメータは以下のようなものとする。
{
"image": {
"file_name": "<保存するファイル名>"
"base64": "<Base64エンコードした画像ファイルの文字列>"
}
}
def upload
# Base64文字列化された画像をデコードしてデータ(バイナリ)を取り出す
dog_image_binary = Base64.decode64(params[:image][base64])
# ファイル名を取り出す
dog_image_file_name = params[:image][file_name]
# ファイル名を名前と拡張子に分離
dog_image_file_basename = File.basename(dog_image_file_name, ".*")
dog_image_file_extname = File.extname(dog_image_file_name)
# <ファイル名>で空っぽのテンポラリファイルを作成
# テンポラリファイルは「Dir.tmpdir」に保存される。
# 自動的に削除はされないので、不要になった時点で「unlink」メソッドで削除する)
dog_image_temp_file = Tempfile.new([dog_image_file_basename,dog_image_file_extname])
# テンポラリファイルをバイナリモードにする
dog_image_temp_file.binmode
# バイナリ状態の画像データを書き込む
dog_image_temp_file.write(dog_image_binary)
# 対象テーブルのレコードを扱うモデルのインスタンスを生成
dog_record = DogRecord.new()
# carrierwaveのuploaderが紐づくカラムに画像データファイル(File型)の値をセットする
dog_record.dog_image = dog_image_temp_file
# 対象テーブルにレコードをsaveする
# ・・・すると、ファイルが「store_dir」へアップロードされ、「dog_records」テーブルの「dog_image」カラムへアップロード情報が書き込まれる
dog_record.save!
end
⑦アップロード情報を扱う
DBに保存したアップロード情報は、以下のように操作できる(例として)
# DogRecordのインスタンスを取得
dog_record = DogRecord.first
# アップロードファイルのURLを取得
dog_record.dog_image.url
# アップロードファイルのパスを取得
dog_record.dog_image.current_path
# アップロードファイル名を取得
dog_record.dog_image_identifier
# アップロードファイル情報があるか確認
dog_record.dog_image.file.nil?
# アップロードファイル情報を削除
dog_record.remove_dog_image!
dog_record.save
動作確認
# クライアント側
## 1. 適当な画像ファイルをローカルに用意してBase64でエンコード
base64 <画像ファイルのパス>
=>
表示されたBase64文字列をコピペしておく
## 2. POSTメソッドでbase64で文字列化した画像データを挿入してリクエスト(postmanなりcurlなり)
## ※以下のJSONキーバリューをリクエストボディへセット
{
"image": {
"file_name": "<保存するファイル名>"
"base64": "<Base64エンコードした画像ファイルの文字列>"
}
}
# サーバー側
## 3. public/<store_dirのパス>に画像が保存されていることを確認する
参考
APIでのファイルアップロード方法
- WebAPI でファイルをアップロードする方法アレコレ
- 画像データをサーバーにPOSTする
- POST時の Content-Type: application/json vs multipart/form-data vs application/x-www-form-urlencoded
- ファイルをS3にアップロードするときはサーバー?クライアント?
- [Rails6]CarrierWaveを基本に忠実に使ってみる~キャッシュ・S3編~
ファイルの扱い