CarrierwaveとMiniMagickでユーザー任意の入力値で画像をトリミングする


RailsでWebアプリを作っている際に、ユーザーのアップロード画像をユーザー任意の値でトリミングしたいことってありますよね。フロントエンドはcropper.jsなどを使って、こんな感じで。

cropper.js

モデルの実装

今回は、Itemというモデルにimageという名前でアップローダーをマウントします。
この時、フロントエンドで取得したcrop用の値(X, Y, Width, Height)などは仮想属性で受け取ります。

models/item.rb
class Item < ActiveRecord::Base
  mount_uploader :image,  ImageUploader

  # crop用の仮想attribute
  attr_accessor :image_x
  attr_accessor :image_y
  attr_accessor :image_w
  attr_accessor :image_h
end

仮想属性とはDBに保存しない値です。
DBに保存しないのでページの遷移などでオブジェクトが消失すると値の中身は消えてしまいます。
RailsでVirtual Attributes(仮想的な属性)をする - Rails Webook

コントローラーの実装

次はコントローラーです。
フロント側の処理(cropper.jsなど)で 先ほど設定した仮想属性と同じ名前(image_xなど)でcropの値をサーバに送信している前提です。

controllers/items_controller.rb
class ItemsController < ApplicationController

  def upload
    @item = Item.new(item_params)
    @item.save!
    # 以下省略(リダイレクトしたりエラー時の処理なんかを書く)
  end

  private

    def item_params
      params.require(:item).permit(
        :image
        :image_x,
        :image_y,
        :image_w,
        :image_h,
      )
    end

end

この時、画像の保存処理自体はsaveメソッドが呼ばれてから走るので、一旦newなどで仮想attributeに値を入れてからsaveするようにします。updateの場合も先にオブジェクトに値を反映してから保存処理を走るようにすれば大丈夫です。

アップローダーの実装

最後にアップローダーの実装です
仮想属性の値が取得できているので、それを元にcropします。

uploaders/image_uploader.rb
  # 省略
  version :resized do
    process :crop
    process resize_to_fill: [600, 600]
  end

  private

    def crop
      return if [model.image_x, model.image_y, model.image_w, model.image_h].all?
      manipulate! do |img|
        crop_x = model.image_x.to_i
        crop_y = model.image_y.to_i
        crop_w = model.image_w.to_i
        crop_h = model.image_h.to_i
        img.crop "#{crop_w}x#{crop_h}+#{crop_x}+#{crop_y}"
        img = yield(img) if block_given?
        img
      end
    end

MiniMagickを使った画像処理の書き方がイマイチわかりづらいのと、
cropメソッドがえらく煩雑なのでもうすこしスッキリ書きたいなあと思いましたが私にはできませんでした。
PLEASE FIX ME.

最後に

ruby on rails - CarrierWaveでCrop処理をする際のversionsについて - スタック・オーバーフロー

StackOverflowにこういう質問があがっていましたが、コントローラーで仮想属性を先にオブジェクトに反映するというのがキモのような気がします。

この記事を書くにあたり、弊社取締役のにしもん先生とのペアプロにより上記の問題が解決したのでお礼を申し上げたく、謝辞にかえさせていただきます。