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

Ticket #9487: parallel_calculations_with_tests.diff

File parallel_calculations_with_tests.diff, 5.8 kB (added by jcoglan, 5 months ago)
  • activerecord/lib/active_record/calculations.rb

    old new  
    111111      #   Person.average(:age) # SELECT AVG(age) FROM people... 
    112112      #   Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake' 
    113113      #   Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors 
    114       def calculate(operation, column_name, options = {}) 
     114      # 
     115      # You can also execute several calculations using a single query to speed things along. To do this, pass a hash as the first argument. 
     116      # the +column_name+ argument is omitted in this case. 
     117      # 
     118      # Examples: 
     119      # 
     120      #   Person.calculate(:how_many => [:count, '*'], :total_age => [:sum, :age]) 
     121      #   #=> {:how_many => 12, :total_age => 387} 
     122      #    
     123      #   Person.calculate({:how_many => [:count, '*'], :total_age => [:sum, :age]}, :conditions => ['age < ?', 30]) 
     124      #   #=> {:how_many => 7, :total_age => 94} 
     125      def calculate(operation, *args) 
     126        column_name = operation.is_a?(Hash) ? nil : args.shift 
     127        options = args.first || {} 
     128 
    115129        validate_calculation_options(operation, options) 
    116130        column_name     = options[:select] if options[:select] 
    117131        column_name     = '*' if column_name == :all 
     
    149163        end 
    150164 
    151165        def construct_calculation_sql(operation, column_name, options) #:nodoc: 
    152           operation = operation.to_s.downcase 
     166          operation = operation.to_s.downcase unless operation.is_a?(Hash) 
    153167          options = options.symbolize_keys 
    154168 
    155169          scope           = scope(:find) 
     
    167181            end 
    168182          end 
    169183 
    170           sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 
     184          if calculations = extract_query_information_from_operation(operation) 
     185            sql = "SELECT " + calculations.collect do |calc| 
     186              calculation_column_name = calc[:column] 
     187              calculation_column_name = '*' if calc[:column].blank? 
     188              "#{calc[:operation]}(#{'DISTINCT ' if options[:distinct]}#{calculation_column_name}) AS #{column_alias_for(calc[:operation], calc[:alias])}" 
     189            end.join(", ") 
     190          else 
     191            sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 
     192          end 
    171193 
    172194          # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 
    173195          sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround 
     
    204226          sql 
    205227        end 
    206228 
     229        def extract_query_information_from_operation(operation) 
     230          return nil unless operation.is_a?(Hash) 
     231          calculations = [] 
     232          operation.each do |key, value| 
     233            value = value.to_s.to_a unless value.is_a?(Array) 
     234            calculations << {:alias => key.to_s, :distinct_alias => "#{key}_#{value[0]}_alias", 
     235                :operation => value[0].to_s.downcase, :column => value[1].to_s} 
     236          end 
     237          calculations 
     238        end 
     239 
    207240        def execute_simple_calculation(operation, column_name, column, options) #:nodoc: 
    208           value = connection.select_value(construct_calculation_sql(operation, column_name, options)) 
    209           type_cast_calculated_value(value, column, operation) 
     241          results = connection.select_one(construct_calculation_sql(operation, column_name, options)) 
     242          if operation.is_a?(Hash) 
     243            calculation_results = {} 
     244            calculations = extract_query_information_from_operation(operation) 
     245            results.each do |key, value| 
     246              calculation = calculations.find { |calc| key == column_alias_for(calc[:operation], calc[:alias]) } 
     247              calculation_results[calculation[:alias].to_sym] = type_cast_calculated_value(value, column_for(calculation[:column]), calculation[:operation]) 
     248            end 
     249            calculation_results 
     250          else 
     251            type_cast_calculated_value(results.values.first, column, operation) 
     252          end 
    210253        end 
    211254 
    212255        def execute_grouped_calculation(operation, column_name, column, options) #:nodoc: 
  • activerecord/test/cases/calculations_test.rb

    old new  
    248248  def test_count_with_too_many_parameters_raises 
    249249    assert_raise(ArgumentError) { Account.count(1, 2, 3) } 
    250250  end 
     251 
     252  def test_parallel_calculations 
     253    account_calculations = Account.calculate(:how_many => :count, :total_credit => [:sum, :credit_limit], :max_limit => [:max, :credit_limit]) 
     254    assert_equal 6, account_calculations[:how_many] 
     255    assert_equal 318, account_calculations[:total_credit] 
     256    assert_equal 60, account_calculations[:max_limit] 
     257  end 
     258 
     259  def test_parallel_calculations_with_conditions 
     260    account_calculations = Account.calculate({:how_many => [:count, '*'], :total_credit => [:sum, :credit_limit]}, :conditions => {:credit_limit => 50}) 
     261    assert_equal 3, account_calculations[:how_many] 
     262    assert_equal 150, account_calculations[:total_credit] 
     263  end 
     264 
     265  def test_parallel_calculations_with_conditions_and_distinct 
     266    account_calculations = Account.calculate( 
     267        {:how_many => [:count, :credit_limit], :total_credit => [:sum, :credit_limit]}, 
     268        :conditions => {:credit_limit => 50}, 
     269        :distinct => true 
     270    ) 
     271    assert_equal 1, account_calculations[:how_many] 
     272    assert_equal 50, account_calculations[:total_credit] 
     273  end 
    251274end