Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

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  
    77require 'fixtures/entrant' 
    88require 'fixtures/developer' 
    99require 'fixtures/post' 
     10require 'fixtures/customer' 
    1011 
    1112class 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 
    1314 
    1415  def test_find 
    1516    assert_equal(topics(:first).title, Topic.find(1).title) 
     
    3940 
    4041    assert_raise(NoMethodError) { Topic.exists?([1,2]) } 
    4142  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 
    4258 
    4359  def test_find_by_array_of_one_id 
    4460    assert_kind_of(Array, Topic.find([ 1 ])) 
     
    173189    assert_raises(ActiveRecord::RecordNotFound) { Topic.find(1, :conditions => { 'topics.approved' => true }) } 
    174190  end 
    175191 
     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 
    176200  def test_find_on_association_proxy_conditions 
    177201    assert_equal [1, 2, 3, 5, 6, 7, 8, 9, 10], Comment.find_all_by_post_id(authors(:david).posts).map(&:id).sort 
    178202  end 
     
    238262    assert_nil topic.last_read 
    239263  end 
    240264 
     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   
    241307  def test_bind_variables 
    242308    assert_kind_of Firm, Company.find(:first, :conditions => ["name = ?", "37signals"]) 
    243309    assert_nil Company.find(:first, :conditions => ["name = ?", "37signals!"]) 
     
    361427  def test_find_by_one_attribute_with_conditions 
    362428    assert_equal accounts(:rails_core_account), Account.find_by_credit_limit(50, :conditions => ['firm_id = ?', 6]) 
    363429  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 
    364448 
     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   
    365465  def test_dynamic_finder_on_one_attribute_with_conditions_caches_method 
    366466    # ensure this test can run independently of order 
    367467    class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.respond_to?(:find_by_credit_limit) 
     
    405505    assert_equal [], Topic.find_all_by_title("The First Topic!!") 
    406506  end 
    407507 
     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 
    408534  def test_find_all_by_one_attribute_with_options 
    409535    topics = Topic.find_all_by_content("Have a nice day", :order => "id DESC") 
    410536    assert topics(:first), topics.last 
     
    457583    assert_equal sig38, Company.find_or_create_by_name("38signals") 
    458584    assert !sig38.new_record? 
    459585  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 
    460594 
    461595  def test_find_or_create_from_two_attributes 
    462596    number_of_topics = Topic.count 
     
    465599    assert_equal another, Topic.find_or_create_by_title_and_author_name("Another topic", "John") 
    466600    assert !another.new_record? 
    467601  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   
    468610 
    469611  def test_find_or_create_from_one_attribute_and_hash 
    470612    number_of_companies = Company.count 
     
    477619    assert_equal 23, sig38.client_of 
    478620  end 
    479621 
     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 
    480634  def test_find_or_initialize_from_one_attribute 
    481635    sig38 = Company.find_or_initialize_by_name("38signals") 
    482636    assert_equal "38signals", sig38.name 
    483637    assert sig38.new_record? 
    484638  end 
    485639   
     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   
    486646  def test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected 
    487647    c = Company.find_or_initialize_by_name_and_rating("Fortune 1000", 1000) 
    488648    assert_equal "Fortune 1000", c.name 
     
    513673    assert another.new_record? 
    514674  end 
    515675 
     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 
    516683  def test_find_or_initialize_from_one_attribute_and_hash 
    517684    sig38 = Company.find_or_initialize_by_name({:name => "38signals", :firm_id => 17, :client_of => 23}) 
    518685    assert_equal "38signals", sig38.name 
     
    520687    assert_equal 23, sig38.client_of 
    521688    assert sig38.new_record? 
    522689  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 
    523699 
    524700  def test_find_with_bad_sql 
    525701    assert_raises(ActiveRecord::StatementInvalid) { Topic.find_by_sql "select 1 from badtable" } 
  • activerecord/lib/active_record/base.rb

    old new  
    15501550          attributes 
    15511551        end 
    15521552 
     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 
    15531568        def all_attributes_exists?(attribute_names) 
     1569          attribute_names = expand_attribute_names_for_aggregates(attribute_names) 
    15541570          attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) } 
    15551571        end 
    15561572 
     
    17911807          end 
    17921808        end 
    17931809 
     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 
    17941845        # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause. 
    17951846        #   { :name => "foo'bar", :group_id => 4 } 
    17961847        #     # => "name='foo''bar' and group_id= 4" 
     
    18001851        #     # => "age BETWEEN 13 AND 18" 
    18011852        #   { 'other_records.id' => 7 } 
    18021853        #     # => "`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'" 
    18031857        def sanitize_sql_hash_for_conditions(attrs) 
     1858          attrs = expand_hash_conditions_for_aggregates(attrs) 
     1859 
    18041860          conditions = attrs.map do |attr, value| 
    18051861            attr = attr.to_s 
    18061862 
  • activerecord/lib/active_record/aggregations.rb

    old new  
    108108    #  
    109109    # Read more about value objects on http://c2.com/cgi/wiki?ValueObject and on the dangers of not keeping value objects 
    110110    # 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    #  
    111120    module ClassMethods 
    112121      # Adds reader and writer methods for manipulating a value object: 
    113122      # <tt>composed_of :address</tt> adds <tt>address</tt> and <tt>address=(new_address)</tt> methods.