画像の一括アップロードを実装しました😚
transactionとエラーのキャッチに少し躓いたので備忘録として記録します。
前提
- 単一のファイル名と画像情報を持つMediumモデル(複数形Media)
- 画像とそれに1:1で対応するレコードの一括アップロード処理を実装したい
- 画像アップローダーはCarrierWaveを導入済み
- バリデーションに引っかかった場合は保存せずにエラー対象のファイル名を返却したい
- フォームから受け取るのはimage(multipul: true なフォーム)だけ
例外発生で即終了するのではなく、どのファイルがバリデーションにひっかかったのか、エラーが発生したすべてのファイル一覧を返すようにします。
basenameはファイル名を保存します。Medium内のCarrierWaveオブジェクトでも取得できるのですが、ファイル名重複禁止などのバリデーションに使う場合はテーブルに保存するほうが便利です。
というわけでこんな感じで書きました。
ソースコード
# media_controller.rb
def upload_save
respond_to do |format|
res = Medium.bulk_upload_media(upload_params)
if res[:status] == 'error'
@errors = res[:result]
format.html { render :upload_error }
else
count = res[:result].count
message = "#{count}件のアップロードに成功しました"
format.html { redirect_to upload_media_path, notice: message}
end
end
end
コントローラーから、メソッドを呼び出し、upload_paramsを受け取ります。
upload_paramsのimageは配列の形式で受け取ります。
private
def upload_params
params.permit(
{image: []}
)
end
#medium.rb
class Medium < ApplicationRecord
# 省略〜〜〜〜
def self.bulk_upload_media(upload_params)
errors = []
result = []
ActiveRecord::Base.transaction do
images = upload_params[:image]
images.each do |image|
if image.present?
basename = File.basename(image.original_filename, '.*')
new_image = Medium.new(image: image, basename: basename)
if new_image.save
result << [basename]
else
errors << [basename, new_image.errors.full_messages]
end
end
end
if errors.present?
# 失敗したらerrorsを返してロールバックする
return {
"status": "error",
"result": errors
}
raise ActiveRecord::Rollback
else
# 成功したときはresultを返す
{
"status": "success",
"result": result
}
end
end
end
end
おわり🏆
あれ、これactiverecord-import使えないんだっけ?
追記 書き込みをBulk処理したい
一括書き込みによる高速化を狙い、transactionとRollbackを一旦外したあとsaveをvalid?に変更し、new_imageを配列に入れてactiverecord_importやinsert_allでまとめてbulk処理しようと試みましたが、imageを渡すことができず断念😪