#
# the following model class, with example migration, with demonstrate how
# rails validation (*all* validation not only validates_uniqueness_of) is
# fatally flawed.  get your db setup, run a migration to bootstrap the table,
# and then run this to trigger the problem:
#
#   echo ' Fubar.hork_the_db ' | ./script/runner /dev/stdin
#
# a couple of things to to notice:
#
#  1) all transactions are happening in a separate process
#  2) 16 processes at a time try to insert the same 'uniq' data 
#
# how does this happen?
#
#   process A runs the query to check uniqueness, gets 'ok'
#   process B runs the query to check uniqueness, gets 'ok'
#   process A commits 
#   process B commits 
#
# a classic race condition
#
# wrapping in a transaction will not help unless the isolation level is
# serializable
# (http://www.postgresql.org/docs/7.4/interactive/transaction-iso.html) *and*
# you are using transactions for all db activity.  even so called 'atomic'
# actions like create! are victim to this
#
# i've found a few links to this online like
# http://kpumuk.info/ruby-on-rails/validates_uniqueness_of-vs-mysql-unique-index/
# which have notice the same thing, but it worries me that this fact does not
# seem to be generally known or dealt with.
#
# the answer is simply putting all contraints in the db and giving up on nice
# model.errors, but that removes alot of the niceness ar brings to the table.
#
#

  class Fubar < ActiveRecord::Base
    class Migration < ActiveRecord::Migration
      def self.up
        create_table :fubars do |t|
          t.column :must_be_uniq, :text
          t.timestamps
        end
      end

      def self.down
        drop_table :fubars
      end
    end

    validates_uniqueness_of :must_be_uniq, :if => :you_want_fubar_data

    def you_want_fubar_data
      true
    end

    def self.hork_the_db
      Dir.chdir RAILS_ROOT do
        delete_all
        create_a_script
        run_a_bunch_of_parallel_requests_in_separate_processes
        watch_the_db_get_horked
      end
    end

    def self.create_a_script
      open("a.rb", "w"){|fd| fd.puts "Fubar.create! :must_be_uniq => ARGV.shift"}
    end

    def self.run_a_bunch_of_parallel_requests_in_separate_processes
      Thread.new do
        loop do
          threads = []
          must_be_uniq = rand
          16.times do
            threads <<
              Thread.new{ system "./script/runner a.rb #{ must_be_uniq } >/dev/null 2>&1" }
          end
          threads.map{|t| t.join}
        end
      end
    end

    def self.watch_the_db_get_horked
      loop do
        must_be_uniq = find(:all).map{|fubar| fubar.must_be_uniq}
        total = must_be_uniq.size
        unique = must_be_uniq.uniq.size
        if total == unique
          y "total" => total, "unique" => unique
        else
          y "total!" => total, "unique!" => unique
        end
        sleep 1
      end
    end
  end

__END__

# some sample output of run

...

---
total: 0
unique: 0
---
total: 1
unique: 1
---
total!: 5
unique!: 2
---
total!: 5
unique!: 2
---
total!: 5
unique!: 2

...