yposiのブログ

Web開発日記

IpValidatorを追加した

これはRailsアプリケーションの話です。
今回カラムにipを追加し、バリデーションを追加して欲しいとのことでした。
バリデーションはIPv4IPv6に対応して欲しいということでした。

この記事は、

  • Resolv::IPv4::Regex とResolv::IPv6::Regex というものが用意されていた
  • validatorを用意することでModelがスッキリする

という話ができたらと思います。

Resolv::IPv4::Regex とResolv::IPv6::Regex というものが用意されていた

正規表現って自分で書くとバグを生んだり、面倒だったりするのでまずはネットで調べると思うのですが、ありがたいことにRubyはいろんなものが用意されています。
Resolv::IPv4::Regex の存在を見つけました。
コンソールで実行してみると、

irb(main):001:0> Resolv::IPv4::Regex
=> /\A((?x-mi:0
               |1(?:[0-9][0-9]?)?
               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
               |[3-9][0-9]?))\.((?x-mi:0
               |1(?:[0-9][0-9]?)?
               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
               |[3-9][0-9]?))\.((?x-mi:0
               |1(?:[0-9][0-9]?)?
               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
               |[3-9][0-9]?))\.((?x-mi:0
               |1(?:[0-9][0-9]?)?
               |2(?:[0-4][0-9]?|5[0-5]?|[6-9])?
               |[3-9][0-9]?))\z/

これはすごいですね!
IPv6も動くんじゃないか?と淡い期待をして実行してみました。
やっぱりいました。

irb(main):002:0> Resolv::IPv6::Regex
=> /
      (?:(?x-mi:\A
      (?:[0-9A-Fa-f]{1,4}:){7}
         [0-9A-Fa-f]{1,4}
      \z)) |
      (?:(?x-mi:\A
      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)
      \z)) |
      (?:(?x-mi:\A
      ((?:[0-9A-Fa-f]{1,4}:){6,6})
      (\d+)\.(\d+)\.(\d+)\.(\d+)
      \z)) |
      (?:(?x-mi:\A
      ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::
      ((?:[0-9A-Fa-f]{1,4}:)*)
      (\d+)\.(\d+)\.(\d+)\.(\d+)
      \z))/x

さすがです。
これをいい感じに使用したバリデーターを作ろうと思って作りました。

# frozen_string_literal: true

class IpValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return true if Resolv::IPv4::Regex.match?(value) || Resolv::IPv6::Regex.match?(value)
    record.errors.add(attribute, :invalid_ip, options)
  end
end

作ったバリデーターを呼ぶには、

# == Schema Information
#
# Table name:hoges
#
#  id                 :bigint(8)        not null, primary key
#  ip                 :string(255)
#  created_at         :datetime         not null
#  updated_at         :datetime         not null
#

class Hoge < ApplicationRecord
  validates :ip, ip: true
end

「validates :ip, ip: true」これだけです。
バリデーターを作るところから真似してもらえると幸いです。

参考にしたページ