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

Changeset 3653

Show
Ignore:
Timestamp:
02/25/06 23:41:51 (3 years ago)
Author:
david
Message:

Compatibility patches for calculations

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/lib/active_record/calculations.rb

    r3646 r3653  
    116116      #   Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors 
    117117      def calculate(operation, column_name, options = {}) 
    118         column_name = '*' if column_name == :all 
    119         column     = columns.detect { |c| c.name.to_s == column_name.to_s } 
     118        column_name     = '*' if column_name == :all 
     119        column          = columns.detect { |c| c.name.to_s == column_name.to_s } 
     120        aggregate       = select_aggregate(operation, column_name, options) 
     121        aggregate_alias = column_alias_for(operation, column_name) 
    120122        if options[:group] 
    121           execute_grouped_calculation(operation, column_name, column, options) 
     123          execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 
    122124        else 
    123           execute_simple_calculation(operation, column_name, column, options) 
     125          execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 
    124126        end 
    125127      end 
    126128 
    127129      protected 
    128       def construct_calculation_sql(operation, column_name, options) 
    129         sql  = ["SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})"] 
    130         sql << ", #{options[:group_field]}" if options[:group] 
     130      def construct_calculation_sql(aggregate, aggregate_alias, options) 
     131        sql  = ["SELECT #{aggregate} AS #{aggregate_alias}"] 
     132        sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] 
    131133        sql << " FROM #{table_name} " 
    132134        add_joins!(sql, options) 
     
    137139      end 
    138140 
    139       def execute_simple_calculation(operation, column_name, column, options) 
    140         value  = connection.select_value(construct_calculation_sql(operation, column_name, options)) 
     141      def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 
     142        value     = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options)) 
    141143        type_cast_calculated_value(value, column, operation) 
    142144      end 
    143145 
    144       def execute_grouped_calculation(operation, column_name, column, options) 
     146      def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 
    145147        group_attr      = options[:group].to_s 
    146148        association     = reflect_on_association(group_attr.to_sym) 
    147149        associated      = association && association.macro == :belongs_to # only count belongs_to associations 
    148150        group_field     = (associated ? "#{options[:group]}_id" : options[:group]).to_s 
    149         sql             = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field)) 
     151        group_alias     = column_alias_for(group_field) 
     152        sql             = construct_calculation_sql(aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias)) 
    150153        calculated_data = connection.select_all(sql) 
    151154 
    152155        if association 
    153           key_ids     = calculated_data.collect { |row| row[group_field] } 
     156          key_ids     = calculated_data.collect { |row| row[group_alias] } 
    154157          key_records = ActiveRecord::Base.send(:class_of_active_record_descendant, association.klass).find(key_ids) 
    155158          key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) } 
     
    157160 
    158161        calculated_data.inject(OrderedHash.new) do |all, row| 
    159           key   = associated ? key_records[row[group_field].to_i] : row[column_key(group_field)
    160           value = row[column_key("#{operation}(#{column_name})")
     162          key   = associated ? key_records[row[group_alias].to_i] : row[group_alias
     163          value = row[aggregate_alias
    161164          all << [key, type_cast_calculated_value(value, column, operation)] 
    162165        end 
     
    164167 
    165168      private 
     169      def select_aggregate(operation, column_name, options) 
     170        "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})" 
     171      end 
     172 
    166173      # converts a given key to the value that the database adapter returns as 
    167174      # 
    168       #   users.id #=> id 
    169       #   sum(id) #=> sum(id) 
    170       # 
    171       # psql strips off the () function too 
    172       #  
    173       #   sum(id) #=> sum 
    174       # 
    175       # Should this go in a DB Adapter? 
    176       def column_key(key) 
    177         return key.split('.').last unless key =~ /\(/ # split off table alias 
    178         case connection 
    179           when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter 
    180             key.split('(').first.split('.').last 
    181           else 
    182             sql_func, sql_args = key.split('(') 
    183             "#{sql_func.split('.').last}(#{sql_args}" 
    184         end 
     175      #   users.id #=> users_id 
     176      #   sum(id) #=> sum_id 
     177      #   count(distinct users.id) #=> count_distinct_users_id 
     178      #   count(*) #=> count_all 
     179      def column_alias_for(*keys) 
     180        keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_') 
    185181      end 
    186182 
  • trunk/activerecord/test/calculations_test.rb

    r3646 r3653  
    109109   
    110110  def test_should_calculate_grouped_by_function_with_table_alias 
    111     c = Topic.count(:all, :group => 'DATE(topics.written_on)') 
    112     assert_equal 1, c["2003-07-15"] 
    113     assert_equal 1, c["2003-07-16"] 
     111    c = Company.count(:all, :group => 'UPPER(companies.type)') 
     112    assert_equal 2, c[nil] 
     113    assert_equal 1, c['DEPENDENTFIRM'] 
     114    assert_equal 3, c['CLIENT'] 
     115    assert_equal 2, c['FIRM'] 
    114116  end 
    115117