RailsでWebアプリを作っている際に、ユーザーのアップロード画像をユーザー任意の値でトリミングしたいことってありますよね。フロントエンドはcropper.jsなどを使って、こんな感じで。
モデルの実装
今回は、Itemというモデルにimageという名前でアップローダーをマウントします。
この時、フロントエンドで取得したcrop用の値(X, Y, Width, Height)などは仮想属性で受け取ります。
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の値をサーバに送信している前提です。
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します。
# 省略
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にこういう質問があがっていましたが、コントローラーで仮想属性を先にオブジェクトに反映するというのがキモのような気がします。
この記事を書くにあたり、弊社取締役のにしもん先生とのペアプロにより上記の問題が解決したのでお礼を申し上げたく、謝辞にかえさせていただきます。