Step by Step Ruby on Rails

Ruby on Railsで実際にWebサイトを構築する手順をまとめています。

カスタムバリデータでfloatの型の一意性(uniqueness)チェック

Railsのuniquenessのバリデーションチェックでfloatのカラムで実施しようとすると既存の値の存在チェックをwhere句のイコールで行ってしまい、意図したチェックを実行できませんでした。回避策としてカスタムバリデーターを定義して一意性のチェックを行いました。
 
1)一つのカラムの場合の実施例
 
●意図した動作にならない例
(定義内容)
validates :lat, uniqueness: true
 
(実行されるSQL)
Marker Exists (0.3ms) SELECT 1 AS one FROM `markers` WHERE `markers`.`lat` = BINARY 35.6878 LIMIT 1
 
●カスタムバリデーターを定義
 
・チェック対象のlatカラムは小数点以下4桁の値で入力されているので、±0.00001の範囲にある値は同一とみなすチェックを定義しました。

class MyValidator < ActiveModel::Validator
  def validate(record)
    if Marker.where("lat > ? AND lat < ?",record.lat - 0.00001,record.lat + 0.00001).exists?
      record.errors[:lat] << "latの値#{record.lat}はすでに存在しています。"
    end
  end
end

class Marker < ActiveRecord::Base
  include ActiveModel::Validations
  validates_with MyValidator
end

 
2)二つのカラムの場合の実施例
 
●カスタムバリデーターを定義
 
latとlngは緯度と経度を示すデータでこの組み合わせが同じデータは同じ地図上の位置を表すので重複を許可しないように設定している例です。

class MyValidator < ActiveModel::Validator
  def validate(record)
    if Marker.where("lat > ? AND lat < ? AND lng > ? AND lng < ?",record.lat - 0.00001,record.lat + 0.00001,record.lng - 0.00001,record.lng + 0.00001).exists?
      record.errors[:lat] << "(lat,lng)の値(#{record.lat},#{record.lng})はすでに存在しています。"
      record.errors[:lng] << "(lat,lng)の値(#{record.lat},#{record.lng})はすでに存在しています。"
    end
  end
end

class Marker < ActiveRecord::Base
  include ActiveModel::Validations
  validates_with MyValidator
end