カテゴリー: BackEnd

Shrineをモデルに関連付けないで使用してハマったこと

はじめに

ShrineはRailsでのアップロードを簡単に行えるようにするGemです。
詳しくはこちらを御覧ください。

Shrineで画像をアップロードする処理を実装するなら、多くの場合モデルに関連付けて使用すると思います。
しかし、今回は下記の理由によりモデルに関連付けずに使用しました(ファイル名自体はDBに保存しています)

  • モデルに関連付けて使用する場合テーブルにtext型のxxx_dataというカラムが必要になる。
  • (S3の署名付きURLなどで)画像の公開範囲を制限したいのでShrineが提供しているメソッドがそのままでは使えない。

実装の際、アップロード時のリサイズやバリデーションなどに少しハマったので対応方法を記載します。

アップロード時にリサイズする

まず、アップロードに関する処理ですが、他のアップロード処理にも使いまわせる用にモジュール化しています。

module FileUploadable
  extend ActiveSupport::Concern

  def save_after_upload(image_params)
    if image_params[:image].blank?
      return self.save
    end

    content_type = image_params[:image].content_type
    if content_type.include?('jpeg')
      extension = '.jpg'
    elsif content_type.include?('png')
      extension = '.png'
    else
      return
    end
    remove_images_if_existed
    # 画像のファイル名をDBに保存する
    self.image_filename = "#{Time.now.to_i}#{extension}"
    return unless upload_images(image_params)

    self.save
  end

  def image_path
    "#{base_path}/#{self.image_filename}"
  end

  def thumb_image_path
    filename = self.image_filename.split('.').first
    extension = self.image_filename.split('.').last
    "#{base_path}/#{filename}_thumb.#{extension}"
  end

  private
  def upload_images(image_params)
    return false unless image_params[:image].present?

    # XxxImageUploaderという名前のアップローダのインスタンスを生成する
    uploader_class = "#{self.class.name}ImageUploader".constantize
    uploader = uploader_class.new(:store)
    # 大サイズの画像をアップロードする
    upload_file = uploader.upload(image_params[:image],
                                  location: image_path)
    return false unless upload_file.present?

    # アップロードした画像のバリデーションを行う
    attacher = uploader_class::Attacher.new(self, :image)
    attacher.assign(upload_file)
    return false if attacher.errors.present?

    # サムネイル画像をアップロードする
    upload_thumb_file = uploader.upload(image_params[:image],
                                        location: thumb_image_path,
                                        versions: 'thumb')
    upload_thumb_file.present?
  end

  def remove_images_if_existed
    return unless self.image_filename.present?
    if Rails.env.production?
      # TODO: S3から削除する処理を実装する
    else
      FileUtils.rm_rf("./public/images/uploads/store/#{base_path}")
    end
  end

  def base_path
    "#{self.user_id}/#{self.class.name.underscore}"
  end
end

実装のアップロードは「upload_images」で行っており、引数はStrong Parametersです。

次に、モデルに関連付ける場合のアップローダのコード例は下記のとおりです。

require 'image_processing/mini_magick'

class CoachImageUploader < Shrine
  include ImageProcessing::MiniMagick
  plugin :processing
  plugin :versions
  plugin :delete_raw
  plugin :validation_helpers
  plugin :determine_mime_type

  process(:store) do |io, context|
    # リサイズを行う
    large = resize_to_limit!(io.download, 400, 400) { |cmd| cmd.auto_orient }
    thumb = resize_to_limit(large, 150, 150)

    { large: large, thumb: thumb }
  end

  # 省略
end

上記のコードではアップロード時にリサイズ処理が実行されません。
モデルに関連付けない場合は下記の用になります。

require 'image_processing/mini_magick'

class CoachImageUploader < Shrine
  # 省略

  def process(io, context)
    if context[:versions].blank? or context[:versions] == :original
      resize_to_limit(io, 800, 800) { |cmd| cmd.auto_orient }
    else
      resize_to_limit(io, 300, 300) { |cmd| cmd.auto_orient }
    end
  end

  # 省略
end

アップロード時のバリデーション

バリデーションを行うためにモデルにアタッチする必要があったので、やむなくモデルに「image_data」というattributeを追加しました。

module FileUploadable
  extend ActiveSupport::Concern

  # 省略

  private
  def upload_images(image_params)
    return false unless image_params[:image].present?

    # XxxImageUploaderという名前のアップローダのインスタンスを生成する
    uploader_class = "#{self.class.name}ImageUploader".constantize
    uploader = uploader_class.new(:store)
    # 大サイズの画像をアップロードする
    upload_file = uploader.upload(image_params[:image],
                                  location: image_path)
    return false unless upload_file.present?

    # アップロードした画像のバリデーションを行う
    attacher = uploader_class::Attacher.new(self, :image)
    attacher.assign(upload_file)
    return false if attacher.errors.present?

    # サムネイル画像をアップロードする
    upload_thumb_file = uploader.upload(image_params[:image],
                                        location: thumb_image_path,
                                        versions: 'thumb')
    upload_thumb_file.present?
  end

  # 省略
end

アップロード時に古いファイルを削除する

モデルに関連付ける場合、アップロードし直すと古い画像が削除されますが、関連付けない場合画像がどんどん増えていきます。
下記の方法でアップロード時に古いファイルを削除するようにしました。

module FileUploadable
  extend ActiveSupport::Concern

  # 省略

  def remove_images_if_existed
    return unless self.image_filename.present?
    if Rails.env.production?
      # TODO: S3から削除する処理を実装する
    else
      FileUtils.rm_rf("./public/images/uploads/store/#{base_path}")
    end
  end

  # 省略

end

さいごに

モデルに関連付けずにShrineでアップロードする際にハマったポイントと対応について紹介しました。

Hiroki Ono

シェア
執筆者:
Hiroki Ono
タグ: Rails

最近の投稿

フロントエンドで動画デコレーション&レンダリング

はじめに 今回は、以下のように…

3週間 前

Goのクエリビルダー goqu を使ってみる

はじめに 最近携わっているとあ…

1か月 前

【Xcode15】プライバシーマニフェスト対応に備えて

はじめに こんにちは、suzu…

2か月 前

FSMを使った状態管理をGoで実装する

はじめに 一般的なアプリケーシ…

3か月 前