シュッと開発日記

学んだことのアウトプット。ポートフォリオ作ってます。

Active Storageの簡単なバリデーションの実装とテスト

今日はActive Storageにバリデーションを設定したので、 その内容をまとめておきます。

背景

Active Storageにはデフォルトのバリデーションというのがないようです。 (そのせいで、みんなCarrierWaveで実装しているのかな?)

Rails5.2から入る新機能ActiveStorageを使うべきか?

うーん、なんか移行した方が良さそうなのかな。。。

とはいえ、せっかくS3、clodfrontの設定までしたばかりなので、 ひとまずは簡単なバリデーションを実装しておこうと思います。

実装

今回はこちらの記事を参考に(ほぼまるパクリですみません)で実装しました。

Active Storage移行記:バリデーション編

validate :validate_avatar

  def validate_avatar
    return unless avatar.attached? # ファイルがアタッチされていない場合は何もしない
    if avatar.blob.byte_size > 10.megabytes
      avatar.purge # アタッチされたファイルの削除
      errors.add(:avatar, 'ファイルのサイズが大きすぎます')
    elsif !image?
      avatar.purge # アタッチされたファイルの削除
      errors.add(:avatar, 'ファイルが対応している画像データではありません')
    end
  end

  private

  def image?
    %w[image/jpg image/jpeg image/gif image/png].include?(avatar.blob.content_type)
  end

purgeというのがActiveStorageのAPIでアタッチされたファイルの削除を行います。

これとは別にdetachという実体ファイルは残したまま、関連を削除するといったものもあるようです。 (attachmentで関連を、blobで実体を管理しているというようなイメージかな?)

これで無事実装できました。 ただこれだけだとただの丸パクリなので、テストも実装しておきます。

テスト

単体テストを実装していきます。

始める前にファクトリで添付ファイルを扱えるように設定します。

# spec/rails_helper.rb

FactoryBot::SyntaxRunner.class_eval do
  include ActionDispatch::TestProcess
end

# spec/factories/users.rb

FactoryBot.define do
...

  trait :with_avatar do
      avatar { fixture_file_upload(Rails.root.join('spec', 'support', 'assets', 'test.png'))
      }
    end

...
end

これでファクトリでActiveStorageの画像をテストすることができるようになりました。

この他にも

  • 10MBを超える画像ファイルを添付したトレイト
  • jpeg, jpg, png, gif 以外のデータを添付したトレイト

を用意しました。

あとはテストを書いていきます。

# spec/models/user_spec.rb

require 'rails_helper'

RSpec.describe User, type: :model do
  let(:user) { FactoryBot.create(:user) }

  let(:user_attached_image) { FactoryBot.create(:user, :with_avatar) }

  let(:user_too_large_avatar) {
    FactoryBot.build(:user, :with_too_large_avatar)
  }

  let(:user_attached_not_image) { FactoryBot.build(:user, :with_not_image) }

  describe "avatarのバリデーション", :focus do
    context "ファイルサイズが10MBを越える時" do
      it "ファイルサイズが大きすぎますというエラーメッセージが返る" do
        user_too_large_avatar.valid?
        expect(user_too_large_avatar.errors[:avatar]).to include 'ファイルのサイズが大きすぎます'
      end
    end

    context "ファイルの種類がjpeg, jpg, png, gif以外の時" do
      it "ファイルが対応している画像データではありませんというエラーメッセージが返る" do
        user_attached_not_image.valid?
        expect(user_attached_not_image.errors[:avatar]).to include 'ファイルが対応している画像データではありません'
      end
    end
  end
end

これで(否定にする検証も込みで)テストがグリーンになったので、実装完了とします。

参考