Ruby on Railsで画像の一括アップロードにトランザクション処理を入れる

画像の一括アップロードを実装しました😚
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を渡すことができず断念😪

公開日