Created
May 2, 2009 02:01
-
-
Save RISCfuture/105367 to your computer and use it in GitHub Desktop.
Reimplementation of validates_uniqueness_of that works optimally with MySQL.
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
module ActiveRecord::Validations::ClassMethods | |
def validates_uniqueness_of(*attr_names) | |
configuration = { :case_sensitive => true } | |
configuration.update(attr_names.extract_options!) | |
validates_each(attr_names,configuration) do |record, attr_name, value| | |
# The check for an existing value should be run from a class that | |
# isn't abstract. This means working down from the current class | |
# (self), to the first non-abstract class. Since classes don't know | |
# their subclasses, we have to build the hierarchy between self and | |
# the record's class. | |
class_hierarchy = [record.class] | |
while class_hierarchy.first != self | |
class_hierarchy.insert(0, class_hierarchy.first.superclass) | |
end | |
# Now we can work our way down the tree to the first non-abstract | |
# class (which has a database table to query from). | |
finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? } | |
column = finder_class.columns_hash[attr_name.to_s] | |
if value.nil? | |
comparison_operator = "IS ?" | |
elsif column.text? | |
if configuration[:case_sensitive] then | |
comparison_operator = "#{connection.case_sensitive_equality_operator} BINARY ?" | |
else | |
comparison_operator = "#{connection.case_sensitive_equality_operator} ?" | |
end | |
value = column.limit ? value.to_s[0, column.limit] : value.to_s | |
else | |
comparison_operator = "= ?" | |
end | |
sql_attribute = "#{record.class.quoted_table_name}.#{connection.quote_column_name(attr_name)}" | |
condition_sql = "#{sql_attribute} #{comparison_operator}" | |
condition_params = [value] | |
if scope = configuration[:scope] | |
Array(scope).map do |scope_item| | |
scope_value = record.send(scope_item) | |
condition_sql << " AND " << attribute_condition("#{record.class.quoted_table_name}.#{scope_item}", scope_value) | |
condition_params << scope_value | |
end | |
end | |
unless record.new_record? | |
condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" | |
condition_params << record.send(:id) | |
end | |
finder_class.with_exclusive_scope do | |
if finder_class.exists?([condition_sql, *condition_params]) | |
record.errors.add(attr_name, :taken, :default => configuration[:message], :value => value) | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment