Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 322 lines (284 sloc) 16.049 kb
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
1 module ActiveRecord
fed7d33 @dhh Fixed documentation
dhh authored
2 module Calculations #:nodoc:
ddab9d7 @yukster Add :from option to calculations. [#397 state:resolved]
yukster authored
3 CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from]
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
4 def self.included(base)
5 base.extend(ClassMethods)
6 end
7
8 module ClassMethods
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
9 # Count operates using three different approaches.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
10 #
11 # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
64092de @fxn Improve documentation coverage and markup
fxn authored
12 # * Count using column: By passing a column name to count, it will return a count of all the rows for the model with supplied column present
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
13 # * Count using options will find the row count matched by the options used.
14 #
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
15 # The third approach, count using options, accepts an option hash as the only parameter. The options are:
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
16 #
6ef3546 @lifo Merge docrails
lifo authored
17 # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
9661395 Remove references to nonexistent :joins documentation. Closes #10498 [tp...
Marcel Molina authored
18 # * <tt>:joins</tt>: Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
64092de @fxn Improve documentation coverage and markup
fxn authored
19 # or named associations in the same form used for the <tt>:include</tt> option, which will perform an INNER JOIN on the associated table(s).
7143d80 Smattering of grammatical fixes to documentation. Closes #10083 [BobSilv...
Marcel Molina authored
20 # If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
64092de @fxn Improve documentation coverage and markup
fxn authored
21 # Pass <tt>:readonly => false</tt> to override.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
22 # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
7143d80 Smattering of grammatical fixes to documentation. Closes #10083 [BobSilv...
Marcel Molina authored
23 # to already defined associations. When using named associations, count returns the number of DISTINCT items for the model you're counting.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
24 # See eager loading under Associations.
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
25 # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
26 # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
7143d80 Smattering of grammatical fixes to documentation. Closes #10083 [BobSilv...
Marcel Molina authored
27 # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you, for example, want to do a join but not
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
28 # include the joined columns.
29 # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
ddab9d7 @yukster Add :from option to calculations. [#397 state:resolved]
yukster authored
30 # * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
31 # of a database view).
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
32 #
33 # Examples for counting all:
34 # Person.count # returns the total count of all people
35 #
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
36 # Examples for counting by column:
37 # Person.count(:age) # returns the total count of all people whose age is present in database
38 #
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
39 # Examples for count with options:
40 # Person.count(:conditions => "age > 26")
41 # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
42 # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
43 # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
44 # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
45 #
64092de @fxn Improve documentation coverage and markup
fxn authored
46 # Note: <tt>Person.count(:all)</tt> will not work because it will use <tt>:all</tt> as the condition. Use Person.count instead.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
47 def count(*args)
872c5f4 @technoweenie Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 ...
technoweenie authored
48 calculate(:count, *construct_count_options_from_args(*args))
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
49 end
50
39e1ac6 @lifo Merge docrails
lifo authored
51 # Calculates the average value on a given column. The value is returned as
52 # a float, or +nil+ if there's no row. See +calculate+ for examples with
53 # options.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
54 #
39e1ac6 @lifo Merge docrails
lifo authored
55 # Person.average('age') # => 35.8
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
56 def average(column_name, options = {})
57 calculate(:avg, column_name, options)
58 end
59
39e1ac6 @lifo Merge docrails
lifo authored
60 # Calculates the minimum value on a given column. The value is returned
61 # with the same data type of the column, or +nil+ if there's no row. See
62 # +calculate+ for examples with options.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
63 #
39e1ac6 @lifo Merge docrails
lifo authored
64 # Person.minimum('age') # => 7
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
65 def minimum(column_name, options = {})
66 calculate(:min, column_name, options)
67 end
68
39e1ac6 @lifo Merge docrails
lifo authored
69 # Calculates the maximum value on a given column. The value is returned
70 # with the same data type of the column, or +nil+ if there's no row. See
71 # +calculate+ for examples with options.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
72 #
39e1ac6 @lifo Merge docrails
lifo authored
73 # Person.maximum('age') # => 93
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
74 def maximum(column_name, options = {})
75 calculate(:max, column_name, options)
76 end
77
39e1ac6 @lifo Merge docrails
lifo authored
78 # Calculates the sum of values on a given column. The value is returned
79 # with the same data type of the column, 0 if there's no row. See
80 # +calculate+ for examples with options.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
81 #
39e1ac6 @lifo Merge docrails
lifo authored
82 # Person.sum('age') # => 4562
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
83 def sum(column_name, options = {})
4210d85 Ensure Associations#sum returns 0 when no rows are returned. [#295 state...
Jonathan Viney authored
84 calculate(:sum, column_name, options)
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
85 end
86
7143d80 Smattering of grammatical fixes to documentation. Closes #10083 [BobSilv...
Marcel Molina authored
87 # This calculates aggregate values in the given column. Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
64092de @fxn Improve documentation coverage and markup
fxn authored
88 # Options such as <tt>:conditions</tt>, <tt>:order</tt>, <tt>:group</tt>, <tt>:having</tt>, and <tt>:joins</tt> can be passed to customize the query.
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
89 #
90 # There are two basic forms of output:
91 # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
64092de @fxn Improve documentation coverage and markup
fxn authored
92 # * Grouped values: This returns an ordered hash of the values and groups them by the <tt>:group</tt> option. It takes either a column name, or the name
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
93 # of a belongs_to association.
94 #
95 # values = Person.maximum(:age, :group => 'last_name')
96 # puts values["Drake"]
97 # => 43
98 #
99 # drake = Family.find_by_last_name('Drake')
100 # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
101 # puts values[drake]
102 # => 43
103 #
104 # values.each do |family, max_age|
105 # ...
106 # end
107 #
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
108 # Options:
6ef3546 @lifo Merge docrails
lifo authored
109 # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro to ActiveRecord::Base.
ee6b607 Document how the :include option can be used in Calculations::calculate....
Marcel Molina authored
110 # * <tt>:include</tt>: Eager loading, see Associations for details. Since calculations don't load anything, the purpose of this is to access fields on joined tables in your conditions, order, or group clauses.
9450262 Standardize on using hyphens rather than colons to separate option names...
Marcel Molina authored
111 # * <tt>:joins</tt> - An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
112 # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
9450262 Standardize on using hyphens rather than colons to separate option names...
Marcel Molina authored
113 # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
114 # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
115 # * <tt>:select</tt> - By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
116 # include the joined columns.
9450262 Standardize on using hyphens rather than colons to separate option names...
Marcel Molina authored
117 # * <tt>:distinct</tt> - Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
118 #
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
119 # Examples:
120 # Person.calculate(:count, :all) # The same as Person.count
121 # Person.average(:age) # SELECT AVG(age) FROM people...
122 # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
123 # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
9e45586 @dhh Ensure that you can still do expressions in calculations (closes #11355)...
dhh authored
124 # Person.sum("2 * age")
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
125 def calculate(operation, column_name, options = {})
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
126 validate_calculation_options(operation, options)
127 column_name = options[:select] if options[:select]
ad9f678 @dhh Compatibility patches for calculations
dhh authored
128 column_name = '*' if column_name == :all
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
129 column = column_for column_name
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
130 catch :invalid_query do
131 if options[:group]
132 return execute_grouped_calculation(operation, column_name, column, options)
133 else
134 return execute_simple_calculation(operation, column_name, column, options)
135 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
136 end
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
137 0
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
138 end
139
140 protected
872c5f4 @technoweenie Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 ...
technoweenie authored
141 def construct_count_options_from_args(*args)
df7b746 @technoweenie Fix the HasManyAssociation#count method so it uses the new ActiveRecord:...
technoweenie authored
142 options = {}
143 column_name = :all
34c3162 @NZKoz Revert "Ensure calculations respect scoped :select". Broke .count on a ...
NZKoz authored
144
a0bf019 @jeremy Deprecation: count class method should be called with an options hash ra...
jeremy authored
145 # We need to handle
146 # count()
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
147 # count(:column_name=:all)
a0bf019 @jeremy Deprecation: count class method should be called with an options hash ra...
jeremy authored
148 # count(options={})
149 # count(column_name=:all, options={})
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
150 case args.size
151 when 1
34c3162 @NZKoz Revert "Ensure calculations respect scoped :select". Broke .count on a ...
NZKoz authored
152 args[0].is_a?(Hash) ? options = args[0] : column_name = args[0]
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
153 when 2
872c5f4 @technoweenie Remove deprecated count(conditions=nil, joins=nil) usage. Closes #8993 ...
technoweenie authored
154 column_name, options = args
155 else
a413056 @technoweenie Fix and properly document/test count(column_name) usage. Closes #8999 [l...
technoweenie authored
156 raise ArgumentError, "Unexpected parameters passed to count(): #{args.inspect}"
34c3162 @NZKoz Revert "Ensure calculations respect scoped :select". Broke .count on a ...
NZKoz authored
157 end if args.size > 0
158
159 [column_name, options]
df7b746 @technoweenie Fix the HasManyAssociation#count method so it uses the new ActiveRecord:...
technoweenie authored
160 end
a0bf019 @jeremy Deprecation: count class method should be called with an options hash ra...
jeremy authored
161
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
162 def construct_calculation_sql(operation, column_name, options) #:nodoc:
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
163 operation = operation.to_s.downcase
164 options = options.symbolize_keys
165
37adea6 @dhh Address shortcomings of changeset [8054] [protocool]
dhh authored
166 scope = scope(:find)
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
167 merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
168 aggregate_alias = column_alias_for(operation, column_name)
9e45586 @dhh Ensure that you can still do expressions in calculations (closes #11355)...
dhh authored
169 column_name = "#{connection.quote_table_name(table_name)}.#{column_name}" if column_names.include?(column_name.to_s)
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
170
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
171 if operation == 'count'
172 if merged_includes.any?
173 options[:distinct] = true
8f74527 @NZKoz Escape table names during calculation queries. [wesley.moxam, Koz] Close...
NZKoz authored
174 column_name = options[:select] || [connection.quote_table_name(table_name), primary_key] * '.'
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
175 end
176
177 if options[:distinct]
178 use_workaround = !connection.supports_count_distinct?
179 end
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
180 end
181
50538fb @NZKoz Don't double include DISTINCT when the user has already specified it. C...
NZKoz authored
182 if options[:distinct] && column_name.to_s !~ /\s*DISTINCT\s+/i
183 distinct = 'DISTINCT '
184 end
185 sql = "SELECT #{operation}(#{distinct}#{column_name}) AS #{aggregate_alias}"
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
186
187 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
188 sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
189
1681ede @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
190 options[:group_fields].each_index{|i| sql << ", #{options[:group_fields][i]} AS #{options[:group_aliases][i]}" } if options[:group]
ddab9d7 @yukster Add :from option to calculations. [#397 state:resolved]
yukster authored
191 if options[:from]
192 sql << " FROM #{options[:from]} "
a249cad Fix calculation tests on sqlite2 [#3053 state:resolved]
Jay Pignata authored
193 elsif scope && scope[:from] && !use_workaround
407fbb5 @mrduncan Adding :from scoping to ActiveRecord calculations
mrduncan authored
194 sql << " FROM #{scope[:from]} "
ddab9d7 @yukster Add :from option to calculations. [#397 state:resolved]
yukster authored
195 else
196 sql << " FROM (SELECT #{distinct}#{column_name}" if use_workaround
197 sql << " FROM #{connection.quote_table_name(table_name)} "
198 end
13671cc @tarmo Alias included associations if needed when doing a count
tarmo authored
199
200 joins = ""
db22c89 @pixeltrix Merge scoped :joins together instead of overwriting them. May expose sco...
pixeltrix authored
201 add_joins!(joins, options[:joins], scope)
13671cc @tarmo Alias included associations if needed when doing a count
tarmo authored
202
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
203 if merged_includes.any?
13671cc @tarmo Alias included associations if needed when doing a count
tarmo authored
204 join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
205 sql << join_dependency.join_associations.collect{|join| join.association_join }.join
206 end
13671cc @tarmo Alias included associations if needed when doing a count
tarmo authored
207
208 sql << joins unless joins.blank?
209
fed7d33 @dhh Fixed documentation
dhh authored
210 add_conditions!(sql, options[:conditions], scope)
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
211 add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
0dc53a8 When grouping, use the appropriate option key. [Marcel Molina Jr.]
Marcel Molina authored
212
213 if options[:group]
1681ede @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
214 group_key = connection.adapter_name == 'FrontBase' ? :group_aliases : :group_fields
215 sql << " GROUP BY #{options[group_key].join(',')} "
0dc53a8 When grouping, use the appropriate option key. [Marcel Molina Jr.]
Marcel Molina authored
216 end
b2c0ddf Add support for FrontBase (http://www.frontbase.com/) with a new adapter...
Marcel Molina authored
217
218 if options[:group] && options[:having]
7fb7b48 @willbryant Allow :having conditions to be sanitized like regular :condition. [#2158...
willbryant authored
219 having = sanitize_sql_for_conditions(options[:having])
220
b2c0ddf Add support for FrontBase (http://www.frontbase.com/) with a new adapter...
Marcel Molina authored
221 # FrontBase requires identifiers in the HAVING clause and chokes on function calls
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
222 if connection.adapter_name == 'FrontBase'
7fb7b48 @willbryant Allow :having conditions to be sanitized like regular :condition. [#2158...
willbryant authored
223 having.downcase!
224 having.gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
b2c0ddf Add support for FrontBase (http://www.frontbase.com/) with a new adapter...
Marcel Molina authored
225 end
b6171e7 @jeremy SQLite: count(distinct) queries supported in >= 3.2.6, fix calculations ...
jeremy authored
226
7fb7b48 @willbryant Allow :having conditions to be sanitized like regular :condition. [#2158...
willbryant authored
227 sql << " HAVING #{having} "
b2c0ddf Add support for FrontBase (http://www.frontbase.com/) with a new adapter...
Marcel Molina authored
228 end
229
f106e2c @technoweenie Fix bug where calculations with long alias names return null. [Rick]
technoweenie authored
230 sql << " ORDER BY #{options[:order]} " if options[:order]
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
231 add_limit!(sql, options, scope)
7553a23 @NZKoz Remove AS for oracle compatibility
NZKoz authored
232 sql << ") #{aggregate_alias}_subquery" if use_workaround
9466211 @technoweenie fix calculations for the Oracle Adapter (closes #4626) [Michael Schoen]
technoweenie authored
233 sql
fed7d33 @dhh Fixed documentation
dhh authored
234 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
235
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
236 def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
237 value = connection.select_value(construct_calculation_sql(operation, column_name, options))
fed7d33 @dhh Fixed documentation
dhh authored
238 type_cast_calculated_value(value, column, operation)
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
239 end
240
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
241 def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
1681ede @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
242 group_attr = options[:group]
243 association = reflect_on_association(group_attr.to_s.to_sym)
244 associated = association && association.macro == :belongs_to # only count belongs_to associations
245 group_fields = Array(associated ? association.primary_key_name : group_attr)
246 group_aliases = []
247 group_columns = {}
248
249 group_fields.each do |field|
250 group_aliases << column_alias_for(field)
251 group_columns[column_alias_for(field)] = column_for(field)
252 end
253
254 sql = construct_calculation_sql(operation, column_name, options.merge(:group_fields => group_fields, :group_aliases => group_aliases))
fed7d33 @dhh Fixed documentation
dhh authored
255 calculated_data = connection.select_all(sql)
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
256 aggregate_alias = column_alias_for(operation, column_name)
fed7d33 @dhh Fixed documentation
dhh authored
257
258 if association
1681ede @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
259 key_ids = calculated_data.collect { |row| row[group_aliases.first] }
fed7d33 @dhh Fixed documentation
dhh authored
260 key_records = association.klass.base_class.find(key_ids)
261 key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
262 end
263
325cb12 @technoweenie Namespaced OrderedHash so the Rails implementation does not clash with a...
technoweenie authored
264 calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
1681ede @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
265 key = group_aliases.map{|group_alias| type_cast_calculated_value(row[group_alias], group_columns[group_alias])}
266 key = key.first if key.size == 1
b05fc40 @jeremy Calculations support non-numeric foreign keys. Closes #8154.
jeremy authored
267 key = key_records[key] if associated
fed7d33 @dhh Fixed documentation
dhh authored
268 value = row[aggregate_alias]
57c613a @jeremy Ruby 1.9 compat: calculations don't assume array implementation of order...
jeremy authored
269 all[key] = type_cast_calculated_value(value, column, operation)
270 all
fed7d33 @dhh Fixed documentation
dhh authored
271 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
272 end
273
274 private
fed7d33 @dhh Fixed documentation
dhh authored
275 def validate_calculation_options(operation, options = {})
4251662 @technoweenie Allow all calculations to take the :include option, not just COUNT (clos...
technoweenie authored
276 options.assert_valid_keys(CALCULATIONS_OPTIONS)
fed7d33 @dhh Fixed documentation
dhh authored
277 end
ad9f678 @dhh Compatibility patches for calculations
dhh authored
278
98dc582 @lifo Merge docrails.
lifo authored
279 # Converts the given keys to the value that the database adapter returns as
280 # a usable column name:
281 #
282 # column_alias_for("users.id") # => "users_id"
283 # column_alias_for("sum(id)") # => "sum_id"
284 # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
285 # column_alias_for("count(*)") # => "count_all"
286 # column_alias_for("count", "id") # => "count_id"
fed7d33 @dhh Fixed documentation
dhh authored
287 def column_alias_for(*keys)
8756dd7 @josh Performance: reduce garbage created by ActiveRecord::Calculations#column...
josh authored
288 table_name = keys.join(' ')
289 table_name.downcase!
290 table_name.gsub!(/\*/, 'all')
291 table_name.gsub!(/\W+/, ' ')
292 table_name.strip!
293 table_name.gsub!(/ +/, '_')
294
295 connection.table_alias_for(table_name)
fed7d33 @dhh Fixed documentation
dhh authored
296 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
297
fed7d33 @dhh Fixed documentation
dhh authored
298 def column_for(field)
299 field_name = field.to_s.split('.').last
300 columns.detect { |c| c.name.to_s == field_name }
301 end
a8d085a @dhh Make calculations typecasty! (closes #4016) [Rick Olson]
dhh authored
302
fed7d33 @dhh Fixed documentation
dhh authored
303 def type_cast_calculated_value(value, column, operation = nil)
6dbc75f @brianmario Allow pre-casted values (other than nil) to pass through from calculatio...
brianmario authored
304 if value.is_a?(String) || value.nil?
305 case operation.to_s.downcase
bd75a72 @lifo Ensure AR#sum result is typecasted properly
lifo authored
306 when 'count' then value.to_i
9599948 @lifo Ensure Model.sum and Model.avg typecast appropriately. [#1066 state:reso...
lifo authored
307 when 'sum' then type_cast_using_column(value || '0', column)
ad73a3a @spastorino type_cast_calculated_value refactor: value is never a Fixnum here. Fix ...
spastorino authored
308 when 'avg' then value.try(:to_d)
9599948 @lifo Ensure Model.sum and Model.avg typecast appropriately. [#1066 state:reso...
lifo authored
309 else type_cast_using_column(value, column)
6dbc75f @brianmario Allow pre-casted values (other than nil) to pass through from calculatio...
brianmario authored
310 end
311 else
312 value
fed7d33 @dhh Fixed documentation
dhh authored
313 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
314 end
9599948 @lifo Ensure Model.sum and Model.avg typecast appropriately. [#1066 state:reso...
lifo authored
315
316 def type_cast_using_column(value, column)
317 column ? column.type_cast(value) : value
318 end
99307b9 @dhh Added calculations: Base.count, Base.average, Base.sum, Base.minimum, Ba...
dhh authored
319 end
320 end
445cb5c @NZKoz Add support for :include to with_scope [andrew@redlinesoftware.com]
NZKoz authored
321 end
Something went wrong with that request. Please try again.