diff options
author | Jeremy Kemper <jeremy@bitsweat.net> | 2008-03-31 01:50:07 +0000 |
---|---|---|
committer | Jeremy Kemper <jeremy@bitsweat.net> | 2008-03-31 01:50:07 +0000 |
commit | c52771e7a022a45f2b70bfc8f29b02d008fb9b15 (patch) | |
tree | 247e874a15fbf20d6e0c13d35cc11f4f7e73f3c6 /activerecord/lib | |
parent | 97019f9f5a94fa102debe13f5a957726e7d4783e (diff) | |
download | rails-c52771e7a022a45f2b70bfc8f29b02d008fb9b15.tar.gz rails-c52771e7a022a45f2b70bfc8f29b02d008fb9b15.tar.bz2 rails-c52771e7a022a45f2b70bfc8f29b02d008fb9b15.zip |
Fix case-sensitive validates_uniqueness_of. Closes #11366 [miloops]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9160 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
-rwxr-xr-x | activerecord/lib/active_record/validations.rb | 56 |
1 files changed, 37 insertions, 19 deletions
diff --git a/activerecord/lib/active_record/validations.rb b/activerecord/lib/active_record/validations.rb index aded3c0e85..5e2f710db6 100755 --- a/activerecord/lib/active_record/validations.rb +++ b/activerecord/lib/active_record/validations.rb @@ -602,8 +602,8 @@ module ActiveRecord # # Configuration options: # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken") - # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniquness constraint. - # * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (true by default). + # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint. + # * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (false by default). # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) # * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should @@ -613,14 +613,30 @@ module ActiveRecord # not occur (e.g. :unless => :skip_validation, or :unless => Proc.new { |user| user.signup_step <= 2 }). The # method, proc or string should return or evaluate to a true or false value. def validates_uniqueness_of(*attr_names) - configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken], :case_sensitive => true } + configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } configuration.update(attr_names.extract_options!) validates_each(attr_names,configuration) do |record, attr_name, value| - if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?) + # 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? } + + if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?) condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}" condition_params = [value] else + # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't. + # Hence, this is needed only for sqlite. condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}" condition_params = [value.downcase] end @@ -638,22 +654,24 @@ module ActiveRecord condition_params << record.send(:id) end - # 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? } + results = connection.select_all( + construct_finder_sql( + :select => "#{attr_name}", + :from => "#{finder_class.quoted_table_name}", + :conditions => [condition_sql, *condition_params] + ) + ) + + unless results.length.zero? + found = true + + # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate + # column in ruby when case sensitive option + if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text? + found = results.any? { |a| a[attr_name.to_s] == value } + end - if finder_class.find(:first, :conditions => [condition_sql, *condition_params]) - record.errors.add(attr_name, configuration[:message]) + record.errors.add(attr_name, configuration[:message]) if found end end end |