Validates uniqueness of multiple columns

We came across a case where we wanted to validate that the combination of first and last names for a new person was unique. Add the following snippet

#config/initializers/validators.rb
ActiveRecord::Base.class_eval do
  def self.validates_uniqueness_of_combined(*attr_names)
    options = attr_names.extract_options!.symbolize_keys
    attr_names = attr_names.flatten

    send(validation_method(options[:on] || :save), options) do |record|
      sql             = attr_names.map{ |attr_name| "UPPER(#{attr_name}) = ?"}.join(" AND ")
      values       = attr_names.map{ |a| record.send(a) }.map{ |v| v && v.upcase }
      conditions = [sql, *values]

      db_record = record.class.find(:first, :conditions => conditions)
      if db_record && db_record != record
        default_message = "#{attr_names.map{ |attr_name| record.send attr_name }.join(' ')} has already been added"
        record.errors.add_to_base(options["message"] || default_message)
      end
    end
  end
end

to your initializers then you can do

class Person < ActiveRecord::Base
  validates_uniqueness_of_combined :first_name, :last_name
end

Note that this does a case insensitive match on the column names. If you want case sensitive you should replace

      sql             = attr_names.map{ |attr_name| "UPPER(#{attr_name}) = ?"}.join(" AND ")
      values        = attr_names.map{ |a| record.send(a) }.map{ |v| v && v.upcase }

with

      sql             = attr_names.map{ |attr_name| "#{attr_name} = ?"}.join(" AND ")
      values        = attr_names.map{ |a| record.send(a) }

 
 
 

Leave a Reply