Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 395 lines (342 sloc) 13.586 kb
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
1 module ActiveRecord
9465b84 @lifo Rename CalculationMethods to Calculations and get rid of the old Calc…
lifo authored
2 module Calculations
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
3 # Count the records.
4 #
5 # Person.count
6 # # => the total count of all people
7 #
8 # Person.count(:age)
9 # # => returns the total count of all people whose age is present in database
10 #
11 # Person.count(:all)
12 # # => performs a COUNT(*) (:all is an alias for '*')
13 #
14 # Person.count(:age, distinct: true)
15 # # => counts the number of different age values
a8c2981 @mdesantis Add ActiveRecord.count documentation when used on group relations
mdesantis authored
16 #
2008fe6 @vijaydev copy edits [ci skip]
vijaydev authored
17 # If +count+ is used with +group+, it returns a Hash whose keys represent the aggregated column,
a8c2981 @mdesantis Add ActiveRecord.count documentation when used on group relations
mdesantis authored
18 # and the values are the respective amounts:
19 #
20 # Person.group(:city).count
21 # # => { 'Rome' => 5, 'Paris' => 3 }
73b179e @lifo Delegate count to Relation
lifo authored
22 def count(column_name = nil, options = {})
76a6bfd @carlosantoniodasilva Revert "Yield only one argument instead of splatting."
carlosantoniodasilva authored
23 column_name, options = nil, column_name if column_name.is_a?(Hash)
24 calculate(:count, column_name, options)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
25 end
26
7321a3a @fxn revises the rdoc of #average according to 5f3bd55, and realigns when …
fxn authored
27 # Calculates the average value on a given column. Returns +nil+ if there's
28 # no row. See +calculate+ for examples with options.
4148c68 @lifo Delegate :average, :minimum, :maximum, :sum to Relation
lifo authored
29 #
30 # Person.average('age') # => 35.8
31 def average(column_name, options = {})
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
32 calculate(:average, column_name, options)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
33 end
34
0034b78 @smartinez87 Remove extra white spaces on ActiveRecord docs.
smartinez87 authored
35 # Calculates the minimum value on a given column. The value is returned
4148c68 @lifo Delegate :average, :minimum, :maximum, :sum to Relation
lifo authored
36 # with the same data type of the column, or +nil+ if there's no row. See
37 # +calculate+ for examples with options.
38 #
39 # Person.minimum('age') # => 7
40 def minimum(column_name, options = {})
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
41 calculate(:minimum, column_name, options)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
42 end
43
4148c68 @lifo Delegate :average, :minimum, :maximum, :sum to Relation
lifo authored
44 # Calculates the maximum value on a given column. The value is returned
45 # with the same data type of the column, or +nil+ if there's no row. See
46 # +calculate+ for examples with options.
47 #
48 # Person.maximum('age') # => 93
49 def maximum(column_name, options = {})
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
50 calculate(:maximum, column_name, options)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
51 end
52
4148c68 @lifo Delegate :average, :minimum, :maximum, :sum to Relation
lifo authored
53 # Calculates the sum of values on a given column. The value is returned
54 # with the same data type of the column, 0 if there's no row. See
55 # +calculate+ for examples with options.
56 #
57 # Person.sum('age') # => 4562
e7bec4e @bogdan Fixed AR::Relation#sum compatibility with Array#sum
bogdan authored
58 def sum(*args)
59 if block_given?
ad9983f @carlosantoniodasilva Deprecate Relation#sum with a block.
carlosantoniodasilva authored
60 ActiveSupport::Deprecation.warn(
61 "Calling #sum with a block is deprecated and will be removed in Rails 4.1. " \
62 "If you want to perform sum calculation over the array of elements, use `to_a.sum(&block)`."
63 )
76a6bfd @carlosantoniodasilva Revert "Yield only one argument instead of splatting."
carlosantoniodasilva authored
64 self.to_a.sum(*args) {|*block_args| yield(*block_args)}
e7bec4e @bogdan Fixed AR::Relation#sum compatibility with Array#sum
bogdan authored
65 else
66 calculate(:sum, *args)
67 end
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
68 end
69
0034b78 @smartinez87 Remove extra white spaces on ActiveRecord docs.
smartinez87 authored
70 # This calculates aggregate values in the given column. Methods for count, sum, average,
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
71 # minimum, and maximum have been added as shortcuts.
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
72 #
73 # There are two basic forms of output:
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
74 #
b451de0 @spastorino Deletes trailing whitespaces (over text files only find * -type f -ex…
spastorino authored
75 # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float
1ce40ca @neerajdotname ensuring that description does not exceed 100 columns
neerajdotname authored
76 # for AVG, and the given column's type for everything else.
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
77 #
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
78 # * Grouped values: This returns an ordered hash of the values and groups them. It
79 # takes either a column name, or the name of a belongs_to association.
80 #
81 # values = Person.group('last_name').maximum(:age)
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
82 # puts values["Drake"]
4940d6d @alvaropereyra Cleans and removes useless 'Examples' tag [ci skip]
alvaropereyra authored
83 # # => 43
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
84 #
8aebe30 @guilleiguaran Revert "Merge pull request #8989 from robertomiranda/use-rails-4-find…
guilleiguaran authored
85 # drake = Family.find_by_last_name('Drake')
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
86 # values = Person.group(:family).maximum(:age) # Person belongs_to :family
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
87 # puts values[drake]
4940d6d @alvaropereyra Cleans and removes useless 'Examples' tag [ci skip]
alvaropereyra authored
88 # # => 43
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
89 #
90 # values.each do |family, max_age|
91 # ...
92 # end
93 #
94 # Person.calculate(:count, :all) # The same as Person.count
95 # Person.average(:age) # SELECT AVG(age) FROM people...
1ce40ca @neerajdotname ensuring that description does not exceed 100 columns
neerajdotname authored
96 #
97 # # Selects the minimum age for any family without any minors
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
98 # Person.group(:last_name).having("min(age) > 17").minimum(:age)
1ce40ca @neerajdotname ensuring that description does not exceed 100 columns
neerajdotname authored
99 #
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
100 # Person.sum("2 * age")
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
101 def calculate(operation, column_name, options = {})
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
102 relation = with_default_scope
87d6865 @jonleighton Apply the default scope earlier when doing calculations. Fixes #1682.
jonleighton authored
103
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
104 if relation.equal?(self)
60ffe41 @rafaelfranca Extract conditional to a method to avoid duplication
rafaelfranca authored
105 if has_include?(column_name)
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
106 construct_relation_for_association_calculations.calculate(operation, column_name, options)
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
107 else
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
108 perform_calculation(operation, column_name, options)
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
109 end
5aea01a @jonleighton extract deprecated #calculate code
jonleighton authored
110 else
111 relation.calculate(operation, column_name, options)
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
112 end
113 rescue ThrowResult
114 0
115 end
116
daab9bb Fix Calculations#pluck doc to mention several attributes can be selec…
Florent Guilleux authored
117 # Use <tt>pluck</tt> as a shortcut to select one or more attributes without
118 # loading a bunch of records just to grab the attributes you want.
ea50d53 @jeremy Expand and improve #pluck docs
jeremy authored
119 #
120 # Person.pluck(:name)
121 #
122 # instead of
123 #
124 # Person.all.map(&:name)
125 #
126 # Pluck returns an <tt>Array</tt> of attribute values type-casted to match
9685019 @vijaydev copy edits [ci skip]
vijaydev authored
127 # the plucked column names, if they can be deduced. Plucking an SQL fragment
ea50d53 @jeremy Expand and improve #pluck docs
jeremy authored
128 # returns String values by default.
a382d60 @bogdan ActiveRecord::Relation#pluck method
bogdan authored
129 #
ea50d53 @jeremy Expand and improve #pluck docs
jeremy authored
130 # Person.pluck(:id)
131 # # SELECT people.id FROM people
132 # # => [1, 2, 3]
133 #
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
134 # Person.pluck(:id, :name)
135 # # SELECT people.id, people.name FROM people
e5cd300 @carlosantoniodasilva Add changelog entry and guide updates for pluck with multiple columns
carlosantoniodasilva authored
136 # # => [[1, 'David'], [2, 'Jeremy'], [3, 'Jose']]
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
137 #
ea50d53 @jeremy Expand and improve #pluck docs
jeremy authored
138 # Person.uniq.pluck(:role)
139 # # SELECT DISTINCT role FROM people
140 # # => ['admin', 'member', 'guest']
141 #
890da51 @AvnerCohen 1.9 Syntax related changes
AvnerCohen authored
142 # Person.where(age: 21).limit(5).pluck(:id)
ea50d53 @jeremy Expand and improve #pluck docs
jeremy authored
143 # # SELECT people.id FROM people WHERE people.age = 21 LIMIT 5
144 # # => [2, 3]
145 #
146 # Person.pluck('DATEDIFF(updated_at, created_at)')
147 # # SELECT DATEDIFF(updated_at, created_at) FROM people
148 # # => ['0', '27761', '173']
a382d60 @bogdan ActiveRecord::Relation#pluck method
bogdan authored
149 #
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
150 def pluck(*column_names)
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
151 column_names.map! do |column_name|
152 if column_name.is_a?(Symbol) && self.column_names.include?(column_name.to_s)
c7d752f @elliterate Fix pluck when columns/tables are reserved words.
elliterate authored
153 "#{connection.quote_table_name(table_name)}.#{connection.quote_column_name(column_name)}"
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
154 else
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
155 column_name
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
156 end
a379cb2 @bogdan AR::Relation#pluck: improve to work with joins
bogdan authored
157 end
e0eef11 @tenderlove typecast columns based on the returned types
tenderlove authored
158
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
159 if has_include?(column_names.first)
160 construct_relation_for_association_calculations.pluck(*column_names)
60ffe41 @rafaelfranca Extract conditional to a method to avoid duplication
rafaelfranca authored
161 else
663d9ef @senny `#pluck` can be used on a relation with `select` clause.
senny authored
162 relation = spawn
163 relation.select_values = column_names
164 result = klass.connection.select_all(relation.arel, nil, bind_values)
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
165 columns = result.columns.map do |key|
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
166 klass.column_types.fetch(key) {
167 result.column_types.fetch(key) {
168 Class.new { def type_cast(v); v; end }.new
169 }
60ffe41 @rafaelfranca Extract conditional to a method to avoid duplication
rafaelfranca authored
170 }
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
171 end
e0eef11 @tenderlove typecast columns based on the returned types
tenderlove authored
172
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
173 result = result.map do |attributes|
174 values = klass.initialize_attributes(attributes).values
2a38fd5 @tenderlove MySQL returns "SUM(DISTINCT(credit_limit))" as the column name unless
tenderlove authored
175
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
176 columns.zip(values).map do |column, value|
177 column.type_cast(value)
2e379c1 ActiveRecord#pluck now accepts multiple columns
jeroeningen authored
178 end
60ffe41 @rafaelfranca Extract conditional to a method to avoid duplication
rafaelfranca authored
179 end
6aae17e @carlosantoniodasilva Refactor pluck with multiple columns
carlosantoniodasilva authored
180 columns.one? ? result.map!(&:first) : result
a382d60 @bogdan ActiveRecord::Relation#pluck method
bogdan authored
181 end
182 end
183
9307616 Add ActiveRecord::Base#ids
twinturbo authored
184 # Pluck all the ID's for the relation using the table's primary key
185 #
186 # Person.ids # SELECT people.id FROM people
60aee4e @benpickles Lowercase.
benpickles authored
187 # Person.joins(:companies).ids # SELECT people.id FROM people INNER JOIN companies ON companies.person_id = people.id
9307616 Add ActiveRecord::Base#ids
twinturbo authored
188 def ids
189 pluck primary_key
190 end
191
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
192 private
193
60ffe41 @rafaelfranca Extract conditional to a method to avoid duplication
rafaelfranca authored
194 def has_include?(column_name)
195 eager_loading? || (includes_values.present? && (column_name || references_eager_loaded_tables?))
196 end
197
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
198 def perform_calculation(operation, column_name, options = {})
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
199 operation = operation.to_s.downcase
200
648def4 @senny `#count` in conjunction with `#uniq` performs distinct count.
senny authored
201 # If #count is used in conjuction with #uniq it is considered distinct. (eg. relation.uniq.count)
202 distinct = options[:distinct] || self.uniq_value
e4f424d @tenderlove refactoring to remove duplicate logic
tenderlove authored
203
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
204 if operation == "count"
8f63dcb @lifo Move the only remaining calculation method calculate() to Relation
lifo authored
205 column_name ||= (select_for_count || :all)
206
7408b6e @tenderlove just grep the AST for OuterJoin nodes rather than converting the tree…
tenderlove authored
207 unless arel.ast.grep(Arel::Nodes::OuterJoin).empty?
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
208 distinct = true
209 end
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
210
1db49ce Bug fixes:
Fadzril Muhamad & Joseph Palermo authored
211 column_name = primary_key if column_name == :all && distinct
212
e4f424d @tenderlove refactoring to remove duplicate logic
tenderlove authored
213 distinct = nil if column_name =~ /\s*DISTINCT\s+/i
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
214 end
215
6311975 @jonleighton use a hash to store relation values
jonleighton authored
216 if group_values.any?
1c9022d @marklazz Honor distinct option when used with count operation after group clau…
marklazz authored
217 execute_grouped_calculation(operation, column_name, distinct)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
218 else
632120d @neerajdotname return is not needed here
neerajdotname authored
219 execute_simple_calculation(operation, column_name, distinct)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
220 end
73b179e @lifo Delegate count to Relation
lifo authored
221 end
222
28c73f0 @jmileham Use Arel to build subquery. Adapt tests to changed fixtures.
jmileham authored
223 def aggregate_column(column_name)
a8a62f8 @tenderlove [#5441 state:resolved] refactoring code to determine aggregate column
tenderlove authored
224 if @klass.column_names.include?(column_name.to_s)
28c73f0 @jmileham Use Arel to build subquery. Adapt tests to changed fixtures.
jmileham authored
225 Arel::Attribute.new(@klass.unscoped.table, column_name)
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
226 else
a8a62f8 @tenderlove [#5441 state:resolved] refactoring code to determine aggregate column
tenderlove authored
227 Arel.sql(column_name == :all ? "*" : column_name.to_s)
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
228 end
a8a62f8 @tenderlove [#5441 state:resolved] refactoring code to determine aggregate column
tenderlove authored
229 end
230
1c9022d @marklazz Honor distinct option when used with count operation after group clau…
marklazz authored
231 def operation_over_aggregate_column(column, operation, distinct)
232 operation == 'count' ? column.count(distinct) : column.send(operation)
233 end
234
a8a62f8 @tenderlove [#5441 state:resolved] refactoring code to determine aggregate column
tenderlove authored
235 def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
74e3539 @lifo Ignore order for simple calculations to make postgresql happy
lifo authored
236 # Postgresql doesn't like ORDER BY when there are no GROUP BY
87d6865 @jonleighton Apply the default scope earlier when doing calculations. Fixes #1682.
jonleighton authored
237 relation = reorder(nil)
e6ca7e7 @tenderlove refactoring to remove crazy logic
tenderlove authored
238
8faf2b3 @tenderlove mysql does not return alias names, so fall back
tenderlove authored
239 column_alias = column_name
240
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
241 if operation == "count" && (relation.limit_value || relation.offset_value)
242 # Shortcut when limit is zero.
243 return 0 if relation.limit_value == 0
e6ca7e7 @tenderlove refactoring to remove crazy logic
tenderlove authored
244
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
245 query_builder = build_count_subquery(relation, column_name, distinct)
246 else
247 column = aggregate_column(column_name)
54a2bf6 @tenderlove removing limits and offsets from COUNT queries unless both are specif…
tenderlove authored
248
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
249 select_value = operation_over_aggregate_column(column, operation, distinct)
54a2bf6 @tenderlove removing limits and offsets from COUNT queries unless both are specif…
tenderlove authored
250
8faf2b3 @tenderlove mysql does not return alias names, so fall back
tenderlove authored
251 column_alias = select_value.alias
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
252 relation.select_values = [select_value]
253
254 query_builder = relation.arel
54a2bf6 @tenderlove removing limits and offsets from COUNT queries unless both are specif…
tenderlove authored
255 end
256
54a6518 @tenderlove fix PG typecasting errors
tenderlove authored
257 result = @klass.connection.select_all(query_builder, nil, relation.bind_values)
8faf2b3 @tenderlove mysql does not return alias names, so fall back
tenderlove authored
258 row = result.first
259 value = row && row.values.first
260 column = result.column_types.fetch(column_alias) do
261 column_for(column_name)
262 end
263
264 type_cast_calculated_value(value, column, operation)
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
265 end
266
1c9022d @marklazz Honor distinct option when used with count operation after group clau…
marklazz authored
267 def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
a1c05dd @ernie Stop assuming strings for grouped calculations
ernie authored
268 group_attrs = group_values
269
270 if group_attrs.first.respond_to?(:to_sym)
e5abb89 @carlosantoniodasilva Ensure Arel columns are typecasted properly when grouping with calcul…
carlosantoniodasilva authored
271 association = @klass.reflect_on_association(group_attrs.first.to_sym)
272 associated = group_attrs.size == 1 && association && association.macro == :belongs_to # only count belongs_to associations
273 group_fields = Array(associated ? association.foreign_key : group_attrs)
a1c05dd @ernie Stop assuming strings for grouped calculations
ernie authored
274 else
275 group_fields = group_attrs
276 end
277
d5a8bdb @tenderlove create fewer relation objects
tenderlove authored
278 group_aliases = group_fields.map { |field|
279 column_alias_for(field)
280 }
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
281 group_columns = group_aliases.zip(group_fields).map { |aliaz,field|
160af90 @tenderlove fix time typcasting on group counts in PG
tenderlove authored
282 [aliaz, field]
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
283 }
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
284
ca71bb8 @tenderlove stop hardcoding FrontBase adapter conditionals
tenderlove authored
285 group = group_fields
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
286
a8a62f8 @tenderlove [#5441 state:resolved] refactoring code to determine aggregate column
tenderlove authored
287 if operation == 'count' && column_name == :all
288 aggregate_alias = 'count_all'
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
289 else
d5a8bdb @tenderlove create fewer relation objects
tenderlove authored
290 aggregate_alias = column_alias_for([operation, column_name].join(' '))
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
291 end
292
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
293 select_values = [
294 operation_over_aggregate_column(
295 aggregate_column(column_name),
296 operation,
297 distinct).as(aggregate_alias)
298 ]
6311975 @jonleighton use a hash to store relation values
jonleighton authored
299 select_values += select_values unless having_values.empty?
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
300
301 select_values.concat group_fields.zip(group_aliases).map { |field,aliaz|
a1c05dd @ernie Stop assuming strings for grouped calculations
ernie authored
302 if field.respond_to?(:as)
303 field.as(aliaz)
304 else
305 "#{field} AS #{aliaz}"
306 end
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
307 }
308
d5a8bdb @tenderlove create fewer relation objects
tenderlove authored
309 relation = except(:group)
310 relation.group_values = group
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
311 relation.select_values = select_values
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
312
4bc2ae0 @tenderlove use bind values for join columns
tenderlove authored
313 calculated_data = @klass.connection.select_all(relation, nil, bind_values)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
314
315 if association
a5cdf0b @acatighera Fix ActiveRecord calculations when grouped by multiple fields
acatighera authored
316 key_ids = calculated_data.collect { |row| row[group_aliases.first] }
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
317 key_records = association.klass.base_class.find(key_ids)
bf2223d @tenderlove removing an inject + merge in favor of Hash#[]
tenderlove authored
318 key_records = Hash[key_records.map { |r| [r.id, r] }]
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
319 end
320
fa46468 @raghunadhd Refactored code
raghunadhd authored
321 Hash[calculated_data.map do |row|
92ab7ac @arunagw warning removed: shadowing outer local variable
arunagw authored
322 key = group_columns.map { |aliaz, col_name|
160af90 @tenderlove fix time typcasting on group counts in PG
tenderlove authored
323 column = calculated_data.column_types.fetch(aliaz) do
92ab7ac @arunagw warning removed: shadowing outer local variable
arunagw authored
324 column_for(col_name)
160af90 @tenderlove fix time typcasting on group counts in PG
tenderlove authored
325 end
7ebd36d @tenderlove refactor to reduce method calls
tenderlove authored
326 type_cast_calculated_value(row[aliaz], column)
327 }
e5abb89 @carlosantoniodasilva Ensure Arel columns are typecasted properly when grouping with calcul…
carlosantoniodasilva authored
328 key = key.first if key.size == 1
4513cc1 @miloops Goodbye inject, hello map.
miloops authored
329 key = key_records[key] if associated
330 [key, type_cast_calculated_value(row[aggregate_alias], column_for(column_name), operation)]
331 end]
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
332 end
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
333
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
334 # Converts the given keys to the value that the database adapter returns as
335 # a usable column name:
336 #
337 # column_alias_for("users.id") # => "users_id"
338 # column_alias_for("sum(id)") # => "sum_id"
339 # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
340 # column_alias_for("count(*)") # => "count_all"
341 # column_alias_for("count", "id") # => "count_id"
f77beac @tenderlove stop passing *args to generate aliases
tenderlove authored
342 def column_alias_for(keys)
0803d7a @tenderlove arel columns can be used for grouping so that "weird" column names ar…
tenderlove authored
343 if keys.respond_to? :name
344 keys = "#{keys.relation.name}.#{keys.name}"
345 end
346
f77beac @tenderlove stop passing *args to generate aliases
tenderlove authored
347 table_name = keys.to_s.downcase
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
348 table_name.gsub!(/\*/, 'all')
349 table_name.gsub!(/\W+/, ' ')
350 table_name.strip!
351 table_name.gsub!(/ +/, '_')
352
353 @klass.connection.table_alias_for(table_name)
354 end
355
356 def column_for(field)
e5abb89 @carlosantoniodasilva Ensure Arel columns are typecasted properly when grouping with calcul…
carlosantoniodasilva authored
357 field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
e06c06d @tenderlove use columns hash to look up the column for the count field
tenderlove authored
358 @klass.columns_hash[field_name]
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
359 end
360
361 def type_cast_calculated_value(value, column, operation = nil)
f4f4964 @rsim Always return decimal average of integer fields
rsim authored
362 case operation
363 when 'count' then value.to_i
51d6e21 @refractalize ActiveRecord: sum expression returns string '0' for no records, fixed
refractalize authored
364 when 'sum' then type_cast_using_column(value || 0, column)
95d5d9b @metaskills The type_cast_calculated_value method will trust DB types before cast…
metaskills authored
365 when 'average' then value.respond_to?(:to_d) ? value.to_d : value
f4f4964 @rsim Always return decimal average of integer fields
rsim authored
366 else type_cast_using_column(value, column)
08633ba @lifo Migrate all the calculation methods to Relation
lifo authored
367 end
368 end
369
370 def type_cast_using_column(value, column)
371 column ? column.type_cast(value) : value
372 end
373
73b179e @lifo Delegate count to Relation
lifo authored
374 def select_for_count
6311975 @jonleighton use a hash to store relation values
jonleighton authored
375 if select_values.present?
376 select = select_values.join(", ")
7619bcf @fxn rewrites a couple of alternations in regexps as character classes
fxn authored
377 select if select !~ /[,*]/
73b179e @lifo Delegate count to Relation
lifo authored
378 end
379 end
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
380
381 def build_count_subquery(relation, column_name, distinct)
28c73f0 @jmileham Use Arel to build subquery. Adapt tests to changed fixtures.
jmileham authored
382 column_alias = Arel.sql('count_column')
383 subquery_alias = Arel.sql('subquery_for_count')
384
385 aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
386 relation.select_values = [aliased_column]
387 subquery = relation.arel.as(subquery_alias)
388
389 sm = Arel::SelectManager.new relation.engine
390 select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
391 sm.project(select_value).from(subquery)
d5994ee @jmileham Change behavior of count(:limit => x, :offset => y) to limit/offset b…
jmileham authored
392 end
e8ca22d @lifo Move Relation calculation methods to a separate module
lifo authored
393 end
394 end
Something went wrong with that request. Please try again.