Railsでjson-schemaを使ったJsonカラムのバリデーションについての記事です。
まえがき
MySQLにJsonカラムが追加されて久しいですよね。
Railsのデフォルトだと無さそうだけど、Jsonのバリデーションをちゃんとやりたい!
でも、カスタムメソッドで愚直に書こうとすると…
愚直なカスタムメソッドの例
class SomeModel < ApplicationRecord
validate :verify_timetable
private
def verify_timetable
errors.add(:timetable, '配列ではありません') && return unless timetable.is_a?(Array)
timetable.each do |el|
errors.add(:timetable, '要素がHashではありません') && next unless el.is_a?(Hash)
errors.add(:timetable, 'Hashのkeyが不正です') && next unless el.keys.sort == %w[end_at start_at]
%w[start_at end_at].each do |key|
DateTime.parse(el[key])
rescue ArgumentError
errors.add(:timetable, "#{key}の値が不正です")
end
end
end
end
- かなりの分量になる…
- 「keyの過不足を許すか?」等の細かいチューニングの管理が難しい…
そんなお客様も多いのではないでしょうか?
逆に思ってないお客様は、アプリケーション側のバリデーションちゃんとやってますか?
今回はjson-schemaを使って、私がちょっと考えた中では一番クールなJsonバリデーション方法をご紹介します。
どんな感じになるか
class SomeModel < ApplicationRecord
TIMETABLE_SCHEMA = {
type: 'array',
items: {
type: 'object',
properties: {
start_at: { type: 'string', format: :datetime },
end_at: { type: 'string', format: :datetime },
},
},
}.freeze
validates :timetable, json: { schema: TIMETABLE_SCHEMA }
end
かなり直感的ですよね。
schemaを置く場所はお好きに。
自分はバリデーションはバリデーションでまとめたいので、validatesの直前に定義しています。
使い方
gemのインストール
gem 'json-schema'
カスタムバリデータの定義
validates :hoge, json: { schema: fuga }
で呼べるようにするための設定です。
自分はapp/validators
に置いています。
class JsonValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
JSON::Validator.validate!(options[:schema], value, strict: true)
rescue JSON::Schema::ValidationError => e
record.errors[attribute] << (options[:message] || e.message)
end
end
この時点で、通常のJson Schemaに則ったvalidation(正規表現によるパターンマッチングやminLength,maxLengthの定義など)は可能です。
Json Schemaの仕様をググりつつ、GemのReadmeを読んで色々試してみて下さい。
(例えば、strict: true
はrequiredの内容を無視して、keyの過不足を許さない設定です)
Rubyのハッシュで書けるので気が楽ですよね。
DateTime用バリデータの追加
Jsonの型定義にはdate型がありません。
でも、とりあえず日時情報をJsonにぶち込みたいケースって発生しますよね。
ここでは、このissueを参考に、JSON::ValidatorにDateTime用バリデータを追加しています。
datetime = -> value {
begin
DateTime.parse(value)
rescue ArgumentError
raise JSON::Schema::CustomFormatError.new('not datetime format')
end
}
JSON::Validator.register_format_validator(:datetime, datetime)
「このカスタムフォーマットが欲しい!」等あれば、ここに追記していけば良いわけですね。
締め
いかがでしたか?
これで属人的なバリデーション記述から開放されそうですよね。
今までJsonのバリデーションをおざなりにしてきたエンジニアも、これを機にアプリケーション側のバリデーションを充実させて、健康的なRails生活を送りましょう!