Created
March 7, 2013 21:32
-
-
Save stevecj/5112001 to your computer and use it in GitHub Desktop.
Allow any ActiveRecord uniqueness validation(s) to be deferred, and only check after a failed save due to uniqueness violation error from the database.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# ./config/initializers/deferred_uniqueness_validations.rb | |
# | |
# This initializer adds support for deferred processing of | |
# ActiveRecord uniqueness validations. When a uniqueness | |
# validation is deferred using the :deferred => true option, it is | |
# not checked before save. Instead, if there is a uniqueness | |
# constraint defined for the column in the database, and if the | |
# save operation raises an ActiveRecord::RecordNotUnique | |
# exception, then the validation is performed so that the precise | |
# cause can be determined and the appropriate error information | |
# will be added to the #errors collection. | |
module ActiveRecord | |
# Adds support to ActiveRecord for deferred processing of | |
# uniqueness validations. | |
module WithDeferredUniquenessValidations | |
def ar_deferred_validations_on? | |
@ar_deferred_validations_on ||= false | |
end | |
def add_ar_deferred_validation(&b) | |
@deferred_ar_validations << b | |
end | |
# No obvious clean & reliable way to avoid this duplication... | |
def save(options={}) | |
return super unless have_ar_deferred_validations? | |
start_ar_deferred_validations! | |
begin | |
return transaction(:requires_new => true){ super } | |
rescue ActiveRecord::RecordNotUnique | |
apply_ar_deferred_validations! | |
raise if errors.empty? | |
return false | |
ensure | |
stop_ar_deferred_validations! | |
end | |
end | |
def save!(options={}) | |
return super unless have_ar_deferred_validations? | |
start_ar_deferred_validations! | |
begin | |
transaction :requires_new => true do | |
super | |
end | |
rescue ActiveRecord::RecordNotUnique | |
apply_ar_deferred_validations! | |
raise if errors.empty? | |
raise RecordInvalid.new(self) | |
ensure | |
stop_ar_deferred_validations! | |
end | |
end | |
private | |
def have_ar_deferred_validations? | |
self.class.validators.detect{|v| v.options[:deferred]} && true || false | |
end | |
def start_ar_deferred_validations! | |
@ar_deferred_validations_on = true | |
@deferred_ar_validations = [] | |
end | |
def apply_ar_deferred_validations! | |
@deferred_ar_validations.each do |validation| | |
validation.call | |
end | |
end | |
def stop_ar_deferred_validations! | |
@deferred_ar_validations = nil | |
@ar_deferred_validations_on = false | |
end | |
end | |
module Validations | |
# Patches the uniqueness validator to utilize the :deferred => true | |
# option. Use this option when the uniqueness constraint is also | |
# enforced at the database level, and the validation should be initially | |
# skipped, and then processed after rollback if the save fails with an | |
# ActiveRecord::RecordNotUnique exception. | |
UniquenessValidator.class_eval do | |
def validate_each_with_deferrable(record, *args) | |
if record.ar_deferred_validations_on? && options[:deferred] | |
record.add_ar_deferred_validation do | |
validate_each_without_deferrable(record, *args) | |
end | |
else | |
validate_each_without_deferrable(record, *args) | |
end | |
end | |
alias_method_chain :validate_each, :deferrable | |
end | |
end | |
Base.class_eval do | |
include WithDeferredUniquenessValidations | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment