Changeset 9160
- Timestamp:
- 03/31/08 01:50:07 (3 months ago)
- Files:
-
- trunk/activerecord/lib/active_record/validations.rb (modified) (3 diffs)
- trunk/activerecord/test/cases/validations_test.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/lib/active_record/validations.rb
r9158 r9160 603 603 # Configuration options: 604 604 # * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken") 605 # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniqu ness constraint.606 # * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns ( true by default).605 # * <tt>scope</tt> - One or more columns by which to limit the scope of the uniqueness constraint. 606 # * <tt>case_sensitive</tt> - Looks for an exact match. Ignored by non-text columns (false by default). 607 607 # * <tt>allow_nil</tt> - If set to true, skips this validation if the attribute is null (default is: false) 608 608 # * <tt>allow_blank</tt> - If set to true, skips this validation if the attribute is blank (default is: false) … … 614 614 # method, proc or string should return or evaluate to a true or false value. 615 615 def validates_uniqueness_of(*attr_names) 616 configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] , :case_sensitive => true}616 configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] } 617 617 configuration.update(attr_names.extract_options!) 618 618 619 619 validates_each(attr_names,configuration) do |record, attr_name, value| 620 if value.nil? || (configuration[:case_sensitive] || !columns_hash[attr_name.to_s].text?)621 condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}"622 condition_params = [value]623 else624 condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}"625 condition_params = [value.downcase]626 end627 628 if scope = configuration[:scope]629 Array(scope).map do |scope_item|630 scope_value = record.send(scope_item)631 condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}"632 condition_params << scope_value633 end634 end635 636 unless record.new_record?637 condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?"638 condition_params << record.send(:id)639 end640 641 620 # The check for an existing value should be run from a class that 642 621 # isn't abstract. This means working down from the current class … … 653 632 finder_class = class_hierarchy.detect { |klass| !klass.abstract_class? } 654 633 655 if finder_class.find(:first, :conditions => [condition_sql, *condition_params]) 656 record.errors.add(attr_name, configuration[:message]) 634 if value.nil? || (configuration[:case_sensitive] || !finder_class.columns_hash[attr_name.to_s].text?) 635 condition_sql = "#{record.class.quoted_table_name}.#{attr_name} #{attribute_condition(value)}" 636 condition_params = [value] 637 else 638 # sqlite has case sensitive SELECT query, while MySQL/Postgresql don't. 639 # Hence, this is needed only for sqlite. 640 condition_sql = "LOWER(#{record.class.quoted_table_name}.#{attr_name}) #{attribute_condition(value)}" 641 condition_params = [value.downcase] 642 end 643 644 if scope = configuration[:scope] 645 Array(scope).map do |scope_item| 646 scope_value = record.send(scope_item) 647 condition_sql << " AND #{record.class.quoted_table_name}.#{scope_item} #{attribute_condition(scope_value)}" 648 condition_params << scope_value 649 end 650 end 651 652 unless record.new_record? 653 condition_sql << " AND #{record.class.quoted_table_name}.#{record.class.primary_key} <> ?" 654 condition_params << record.send(:id) 655 end 656 657 results = connection.select_all( 658 construct_finder_sql( 659 :select => "#{attr_name}", 660 :from => "#{finder_class.quoted_table_name}", 661 :conditions => [condition_sql, *condition_params] 662 ) 663 ) 664 665 unless results.length.zero? 666 found = true 667 668 # As MySQL/Postgres don't have case sensitive SELECT queries, we try to find duplicate 669 # column in ruby when case sensitive option 670 if configuration[:case_sensitive] && finder_class.columns_hash[attr_name.to_s].text? 671 found = results.any? { |a| a[attr_name.to_s] == value } 672 end 673 674 record.errors.add(attr_name, configuration[:message]) if found 657 675 end 658 676 end trunk/activerecord/test/cases/validations_test.rb
r9158 r9160 436 436 assert t2.valid?, "should validate with nil" 437 437 assert t2.save, "should save with nil" 438 end 439 440 def test_validate_case_sensitive_uniqueness 441 Topic.validates_uniqueness_of(:title, :case_sensitive => true, :allow_nil => true) 442 443 t = Topic.new("title" => "I'm unique!") 444 assert t.save, "Should save t as unique" 445 446 t.content = "Remaining unique" 447 assert t.save, "Should still save t as unique" 448 449 t2 = Topic.new("title" => "I'M UNIQUE!") 450 assert t2.valid?, "Should be valid" 451 assert t2.save, "Should save t2 as unique" 452 assert !t2.errors.on(:title) 453 assert !t2.errors.on(:parent_id) 454 assert_not_equal "has already been taken", t2.errors.on(:title) 455 456 t3 = Topic.new("title" => "I'M uNiQUe!") 457 assert t3.valid?, "Should be valid" 458 assert t3.save, "Should save t2 as unique" 459 assert !t3.errors.on(:title) 460 assert !t3.errors.on(:parent_id) 461 assert_not_equal "has already been taken", t3.errors.on(:title) 438 462 end 439 463