【Rails】CarrierWave で画像の縦横の大きさの値をバリデーションする
ファイルサイズのバリデーション(10MBまで可、みたいな)は CarrierWave の中に組み込まれていますが、縦横の大きさについてバリデーションする機能は現状ありません(ActiveStorage なら簡単にできるらしいですね)。
今回はその「CarrierWave で縦横の大きさの値をバリデーションする機能」について自前で実装してみました。
実装
アップローダーごとにバリデーションの有無と基準値を定義することができました。
具体的には、以下のように min_dimensions を定義したアップローダーのみ、その値を基準にしたバリデーションがかかります。
↓ app/uploaders/application_uploader.rb
class ApplicationUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick before :cache, :check_dimensions! def min_dimensions nil end private def check_dimensions!(new_file) # NOTE: オリジナル画像だけチェックする(リサイズしたものはチェックしない) return if version_name.present? expected_min_width, expected_min_height = min_dimensions return if expected_min_width.blank? && expected_min_height.blank? width, height = ::MiniMagick::Image.open(new_file.file)[:dimensions] if width < expected_min_width || height < expected_min_height raise CarrierWave::IntegrityError, I18n.translate(:'errors.messages.dimensions_error', min_width: expected_min_width, min_height: expected_min_height) end end end
↓ app/uploaders/image_uploader.rb(例:1000 × 1000 より小さいものを弾くバリデーション)
class ImageUploader < ApplicationUploader def min_dimensions [1000, 1000] end end
参考にした情報など
基本的には CarrierWave のファイルサイズのバリデーションの実装↓を参考にしました。
(コールバックや raise の部分、アップローダーごとにバリデーションを使うかどうかを決められるようにするための書き方など)
Added errors on file size by gautampunhani · Pull Request #1026 · carrierwaveuploader/carrierwave
縦横の値(dimensions)を取得する方法も、CarrierWave の Wiki にありました。
How to: Get image dimensions · carrierwaveuploader/carrierwave Wiki
CarrierWave 独自のコールバック(フック)についても、Wiki を参考にしました。
How to: use callbacks · carrierwaveuploader/carrierwave Wiki
そのほかのポイント
version
を使ってサムネイルなども一緒に生成している場合に、リサイズ後のファイルでもバリデーションが実行されてしまいました。
ここで少しハマりましたが、コールバックの Wiki を読んでいると、「version_name の値を見ることでそれがオリジナルなのかどうかがわかる(nil ならオリジナル)」と知り、無事以下のようにガードすることができました。
def check_dimensions!(new_file) # NOTE: オリジナル画像だけチェックする(リサイズしたものはチェックしない) return if version_name.present? # ...
テストコード
特にポイントなどありませんが一応載せておきます。
context 'メイン画像の大きさが 1000 × 1000 の場合' do let(:book) { build(:book, image: Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/images/1000x1000.png'))) } it 'valid' do expect(book).to be_valid end end context 'メイン画像の大きさが 1000 × 999 の場合' do let(:book) { build(:book, image: Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/images/1000x999.png'))) } it 'is invalid' do expect(book).to be_invalid expect(book.errors[:image]).to include '縦:1000px 以上 かつ 横:1000px 以上のファイルをアップロードしてください' end end context 'メイン画像の大きさが 999 × 1000 の場合' do let(:book) { build(:book, image: Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/images/999x1000.png'))) } it 'is invalid' do expect(book).to be_invalid expect(book.errors[:image]).to include '縦:1000px 以上 かつ 横:1000px 以上のファイルをアップロードしてください' end end