Ticket #10572: find_conditions_with_aggregation_support.diff
| File find_conditions_with_aggregation_support.diff, 15.9 kB (added by ryankinderman, 7 months ago) |
|---|
-
activerecord/test/finder_test.rb
old new 7 7 require 'fixtures/entrant' 8 8 require 'fixtures/developer' 9 9 require 'fixtures/post' 10 require 'fixtures/customer' 10 11 11 12 class FinderTest < Test::Unit::TestCase 12 fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors 13 fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :customers 13 14 14 15 def test_find 15 16 assert_equal(topics(:first).title, Topic.find(1).title) … … 39 40 40 41 assert_raise(NoMethodError) { Topic.exists?([1,2]) } 41 42 end 43 44 def test_exists_with_aggregate_having_three_mappings 45 existing_address = customers(:david).address 46 assert Customer.exists?(:address => existing_address) 47 end 48 49 def test_exists_with_aggregate_having_three_mappings_with_one_difference 50 existing_address = customers(:david).address 51 assert !Customer.exists?(:address => 52 Address.new(existing_address.street, existing_address.city, existing_address.country + "1")) 53 assert !Customer.exists?(:address => 54 Address.new(existing_address.street, existing_address.city + "1", existing_address.country)) 55 assert !Customer.exists?(:address => 56 Address.new(existing_address.street + "1", existing_address.city, existing_address.country)) 57 end 42 58 43 59 def test_find_by_array_of_one_id 44 60 assert_kind_of(Array, Topic.find([ 1 ])) … … 173 189 assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } 174 190 end 175 191 192 def test_find_on_hash_conditions_with_explicit_table_name_and_aggregate 193 david = customers(:david) 194 assert Customer.find(david.id, :conditions => { 'customers.name' => david.name, :address => david.address }) 195 assert_raises(ActiveRecord::RecordNotFound) { 196 Customer.find(david.id, :conditions => { 'customers.name' => david.name + "1", :address => david.address }) 197 } 198 end 199 176 200 def test_find_on_association_proxy_conditions 177 201 assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort 178 202 end … … 238 262 assert_nil topic.last_read 239 263 end 240 264 265 def test_hash_condition_find_with_aggregate_having_one_mapping 266 balance = customers(:david).balance 267 assert_kind_of Money, balance 268 found_customer = Customer.find(:first, :conditions => {:balance => balance}) 269 assert_equal customers(:david), found_customer 270 end 271 272 def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_aggregate 273 gps_location = customers(:david).gps_location 274 assert_kind_of GpsLocation, gps_location 275 found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location}) 276 assert_equal customers(:david), found_customer 277 end 278 279 def test_hash_condition_find_with_aggregate_having_one_mapping_and_key_value_being_attribute_value 280 balance = customers(:david).balance 281 assert_kind_of Money, balance 282 found_customer = Customer.find(:first, :conditions => {:balance => balance.amount}) 283 assert_equal customers(:david), found_customer 284 end 285 286 def test_hash_condition_find_with_aggregate_attribute_having_same_name_as_field_and_key_value_being_attribute_value 287 gps_location = customers(:david).gps_location 288 assert_kind_of GpsLocation, gps_location 289 found_customer = Customer.find(:first, :conditions => {:gps_location => gps_location.gps_location}) 290 assert_equal customers(:david), found_customer 291 end 292 293 def test_hash_condition_find_with_aggregate_having_three_mappings 294 address = customers(:david).address 295 assert_kind_of Address, address 296 found_customer = Customer.find(:first, :conditions => {:address => address}) 297 assert_equal customers(:david), found_customer 298 end 299 300 def test_hash_condition_find_with_one_condition_being_aggregate_and_another_not 301 address = customers(:david).address 302 assert_kind_of Address, address 303 found_customer = Customer.find(:first, :conditions => {:address => address, :name => customers(:david).name}) 304 assert_equal customers(:david), found_customer 305 end 306 241 307 def test_bind_variables 242 308 assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"]) 243 309 assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"]) … … 361 427 def test_find_by_one_attribute_with_conditions 362 428 assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) 363 429 end 430 431 def test_find_by_one_attribute_that_is_an_aggregate 432 address = customers(:david).address 433 assert_kind_of Address, address 434 found_customer = Customer.find_by_address(address) 435 assert_equal customers(:david), found_customer 436 end 437 438 def test_find_by_one_attribute_that_is_an_aggregate_with_one_attribute_difference 439 address = customers(:david).address 440 assert_kind_of Address, address 441 missing_address = Address.new(address.street, address.city, address.country + "1") 442 assert_nil Customer.find_by_address(missing_address) 443 missing_address = Address.new(address.street, address.city + "1", address.country) 444 assert_nil Customer.find_by_address(missing_address) 445 missing_address = Address.new(address.street + "1", address.city, address.country) 446 assert_nil Customer.find_by_address(missing_address) 447 end 364 448 449 def test_find_by_two_attributes_that_are_both_aggregates 450 balance = customers(:david).balance 451 address = customers(:david).address 452 assert_kind_of Money, balance 453 assert_kind_of Address, address 454 found_customer = Customer.find_by_balance_and_address(balance, address) 455 assert_equal customers(:david), found_customer 456 end 457 458 def test_find_by_two_attributes_with_one_being_an_aggregate 459 balance = customers(:david).balance 460 assert_kind_of Money, balance 461 found_customer = Customer.find_by_balance_and_name(balance, customers(:david).name) 462 assert_equal customers(:david), found_customer 463 end 464 365 465 def test_dynamic_finder_on_one_attribute_with_conditions_caches_method 366 466 # ensure this test can run independently of order 367 467 class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit) … … 405 505 assert_equal [], Topic.find_all_by_title("The First Topic!!") 406 506 end 407 507 508 def test_find_all_by_one_attribute_that_is_an_aggregate 509 balance = customers(:david).balance 510 assert_kind_of Money, balance 511 found_customers = Customer.find_all_by_balance(balance) 512 assert_equal 1, found_customers.size 513 assert_equal customers(:david), found_customers.first 514 end 515 516 def test_find_all_by_two_attributes_that_are_both_aggregates 517 balance = customers(:david).balance 518 address = customers(:david).address 519 assert_kind_of Money, balance 520 assert_kind_of Address, address 521 found_customers = Customer.find_all_by_balance_and_address(balance, address) 522 assert_equal 1, found_customers.size 523 assert_equal customers(:david), found_customers.first 524 end 525 526 def test_find_all_by_two_attributes_with_one_being_an_aggregate 527 balance = customers(:david).balance 528 assert_kind_of Money, balance 529 found_customers = Customer.find_all_by_balance_and_name(balance, customers(:david).name) 530 assert_equal 1, found_customers.size 531 assert_equal customers(:david), found_customers.first 532 end 533 408 534 def test_find_all_by_one_attribute_with_options 409 535 topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") 410 536 assert topics(:first), topics.last … … 457 583 assert_equal sig38, Company.find_or_create_by_name("38signals") 458 584 assert !sig38.new_record? 459 585 end 586 587 def test_find_or_create_from_one_aggregate_attribute 588 number_of_customers = Customer.count 589 created_customer = Customer.find_or_create_by_balance(Money.new(123)) 590 assert_equal number_of_customers + 1, Customer.count 591 assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) 592 assert !created_customer.new_record? 593 end 460 594 461 595 def test_find_or_create_from_two_attributes 462 596 number_of_topics = Topic.count … … 465 599 assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") 466 600 assert !another.new_record? 467 601 end 602 603 def test_find_or_create_from_two_attributes_with_one_being_an_aggregate 604 number_of_customers = Customer.count 605 created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") 606 assert_equal number_of_customers + 1, Customer.count 607 assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") 608 assert !created_customer.new_record? 609 end 468 610 469 611 def test_find_or_create_from_one_attribute_and_hash 470 612 number_of_companies = Company.count … … 477 619 assert_equal 23, sig38.client_of 478 620 end 479 621 622 def test_find_or_create_from_one_aggregate_attribute_and_hash 623 number_of_customers = Customer.count 624 balance = Money.new(123) 625 name = "Elizabeth" 626 created_customer = Customer.find_or_create_by_balance({:balance => balance, :name => name}) 627 assert_equal number_of_customers + 1, Customer.count 628 assert_equal created_customer, Customer.find_or_create_by_balance({:balance => balance, :name => name}) 629 assert !created_customer.new_record? 630 assert_equal balance, created_customer.balance 631 assert_equal name, created_customer.name 632 end 633 480 634 def test_find_or_initialize_from_one_attribute 481 635 sig38 = Company.find_or_initialize_by_name("38signals") 482 636 assert_equal "38signals", sig38.name 483 637 assert sig38.new_record? 484 638 end 485 639 640 def test_find_or_initialize_from_one_aggregate_attribute 641 new_customer = Customer.find_or_initialize_by_balance(Money.new(123)) 642 assert_equal 123, new_customer.balance.amount 643 assert new_customer.new_record? 644 end 645 486 646 def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected 487 647 c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) 488 648 assert_equal "Fortune 1000", c.name … … 513 673 assert another.new_record? 514 674 end 515 675 676 def test_find_or_initialize_from_one_aggregate_attribute_and_one_not 677 new_customer = Customer.find_or_initialize_by_balance_and_name(Money.new(123), "Elizabeth") 678 assert_equal 123, new_customer.balance.amount 679 assert_equal "Elizabeth", new_customer.name 680 assert new_customer.new_record? 681 end 682 516 683 def test_find_or_initialize_from_one_attribute_and_hash 517 684 sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) 518 685 assert_equal "38signals", sig38.name … … 520 687 assert_equal 23, sig38.client_of 521 688 assert sig38.new_record? 522 689 end 690 691 def test_find_or_initialize_from_one_aggregate_attribute_and_hash 692 balance = Money.new(123) 693 name = "Elizabeth" 694 new_customer = Customer.find_or_initialize_by_balance({:balance => balance, :name => name}) 695 assert_equal balance, new_customer.balance 696 assert_equal name, new_customer.name 697 assert new_customer.new_record? 698 end 523 699 524 700 def test_find_with_bad_sql 525 701 assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } -
activerecord/lib/active_record/base.rb
old new 1550 1550 attributes 1551 1551 end 1552 1552 1553 # Similar in purpose to +expand_hash_conditions_for_aggregates+. 1554 def expand_attribute_names_for_aggregates(attribute_names) 1555 expanded_attribute_names = [] 1556 attribute_names.each do |attribute_name| 1557 unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil? 1558 aggregate_mapping(aggregation).each do |field_attr, aggregate_attr| 1559 expanded_attribute_names << field_attr 1560 end 1561 else 1562 expanded_attribute_names << attribute_name 1563 end 1564 end 1565 expanded_attribute_names 1566 end 1567 1553 1568 def all_attributes_exists?(attribute_names) 1569 attribute_names = expand_attribute_names_for_aggregates(attribute_names) 1554 1570 attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) } 1555 1571 end 1556 1572 … … 1791 1807 end 1792 1808 end 1793 1809 1810 def aggregate_mapping(reflection) 1811 mapping = reflection.options[:mapping] || [reflection.name, reflection.name] 1812 mapping.first.is_a?(Array) ? mapping : [mapping] 1813 end 1814 1815 # Accepts a hash of sql conditions and replaces those attributes 1816 # that correspond to a +composed_of+ relationship with their expanded 1817 # aggregate attribute values. 1818 # Given: 1819 # class Person < ActiveRecord::Base 1820 # composed_of :address, :class_name => "Address", 1821 # :mapping => [%w(address_street street), %w(address_city city)] 1822 # end 1823 # Then: 1824 # { :address => Address.new("813 abc st.", "chicago") } 1825 # # => { :address_street => "813 abc st.", :address_city => "chicago" } 1826 def expand_hash_conditions_for_aggregates(attrs) 1827 expanded_attrs = {} 1828 attrs.each do |attr, value| 1829 unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil? 1830 mapping = aggregate_mapping(aggregation) 1831 mapping.each do |field_attr, aggregate_attr| 1832 if mapping.size == 1 && !value.respond_to?(aggregate_attr) 1833 expanded_attrs[field_attr] = value 1834 else 1835 expanded_attrs[field_attr] = value.send(aggregate_attr) 1836 end 1837 end 1838 else 1839 expanded_attrs[attr] = value 1840 end 1841 end 1842 expanded_attrs 1843 end 1844 1794 1845 # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. 1795 1846 # { :name => "foo'bar", :group_id => 4 } 1796 1847 # # => "name='foo''bar' and group_id= 4" … … 1800 1851 # # => "age BETWEEN 13 AND 18" 1801 1852 # { 'other_records.id' => 7 } 1802 1853 # # => "`other_records`.`id` = 7" 1854 # And for value objects on a composed_of relationship: 1855 # { :address => Address.new("123 abc st.", "chicago") } 1856 # # => "address_street='123 abc st.' and address_city='chicago'" 1803 1857 def sanitize_sql_hash_for_conditions(attrs) 1858 attrs = expand_hash_conditions_for_aggregates(attrs) 1859 1804 1860 conditions = attrs.map do |attr, value| 1805 1861 attr = attr.to_s 1806 1862 -
activerecord/lib/active_record/aggregations.rb
old new 108 108 # 109 109 # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects 110 110 # immutable on http://c2.com/cgi/wiki?ValueObjectsShouldBeImmutable 111 # 112 # == Finding records by a value object 113 # 114 # Once a +composed_of+ relationship is specified for a model, records can be loaded from the database by specifying an instance 115 # of the value object in the conditions hash. The following example finds all customers with +balance_amount+ equal to 20 and 116 # +balance_currency+ equal to "USD": 117 # 118 # Customer.find(:all, :conditions => {:balance => Money.new(20, "USD")}) 119 # 111 120 module ClassMethods 112 121 # Adds reader and writer methods for manipulating a value object: 113 122 # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.