Changeset 8671
- Timestamp:
- 01/19/08 03:45:24 (4 months ago)
- Files:
-
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record/aggregations.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/base.rb (modified) (3 diffs)
- trunk/activerecord/test/cases/finder_test.rb (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/CHANGELOG
r8662 r8671 1 1 *SVN* 2 3 * Support aggregations in finder conditions. #10572 [Ryan Kinderman] 2 4 3 5 * Organize and clean up the Active Record test suite. #10742 [John Barnette] trunk/activerecord/lib/active_record/aggregations.rb
r8510 r8671 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: trunk/activerecord/lib/active_record/base.rb
r8571 r8671 1561 1561 end 1562 1562 1563 # Similar in purpose to +expand_hash_conditions_for_aggregates+. 1564 def expand_attribute_names_for_aggregates(attribute_names) 1565 expanded_attribute_names = [] 1566 attribute_names.each do |attribute_name| 1567 unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil? 1568 aggregate_mapping(aggregation).each do |field_attr, aggregate_attr| 1569 expanded_attribute_names << field_attr 1570 end 1571 else 1572 expanded_attribute_names << attribute_name 1573 end 1574 end 1575 expanded_attribute_names 1576 end 1577 1563 1578 def all_attributes_exists?(attribute_names) 1579 attribute_names = expand_attribute_names_for_aggregates(attribute_names) 1564 1580 attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) } 1565 1581 end … … 1802 1818 end 1803 1819 1820 def aggregate_mapping(reflection) 1821 mapping = reflection.options[:mapping] || [reflection.name, reflection.name] 1822 mapping.first.is_a?(Array) ? mapping : [mapping] 1823 end 1824 1825 # Accepts a hash of sql conditions and replaces those attributes 1826 # that correspond to a +composed_of+ relationship with their expanded 1827 # aggregate attribute values. 1828 # Given: 1829 # class Person < ActiveRecord::Base 1830 # composed_of :address, :class_name => "Address", 1831 # :mapping => [%w(address_street street), %w(address_city city)] 1832 # end 1833 # Then: 1834 # { :address => Address.new("813 abc st.", "chicago") } 1835 # # => { :address_street => "813 abc st.", :address_city => "chicago" } 1836 def expand_hash_conditions_for_aggregates(attrs) 1837 expanded_attrs = {} 1838 attrs.each do |attr, value| 1839 unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil? 1840 mapping = aggregate_mapping(aggregation) 1841 mapping.each do |field_attr, aggregate_attr| 1842 if mapping.size == 1 && !value.respond_to?(aggregate_attr) 1843 expanded_attrs[field_attr] = value 1844 else 1845 expanded_attrs[field_attr] = value.send(aggregate_attr) 1846 end 1847 end 1848 else 1849 expanded_attrs[attr] = value 1850 end 1851 end 1852 expanded_attrs 1853 end 1854 1804 1855 # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. 1805 1856 # { :name => "foo'bar", :group_id => 4 } … … 1811 1862 # { 'other_records.id' => 7 } 1812 1863 # # => "`other_records`.`id` = 7" 1864 # And for value objects on a composed_of relationship: 1865 # { :address => Address.new("123 abc st.", "chicago") } 1866 # # => "address_street='123 abc st.' and address_city='chicago'" 1813 1867 def sanitize_sql_hash_for_conditions(attrs) 1868 attrs = expand_hash_conditions_for_aggregates(attrs) 1869 1814 1870 conditions = attrs.map do |attr, value| 1815 1871 attr = attr.to_s trunk/activerecord/test/cases/finder_test.rb
r8661 r8671 8 8 require 'models/developer' 9 9 require 'models/post' 10 require 'models/customer' 10 11 11 12 class FinderTest < ActiveSupport::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 … … 39 40 40 41 assert_raise(NoMethodError) { Topic.exists?([1,2]) } 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)) 41 57 end 42 58 … … 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 … … 237 261 assert_not_nil topic 238 262 assert_nil topic.last_read 263 end 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 239 305 end 240 306 … … 363 429 end 364 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 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 … … 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") … … 465 591 assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") 466 592 assert !another.new_record? 593 end 594 595 def test_find_or_create_from_two_attributes_with_one_being_an_aggregate 596 number_of_customers = Customer.count 597 created_customer = Customer.find_or_create_by_balance_and_name(Money.new(123), "Elizabeth") 598 assert_equal number_of_customers + 1, Customer.count 599 assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123), "Elizabeth") 600 assert !created_customer.new_record? 467 601 end 468 602 … … 478 612 end 479 613 614 def test_find_or_create_from_one_aggregate_attribute 615 number_of_customers = Customer.count 616 created_customer = Customer.find_or_create_by_balance(Money.new(123)) 617 assert_equal number_of_customers + 1, Customer.count 618 assert_equal created_customer, Customer.find_or_create_by_balance(Money.new(123)) 619 assert !created_customer.new_record? 620 end 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? 638 end 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? 484 644 end 485 645 … … 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}) … … 520 687 assert_equal 23, sig38.client_of 521 688 assert sig38.new_record? 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? 522 698 end 523 699