[RUBY] Is there an engineer who hasn't validated the Json column in Rails?

This is an article about validation of Json columns using json-schema in Rails.

Preface

It's been a long time since the Json column was added to MySQL.

** Rails doesn't seem to be the default, but I want to validate Json properly! ** ** But when I try to write it honestly with a custom method ...

Stupid custom method example

some_model.rb


class SomeModel < ApplicationRecord
  validate :verify_timetable

  private

  def verify_timetable
    errors.add(:timetable, 'Not an array') && return unless timetable.is_a?(Array)

    timetable.each do |el|
      errors.add(:timetable, 'Element is not Hash') && next unless el.is_a?(Hash)
      errors.add(:timetable, 'Hash key is invalid') && 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}The value of is invalid")
      end
    end
  end
end

――It will be a considerable amount ... ――It is difficult to manage fine tuning such as "Do you allow excess or deficiency of keys?"

I think there are many such customers. If you don't think about it, do you do validation on the application side?

This time, I will use json-schema to introduce the coolest Json validation method I have ever thought of.

What it will look like

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

It's pretty intuitive, isn't it? You can place the schema anywhere you like. I want to summarize validation in validation, so I define it just before validates.

How to use

gem installation

Gemfile


gem 'json-schema'

Definition of custom validator

It is a setting to be able to call with validates: hoge, json: {schema: fuga}. I put it in ʻ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

At this point, validation according to normal Json Schema (pattern matching by regular expression, definition of minLength, maxLength, etc.) is possible. While googled the Json Schema specifications, read the Gem Readme and try various things. (For example, strict: true is a setting that ignores the required content and does not allow excess or deficiency of keys) It's easy because you can write it with a Ruby hash.

Add validator for DateTime

There is no date type in the Json type definition. But for the time being, there are cases where you want to put date and time information into Json. Here, referring to this issue, the validator for DateTime is added to JSON :: Validator.

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)

If you have something like "I want this custom format!", You can add it here.

Tightening

How was it? This seems to free you from personal validation descriptions. Even engineers who have neglected Json's validation will take this opportunity to enhance the validation on the application side and lead a healthy Rails life!

Recommended Posts

Is there an engineer who hasn't validated the Json column in Rails?
How to output the value when there is an array in the array
Is there an instance even if the constructor fails?
[Rails] How to display an image in the view
Throw an exception and catch when there is no handler corresponding to the path in spring
What to do if Cloud9 is full in the Rails tutorial
Isn't it reflected even if the content is updated in Rails?
I don't see an error in Rails bundle install ... the solution
How to write the view when Vue is introduced in Rails?
Puma --Nignx is an escape route when rails s -e production -d doesn't work in the environment