LoginSignup
15
11

More than 3 years have passed since last update.

RailsでJsonカラムのvalidationしてないエンジニアいますかっていねーか、はは

Posted at

Railsでjson-schemaを使ったJsonカラムのバリデーションについての記事です。

まえがき

MySQLにJsonカラムが追加されて久しいですよね。

Railsのデフォルトだと無さそうだけど、Jsonのバリデーションをちゃんとやりたい!
でも、カスタムメソッドで愚直に書こうとすると…

愚直なカスタムメソッドの例

some_model.rb
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バリデーション方法をご紹介します。

どんな感じになるか

some_model.rb
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のインストール

Gemfile
gem 'json-schema'

カスタムバリデータの定義

validates :hoge, json: { schema: fuga }で呼べるようにするための設定です。
自分はapp/validatorsに置いています。

app/validators/json_validator.rb
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用バリデータを追加しています。

config/initializers/json_validator.rb
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生活を送りましょう!

15
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
11