はじめに
Rails5.2から導入されたActive Storageは設定が簡単でとても導入しやすく、シンプルな機能を備えていて、少し使ってみた感じだとよい機能だと思っています。
ただ、実運用で使おうとすると、バリデーションの機能がなくて、惜しい感じです…。
そこで、自分が最低限欲しいと感じた、いくつかのバリデーションを作ったので紹介します。
サンプルアプリケーションのコードはこちらです。
Rails5.2で書いていますが、ほぼ同じものを6.0に持ってきても動きます。
必須
入力フォームで、ファイルの添付を必須強制したいときはごく普通にあると思います。
ただ、普通のpresenceのバリデーションは使えなかったので、別途用意しました。
設置例
attached_file_presence
で設定できるようにしています。
class Post < ApplicationRecord
has_one_attached :main_image
has_many_attached :other_images
validates :main_image, attached_file_presence: true
validates :other_images, attached_file_presence: true
end
コード
ファイルが添付されているかどうか検査できるattached?
を使います。
class AttachedFilePresenceValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, :blank) unless value.attached?
end
end
ファイル数
has_many_attached
を使うと、複数のファイルを添付できますが、個数の制限をしたいときがあると思います。
設置例
attached_file_number
とmaximum
オプションで最大個数を設定できるようにしています。
class Post < ApplicationRecord
has_many_attached :other_images
validates :other_images, attached_file_number: { maximum: 3 }
end
コード
ファイル数はsizeで取れるので、それを使って検証しています。
class AttachedFileNumberValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true unless value.attached?
file_number = value.size
if (limit = options[:maximum]).present? && file_number > limit
record.errors.add(attribute, :too_many_files, count: limit)
end
if (limit = options[:minimum]).present? && file_number < limit
record.errors.add(attribute, :too_few_files, count: limit)
end
end
end
エラーメッセージで指定している、too_many_files
やtoo_few_files
はconfig/locales
で設定しています。
ja:
errors:
messages:
too_many_files: は%{count}個以内で入力してください
too_few_files: は%{count}個以上で入力してください
ファイルサイズ
ユーザに画像を登録できるようにすると、本格的なカメラで撮ったような高精細の画像が添付されてくることがしばしばありますが、リソース上の制約から受け取らないようにしたいときもあると思います。
設置例
attached_file_size
とmaximum
オプションで最大ファイルサイズを設定できるようにしています。
class Post < ApplicationRecord
has_one_attached :main_image
has_many_attached :other_images
validates :main_image, attached_file_size: { maximum: 5.megabytes }
validates :other_images, attached_file_size: { maximum: 5.megabytes }
end
コード
ファイルのサイズを得るattachement.byte_size
を使っています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。
class AttachedFileSizeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true unless value.attached?
return true unless options&.dig(:maximum)
maximum = options[:maximum]
attachements = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment]
if attachements.any? { |attachment| attachment.byte_size >= maximum }
record.errors.add(attribute, :less_than, { count: maximum.to_s(:human_size) })
end
end
end
ファイルタイプ
例えば画像を登録してもらうつもりのところに、間違ってテキストファイルを登録しないようになど、アップロードするファイルの形式を保存する前にチェックしたいことは多いと思います。
設置例
attached_file_type
とpattern
オプションで最大ファイルサイズを設定できるようにしています。
pattern
は正規表現で設定します。
class Post < ApplicationRecord
has_one_attached :main_image
has_many_attached :other_images
validates :main_image, attached_file_type: { pattern: /^image\// }
validates :other_images, attached_file_type: { pattern: /^image\// }
end
コード
content_typeを指定のパターンに合致しているかチェックしています。
ファイルの単複で微妙に処理が変わるのが嫌で、単数のときは配列に詰めて複数と同じように処理できるようにしています。
class AttachedFileTypeValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return true unless value.attached?
return true unless options&.dig(:pattern)
pattern = options[:pattern]
attachments = value.is_a?(ActiveStorage::Attached::Many) ? value.attachments : [value.attachment]
if attachments.any? { |attachment| !attachment.content_type.match?(pattern) }
record.errors.add(attribute, :invalid_file_type)
end
end
end
エラーメッセージで指定している、invalid_file_type
はconfig/locales
で設定しています。
ja:
errors:
messages:
invalid_file_type: は不正なファイル形式です
Validatorのテスト
RSpecで書いたものがありますが、全部載せるととても長いので、こちらを参照してください。
ここでのポイントは、アプリケーションで実際に使っているテーブルを使ったモデルを定義して、そこにバリデーションを設置してテストに使うところです。
最初は使いそうなものをスタブやモックを使って作ろうとしていたのですが、ファイルの単複を扱うのでとても記述量が多くなり、わかりにくくなってしまったのでやめました。
最後に
バリデータもテストを書いたりすると、結構楽しいですね。これのおかげでActiveStorageと少し仲よくなれた気がします。
書きながらあれこれ探してたら、もっとスマートなactivestorage-validatorというGemがあったので、こちらを使ってもらってもいいかもしれません。