acts_as_paranoid + ユニークインデックス

Railsで論理削除を行うときはacts_as_paranoidを使用するのが一般的です。
Model側でvalidates_uniqueness_of_without_deletedを使用するとユニーク制限をかけることができますが、DB側でユニーク制限をかけようとすると問題が発生します。

  # schema.rb
  create_table "customers", :force => true do |t|
    t.string   "code", :null => false
    t.datetime "deleted_at"
    t.datetime "created_at"
    t.datetime "updated_at"
  end
  add_index "customers", ["code", "deleted_at"], :name => "customers_idx01", :unique => true

一見正しそうですがnullはユニーク制限の対象外となり次のようなレコードが入ってしまいます。

|code|deleted_at|
|001| NULL|
|001| NULL|

こういう時はindexにwhere条件を指定して直接DBに設定しましょう。

class AddIndexCodeToCustomers < ActiveRecord::Migration
  def self.up
    sql = 'create unique index customers_idx01 on customers (code) where deleted_at is null;'
    ActiveRecord::Base.connection.execute(sql)
  end

  def self.down
    remove_index :customers, :name => :customers_idx01
  end
end

procedureを使用してDBの操作を行うとRailsのModelを経由しないのでバリデーションが行われません。そういった場合はDB側でもチェックを行いたいですね。