Changeset 4264
- Timestamp:
- 04/25/06 05:25:04 (4 years ago)
- Files:
-
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record/associations.rb (modified) (2 diffs)
- trunk/activerecord/lib/active_record/calculations.rb (modified) (7 diffs)
- trunk/activerecord/test/calculations_test.rb (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/CHANGELOG
r4249 r4264 1 1 *SVN* 2 3 * Allow all calculations to take the :include option, not just COUNT (closes #4840) [Rick] 2 4 3 5 * Update inconsistent migrations documentation. #4683 [machomagna@gmail.com] trunk/activerecord/lib/active_record/associations.rb
r4232 r4264 967 967 end 968 968 969 def count_with_associations(options = {})970 catch :invalid_query do971 join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])972 return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))973 end974 0975 end976 977 969 def find_with_associations(options = {}) 978 970 catch :invalid_query do … … 1117 1109 "#{name} Load Including Associations" 1118 1110 ) 1119 end1120 1121 def construct_counter_sql_with_included_associations(options, join_dependency)1122 scope = scope(:find)1123 sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"1124 1125 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.1126 if !Base.connection.supports_count_distinct?1127 sql = "SELECT COUNT(*) FROM (SELECT DISTINCT #{table_name}.#{primary_key}"1128 end1129 1130 sql << " FROM #{table_name} "1131 sql << join_dependency.join_associations.collect{|join| join.association_join }.join1132 1133 add_joins!(sql, options, scope)1134 add_conditions!(sql, options[:conditions], scope)1135 add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])1136 1137 add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)1138 1139 if !Base.connection.supports_count_distinct?1140 sql << ")"1141 end1142 1143 return sanitize_sql(sql)1144 1111 end 1145 1112 trunk/activerecord/lib/active_record/calculations.rb
r4237 r4264 1 1 module ActiveRecord 2 2 module Calculations #:nodoc: 3 CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset ]3 CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include] 4 4 def self.included(base) 5 5 base.extend(ClassMethods) … … 43 43 # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead. 44 44 def count(*args) 45 column_name, options = construct_count_options_from_legacy_args(*args) 46 47 if options[:include] || scope(:find, :include) 48 count_with_associations(options) 49 else 50 calculate(:count, column_name, options) 51 end 45 calculate(:count, *construct_count_options_from_legacy_args(*args)) 52 46 end 53 47 … … 121 115 column_name = '*' if column_name == :all 122 116 column = column_for column_name 123 aggregate = select_aggregate(operation, column_name, options) 124 aggregate_alias = column_alias_for(operation, column_name) 125 if options[:group] 126 execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 127 else 128 execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) 129 end 117 catch :invalid_query do 118 if options[:group] 119 return execute_grouped_calculation(operation, column_name, column, options) 120 else 121 return execute_simple_calculation(operation, column_name, column, options) 122 end 123 end 124 0 130 125 end 131 126 … … 144 139 # Handle legacy paramter options: def count(conditions=nil, joins=nil) 145 140 options.merge!(:conditions => args[0]) if args.length > 0 146 options.merge!(:joins => args[1])if args.length > 1141 options.merge!(:joins => args[1]) if args.length > 1 147 142 end 148 143 else … … 152 147 end 153 148 154 def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc: 155 scope = scope(:find) 156 sql = "SELECT #{aggregate} AS #{aggregate_alias}" 149 def construct_calculation_sql(operation, column_name, options) #:nodoc: 150 scope = scope(:find) 151 merged_includes = merge_includes(scope ? scope[:include] : [], options[:include]) 152 aggregate_alias = column_alias_for(operation, column_name) 153 use_workaround = !Base.connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count' 154 join_dependency = nil 155 156 if merged_includes.any? && operation.to_s.downcase == 'count' 157 options[:distinct] = true 158 column_name = [table_name, primary_key] * '.' 159 end 160 161 sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}" 162 163 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT. 164 sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround 165 157 166 sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group] 167 sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround 158 168 sql << " FROM #{table_name} " 169 if merged_includes.any? 170 join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins]) 171 sql << join_dependency.join_associations.collect{|join| join.association_join }.join 172 end 159 173 add_joins!(sql, options, scope) 160 174 add_conditions!(sql, options[:conditions], scope) 161 175 sql << " GROUP BY #{options[:group_field]}" if options[:group] 162 sql << " HAVING #{options[:having]}" if options[:group] && options[:having] 163 sql << " ORDER BY #{options[:order]}" if options[:order] 164 add_limit!(sql, options) 176 sql << " HAVING #{options[:having]}" if options[:group] && options[:having] 177 sql << " ORDER BY #{options[:order]}" if options[:order] 178 add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) 179 add_limit!(sql, options, scope) 180 sql << ')' if use_workaround 165 181 sql 166 182 end 167 183 168 def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias,options) #:nodoc:169 value = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options))184 def execute_simple_calculation(operation, column_name, column, options) #:nodoc: 185 value = connection.select_value(construct_calculation_sql(operation, column_name, options)) 170 186 type_cast_calculated_value(value, column, operation) 171 187 end 172 188 173 def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias,options) #:nodoc:189 def execute_grouped_calculation(operation, column_name, column, options) #:nodoc: 174 190 group_attr = options[:group].to_s 175 191 association = reflect_on_association(group_attr.to_sym) … … 178 194 group_alias = column_alias_for(group_field) 179 195 group_column = column_for group_field 180 sql = construct_calculation_sql( aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias))196 sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias)) 181 197 calculated_data = connection.select_all(sql) 198 aggregate_alias = column_alias_for(operation, column_name) 182 199 183 200 if association … … 196 213 private 197 214 def validate_calculation_options(operation, options = {}) 198 if operation.to_s == 'count' 199 options.assert_valid_keys(CALCULATIONS_OPTIONS + [:include]) 200 else 201 options.assert_valid_keys(CALCULATIONS_OPTIONS) 202 end 203 end 204 205 def select_aggregate(operation, column_name, options) 206 "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})" 215 options.assert_valid_keys(CALCULATIONS_OPTIONS) 207 216 end 208 217 trunk/activerecord/test/calculations_test.rb
r4185 r4264 20 20 def test_should_get_maximum_of_field 21 21 assert_equal 60, Account.maximum(:credit_limit) 22 end 23 24 def test_should_get_maximum_of_field_with_include 25 assert_equal 50, Account.maximum(:credit_limit, :include => :firm, :conditions => "companies.name != 'Summit'") 26 end 27 28 def test_should_get_maximum_of_field_with_scoped_include 29 Account.with_scope :find => { :include => :firm, :conditions => "companies.name != 'Summit'" } do 30 assert_equal 50, Account.maximum(:credit_limit) 31 end 22 32 end 23 33 … … 175 185 end 176 186 177 assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :include => :posts) }178 187 assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :sum, :foo => :bar) } 179 188 assert_raises(ArgumentError) { Company.send(:validate_calculation_options, :count, :foo => :bar) }