追加gem無しでActiveModelの画像ファイルにバリデーションをかけたい!
こんにちは!ラブグラフで開発インターンをしている筒井です!
今回は新規 gem を追加せずに ActiveModel で画像ファイルにバリデーションをかける方法を紹介します。
背景
画像ファイルのフォーマットを JPEG だけ受けつけるようなバリデーションを実装したく、最初に出た案がfile_validators
というgemを使うというものでした。
ですが、リポジトリを見てみると3年前から更新がなく、今後 Rails のバージョンを上げる際に問題が出る可能性があったため、今回は ActiveStorage の依存関係に含まれている Marcel というgemを使ってバリデーションを実装していきます。
実装
CarrierWave を使って作成した Uploader を使って、JPEG ファイルのみを受けつけるカスタムバリデーションメソッドを作りました。
class Image < ApplicationRecord
mount_uploader :file, FileUploader
validate :only_accept_jpeg
private
def only_accept_jpeg
if file.file.present?
mime_type_str = Marcel::MimeType.for(file.file.read, name: file.file.filename)
errors.add(:file, "は無効なファイルです") if mime_type_str != "image/jpeg"
end
end
end
実装自体は意外とシンプルですね。
Marcel は何をしているのか?
Marcel は、ファイルに対してその中身、定義された MIME タイプ、ファイル拡張子を確認して、もっとも適切な MIME タイプを見つけてくれる gem です。
どうやら Marcel はこのような優先順位で MIME タイプを判断してくれるようです。
- マジックバイト(ファイルの中身)
- 宣言された MIME タイプ
- ファイル拡張子
それでも判断できないようなら、安全策でapplication/octet-stream
が設定されるようです。
MIME タイプを取得する
mime_type_str = Marcel::MimeType.for(file.file.read, name: file.file.filename)
このように for メソッドを使うことで、指定したファイルの MIME タイプを取得できます。
この for メソッドの引数はバイナリデータの文字列をとるため、file.file.read
でバイナリデータを取得し、渡してあげます。
また、これはオプションですが、ファイル名も引数に渡して上げることで MIME タイプ判定の精度が上がるようなので、安全性の確保も兼ねて渡しています。
他にも渡せる引数はあるので、興味がある方はこちらからどうぞ!
バリデーションをする
そしてこのように MIME タイプがimage/jpeg
でない場合はエラーを吐くようにします。
errors.add(:file, "は無効なファイルです") if mime_type_str != "image/jpeg"
フロントエンド側でも JPEG のみをアップロードできるように実装していると仮定すると、このバックエンド側の errors.add()
が発生しうるのはプロキシツールでの改ざんなどの悪意のある操作によるものの可能性が高いので、エラーの詳細を出さないようにしました。
さいごに
このように ActiveStorage が依存している Marcel という gem を使って、画像ファイルのバリデーションを実装しました。この実装方法なら新たな gem を追加することなくできるため、影響範囲も少なくて良かったと思います!
ラブグラフについて
株式会社ラブグラフでは家族写真・七五三・カップルなどの全国出張撮影を展開しています。
開発チームは Ruby on Rails を中心に Web サイトや管理画面を自社で開発しています。
現在、ラブグラフでは学生インターンを募集中です!
少しでも気になった学生の皆さんは、ぜひご応募ください!!
Discussion