activerecord 5.0.7 → 5.1.7

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (219) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -2080
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record/aggregations.rb +244 -244
  8. data/lib/active_record/association_relation.rb +5 -5
  9. data/lib/active_record/associations/alias_tracker.rb +10 -11
  10. data/lib/active_record/associations/association.rb +23 -5
  11. data/lib/active_record/associations/association_scope.rb +95 -81
  12. data/lib/active_record/associations/belongs_to_association.rb +7 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +30 -16
  14. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  16. data/lib/active_record/associations/collection_association.rb +36 -205
  17. data/lib/active_record/associations/collection_proxy.rb +132 -63
  18. data/lib/active_record/associations/has_many_association.rb +10 -19
  19. data/lib/active_record/associations/has_many_through_association.rb +12 -4
  20. data/lib/active_record/associations/has_one_association.rb +24 -28
  21. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  22. data/lib/active_record/associations/join_dependency/join_association.rb +4 -28
  23. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  24. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +121 -118
  26. data/lib/active_record/associations/preloader/association.rb +64 -64
  27. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  28. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  29. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  30. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  31. data/lib/active_record/associations/preloader/through_association.rb +41 -41
  32. data/lib/active_record/associations/preloader.rb +94 -94
  33. data/lib/active_record/associations/singular_association.rb +8 -25
  34. data/lib/active_record/associations/through_association.rb +2 -5
  35. data/lib/active_record/associations.rb +1591 -1562
  36. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute_assignment.rb +61 -61
  39. data/lib/active_record/attribute_decorators.rb +35 -13
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  41. data/lib/active_record/attribute_methods/dirty.rb +229 -46
  42. data/lib/active_record/attribute_methods/primary_key.rb +74 -73
  43. data/lib/active_record/attribute_methods/read.rb +39 -35
  44. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  46. data/lib/active_record/attribute_methods/write.rb +30 -33
  47. data/lib/active_record/attribute_methods.rb +56 -65
  48. data/lib/active_record/attribute_mutation_tracker.rb +63 -11
  49. data/lib/active_record/attribute_set/builder.rb +27 -33
  50. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  51. data/lib/active_record/attribute_set.rb +9 -6
  52. data/lib/active_record/attributes.rb +22 -22
  53. data/lib/active_record/autosave_association.rb +18 -13
  54. data/lib/active_record/base.rb +24 -22
  55. data/lib/active_record/callbacks.rb +56 -14
  56. data/lib/active_record/coders/yaml_column.rb +9 -11
  57. data/lib/active_record/collection_cache_key.rb +3 -4
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +330 -284
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +39 -37
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -27
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -51
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +10 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +74 -79
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +120 -100
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +49 -43
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -135
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +404 -424
  70. data/lib/active_record/connection_adapters/column.rb +26 -4
  71. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  72. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -28
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +7 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +23 -27
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +32 -53
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +19 -9
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  91. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +0 -10
  92. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +32 -30
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  99. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -35
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +182 -222
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +6 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +7 -5
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +198 -167
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -19
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +32 -0
  116. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +184 -167
  117. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  118. data/lib/active_record/connection_handling.rb +14 -26
  119. data/lib/active_record/core.rb +109 -93
  120. data/lib/active_record/counter_cache.rb +60 -13
  121. data/lib/active_record/define_callbacks.rb +20 -0
  122. data/lib/active_record/dynamic_matchers.rb +80 -79
  123. data/lib/active_record/enum.rb +8 -6
  124. data/lib/active_record/errors.rb +64 -15
  125. data/lib/active_record/explain.rb +1 -2
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +7 -4
  128. data/lib/active_record/fixture_set/file.rb +11 -8
  129. data/lib/active_record/fixtures.rb +66 -53
  130. data/lib/active_record/gem_version.rb +1 -1
  131. data/lib/active_record/inheritance.rb +93 -79
  132. data/lib/active_record/integration.rb +7 -7
  133. data/lib/active_record/internal_metadata.rb +3 -16
  134. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  135. data/lib/active_record/locking/optimistic.rb +69 -74
  136. data/lib/active_record/locking/pessimistic.rb +10 -1
  137. data/lib/active_record/log_subscriber.rb +23 -28
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +100 -47
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/migration.rb +153 -155
  142. data/lib/active_record/model_schema.rb +94 -107
  143. data/lib/active_record/nested_attributes.rb +200 -199
  144. data/lib/active_record/null_relation.rb +11 -34
  145. data/lib/active_record/persistence.rb +65 -50
  146. data/lib/active_record/query_cache.rb +2 -6
  147. data/lib/active_record/querying.rb +3 -4
  148. data/lib/active_record/railtie.rb +16 -17
  149. data/lib/active_record/railties/controller_runtime.rb +6 -2
  150. data/lib/active_record/railties/databases.rake +105 -133
  151. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  152. data/lib/active_record/readonly_attributes.rb +2 -2
  153. data/lib/active_record/reflection.rb +154 -108
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  155. data/lib/active_record/relation/batches.rb +80 -51
  156. data/lib/active_record/relation/calculations.rb +169 -162
  157. data/lib/active_record/relation/delegation.rb +32 -31
  158. data/lib/active_record/relation/finder_methods.rb +197 -231
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  161. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  162. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  163. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  166. data/lib/active_record/relation/predicate_builder.rb +92 -89
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +255 -293
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +80 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/relation.rb +93 -119
  174. data/lib/active_record/result.rb +41 -32
  175. data/lib/active_record/runtime_registry.rb +3 -3
  176. data/lib/active_record/sanitization.rb +176 -192
  177. data/lib/active_record/schema.rb +3 -3
  178. data/lib/active_record/schema_dumper.rb +15 -38
  179. data/lib/active_record/schema_migration.rb +8 -4
  180. data/lib/active_record/scoping/default.rb +90 -90
  181. data/lib/active_record/scoping/named.rb +11 -11
  182. data/lib/active_record/scoping.rb +6 -6
  183. data/lib/active_record/secure_token.rb +2 -2
  184. data/lib/active_record/statement_cache.rb +13 -15
  185. data/lib/active_record/store.rb +31 -32
  186. data/lib/active_record/suppressor.rb +2 -1
  187. data/lib/active_record/table_metadata.rb +9 -5
  188. data/lib/active_record/tasks/database_tasks.rb +65 -55
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +76 -73
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +72 -47
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  192. data/lib/active_record/timestamp.rb +46 -25
  193. data/lib/active_record/touch_later.rb +1 -2
  194. data/lib/active_record/transactions.rb +97 -109
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +13 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/internal/abstract_json.rb +4 -0
  199. data/lib/active_record/type/serialized.rb +14 -8
  200. data/lib/active_record/type/text.rb +9 -0
  201. data/lib/active_record/type/time.rb +0 -1
  202. data/lib/active_record/type/type_map.rb +11 -15
  203. data/lib/active_record/type/unsigned_integer.rb +15 -0
  204. data/lib/active_record/type.rb +17 -13
  205. data/lib/active_record/type_caster/connection.rb +8 -6
  206. data/lib/active_record/type_caster/map.rb +3 -1
  207. data/lib/active_record/type_caster.rb +2 -2
  208. data/lib/active_record/validations/associated.rb +1 -1
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +8 -39
  211. data/lib/active_record/validations.rb +4 -4
  212. data/lib/active_record/version.rb +1 -1
  213. data/lib/active_record.rb +20 -20
  214. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  215. data/lib/rails/generators/active_record/migration.rb +1 -1
  216. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  217. data/lib/rails/generators/active_record.rb +4 -4
  218. metadata +24 -13
  219. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -37,6 +37,7 @@ module ActiveRecord
37
37
  # Note: not all valid {Relation#select}[rdoc-ref:QueryMethods#select] expressions are valid #count expressions. The specifics differ
38
38
  # between databases. In invalid cases, an error from the database is thrown.
39
39
  def count(column_name = nil)
40
+ return super() if block_given?
40
41
  calculate(:count, column_name)
41
42
  end
42
43
 
@@ -71,8 +72,8 @@ module ActiveRecord
71
72
  # #calculate for examples with options.
72
73
  #
73
74
  # Person.sum(:age) # => 4562
74
- def sum(column_name = nil, &block)
75
- return super(&block) if block_given?
75
+ def sum(column_name = nil)
76
+ return super() if block_given?
76
77
  calculate(:sum, column_name)
77
78
  end
78
79
 
@@ -108,13 +109,16 @@ module ActiveRecord
108
109
  # ...
109
110
  # end
110
111
  def calculate(operation, column_name)
111
- if column_name.is_a?(Symbol) && attribute_alias?(column_name)
112
- column_name = attribute_alias(column_name)
113
- end
114
-
115
112
  if has_include?(column_name)
116
113
  relation = construct_relation_for_association_calculations
117
- relation = relation.distinct if operation.to_s.downcase == "count"
114
+
115
+ if operation.to_s.downcase == "count"
116
+ relation.distinct!
117
+ # PostgreSQL: ORDER BY expressions must appear in SELECT list when using DISTINCT
118
+ if (column_name == :all || column_name.nil?) && select_values.empty?
119
+ relation.order_values = []
120
+ end
121
+ end
118
122
 
119
123
  relation.calculate(operation, column_name)
120
124
  else
@@ -184,202 +188,205 @@ module ActiveRecord
184
188
 
185
189
  private
186
190
 
187
- def has_include?(column_name)
188
- eager_loading? || (includes_values.present? && column_name && column_name != :all)
189
- end
191
+ def has_include?(column_name)
192
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
193
+ end
190
194
 
191
- def perform_calculation(operation, column_name)
192
- operation = operation.to_s.downcase
195
+ def perform_calculation(operation, column_name)
196
+ operation = operation.to_s.downcase
193
197
 
194
- # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
195
- # considered distinct.
196
- distinct = self.distinct_value
198
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
199
+ # considered distinct.
200
+ distinct = distinct_value
197
201
 
198
- if operation == "count"
199
- column_name ||= select_for_count
200
- column_name = primary_key if column_name == :all && distinct
201
- distinct = nil if column_name =~ /\s*DISTINCT[\s(]+/i
202
- end
202
+ if operation == "count"
203
+ column_name ||= select_for_count
204
+ if column_name == :all
205
+ if distinct && (group_values.any? || select_values.empty? && order_values.empty?)
206
+ column_name = primary_key
207
+ end
208
+ elsif column_name =~ /\s*DISTINCT[\s(]+/i
209
+ distinct = nil
210
+ end
211
+ end
203
212
 
204
- if group_values.any?
205
- execute_grouped_calculation(operation, column_name, distinct)
206
- else
207
- execute_simple_calculation(operation, column_name, distinct)
213
+ if group_values.any?
214
+ execute_grouped_calculation(operation, column_name, distinct)
215
+ else
216
+ execute_simple_calculation(operation, column_name, distinct)
217
+ end
208
218
  end
209
- end
210
219
 
211
- def aggregate_column(column_name)
212
- return column_name if Arel::Expressions === column_name
220
+ def aggregate_column(column_name)
221
+ return column_name if Arel::Expressions === column_name
213
222
 
214
- if @klass.column_names.include?(column_name.to_s)
215
- Arel::Attribute.new(@klass.unscoped.table, column_name)
216
- else
217
- Arel.sql(column_name == :all ? "*" : column_name.to_s)
223
+ if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s)
224
+ @klass.arel_attribute(column_name)
225
+ else
226
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
227
+ end
218
228
  end
219
- end
220
-
221
- def operation_over_aggregate_column(column, operation, distinct)
222
- operation == 'count' ? column.count(distinct) : column.send(operation)
223
- end
224
-
225
- def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
226
- # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
227
- relation = unscope(:order)
228
229
 
229
- column_alias = column_name
230
-
231
- if operation == "count" && (relation.limit_value || relation.offset_value)
232
- # Shortcut when limit is zero.
233
- return 0 if relation.limit_value == 0
230
+ def operation_over_aggregate_column(column, operation, distinct)
231
+ operation == "count" ? column.count(distinct) : column.send(operation)
232
+ end
234
233
 
235
- query_builder = build_count_subquery(relation, column_name, distinct)
236
- else
237
- column = aggregate_column(column_name)
234
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
235
+ column_alias = column_name
238
236
 
239
- select_value = operation_over_aggregate_column(column, operation, distinct)
237
+ if operation == "count" && (column_name == :all && distinct || has_limit_or_offset?)
238
+ # Shortcut when limit is zero.
239
+ return 0 if limit_value == 0
240
240
 
241
- if operation == "sum" && distinct
242
- select_value.distinct = true
243
- end
241
+ query_builder = build_count_subquery(spawn, column_name, distinct)
242
+ else
243
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
244
+ relation = unscope(:order).distinct!(false)
244
245
 
245
- column_alias = select_value.alias
246
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
247
- relation.select_values = [select_value]
246
+ column = aggregate_column(column_name)
248
247
 
249
- query_builder = relation.arel
250
- end
248
+ select_value = operation_over_aggregate_column(column, operation, distinct)
249
+ if operation == "sum" && distinct
250
+ select_value.distinct = true
251
+ end
251
252
 
252
- result = @klass.connection.select_all(query_builder, nil, bound_attributes)
253
- row = result.first
254
- value = row && row.values.first
255
- column = result.column_types.fetch(column_alias) do
256
- type_for(column_name)
257
- end
253
+ column_alias = select_value.alias
254
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
255
+ relation.select_values = [select_value]
258
256
 
259
- type_cast_calculated_value(value, column, operation)
260
- end
257
+ query_builder = relation.arel
258
+ end
261
259
 
262
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
263
- group_attrs = group_values
260
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
261
+ row = result.first
262
+ value = row && row.values.first
263
+ type = result.column_types.fetch(column_alias) do
264
+ type_for(column_name)
265
+ end
264
266
 
265
- if group_attrs.first.respond_to?(:to_sym)
266
- association = @klass._reflect_on_association(group_attrs.first)
267
- associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
268
- group_fields = Array(associated ? association.foreign_key : group_attrs)
269
- else
270
- group_fields = group_attrs
267
+ type_cast_calculated_value(value, type, operation)
271
268
  end
272
- group_fields = arel_columns(group_fields)
273
269
 
274
- group_aliases = group_fields.map { |field| column_alias_for(field) }
275
- group_columns = group_aliases.zip(group_fields)
276
-
277
- if operation == 'count' && column_name == :all
278
- aggregate_alias = 'count_all'
279
- else
280
- aggregate_alias = column_alias_for([operation, column_name].join(' '))
281
- end
270
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
271
+ group_attrs = group_values
282
272
 
283
- select_values = [
284
- operation_over_aggregate_column(
285
- aggregate_column(column_name),
286
- operation,
287
- distinct).as(aggregate_alias)
288
- ]
289
- select_values += self.select_values unless having_clause.empty?
290
-
291
- select_values.concat group_columns.map { |aliaz, field|
292
- if field.respond_to?(:as)
293
- field.as(aliaz)
273
+ if group_attrs.first.respond_to?(:to_sym)
274
+ association = @klass._reflect_on_association(group_attrs.first)
275
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
276
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
294
277
  else
295
- "#{field} AS #{aliaz}"
278
+ group_fields = group_attrs
296
279
  end
297
- }
298
-
299
- relation = except(:group)
300
- relation.group_values = group_fields
301
- relation.select_values = select_values
280
+ group_fields = arel_columns(group_fields)
302
281
 
303
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
282
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
283
+ group_columns = group_aliases.zip(group_fields)
304
284
 
305
- if association
306
- key_ids = calculated_data.collect { |row| row[group_aliases.first] }
307
- key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
308
- key_records = Hash[key_records.map { |r| [r.id, r] }]
309
- end
285
+ if operation == "count" && column_name == :all
286
+ aggregate_alias = "count_all"
287
+ else
288
+ aggregate_alias = column_alias_for([operation, column_name].join(" "))
289
+ end
310
290
 
311
- Hash[calculated_data.map do |row|
312
- key = group_columns.map { |aliaz, col_name|
313
- column = type_for(col_name) do
314
- calculated_data.column_types.fetch(aliaz) do
315
- Type::Value.new
316
- end
291
+ select_values = [
292
+ operation_over_aggregate_column(
293
+ aggregate_column(column_name),
294
+ operation,
295
+ distinct).as(aggregate_alias)
296
+ ]
297
+ select_values += self.select_values unless having_clause.empty?
298
+
299
+ select_values.concat group_columns.map { |aliaz, field|
300
+ if field.respond_to?(:as)
301
+ field.as(aliaz)
302
+ else
303
+ "#{field} AS #{aliaz}"
317
304
  end
318
- type_cast_calculated_value(row[aliaz], column)
319
305
  }
320
- key = key.first if key.size == 1
321
- key = key_records[key] if associated
322
306
 
323
- column_type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
324
- [key, type_cast_calculated_value(row[aggregate_alias], column_type, operation)]
325
- end]
326
- end
307
+ relation = except(:group).distinct!(false)
308
+ relation.group_values = group_fields
309
+ relation.select_values = select_values
327
310
 
328
- # Converts the given keys to the value that the database adapter returns as
329
- # a usable column name:
330
- #
331
- # column_alias_for("users.id") # => "users_id"
332
- # column_alias_for("sum(id)") # => "sum_id"
333
- # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
334
- # column_alias_for("count(*)") # => "count_all"
335
- def column_alias_for(keys)
336
- if keys.respond_to? :name
337
- keys = "#{keys.relation.name}.#{keys.name}"
311
+ calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
312
+
313
+ if association
314
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
315
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
316
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
317
+ end
318
+
319
+ Hash[calculated_data.map do |row|
320
+ key = group_columns.map { |aliaz, col_name|
321
+ type = type_for(col_name) do
322
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
323
+ end
324
+ type_cast_calculated_value(row[aliaz], type)
325
+ }
326
+ key = key.first if key.size == 1
327
+ key = key_records[key] if associated
328
+
329
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
330
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
331
+ end]
338
332
  end
339
333
 
340
- table_name = keys.to_s.downcase
341
- table_name.gsub!(/\*/, 'all')
342
- table_name.gsub!(/\W+/, ' ')
343
- table_name.strip!
344
- table_name.gsub!(/ +/, '_')
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
+ def column_alias_for(keys)
342
+ if keys.respond_to? :name
343
+ keys = "#{keys.relation.name}.#{keys.name}"
344
+ end
345
345
 
346
- @klass.connection.table_alias_for(table_name)
347
- end
346
+ table_name = keys.to_s.downcase
347
+ table_name.gsub!(/\*/, "all")
348
+ table_name.gsub!(/\W+/, " ")
349
+ table_name.strip!
350
+ table_name.gsub!(/ +/, "_")
348
351
 
349
- def type_for(field, &block)
350
- field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split('.').last
351
- @klass.type_for_attribute(field_name, &block)
352
- end
352
+ @klass.connection.table_alias_for(table_name)
353
+ end
353
354
 
354
- def type_cast_calculated_value(value, type, operation = nil)
355
- case operation
356
- when 'count' then value.to_i
357
- when 'sum' then type.deserialize(value || 0)
358
- when 'average' then value.respond_to?(:to_d) ? value.to_d : value
355
+ def type_for(field, &block)
356
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
357
+ @klass.type_for_attribute(field_name, &block)
358
+ end
359
+
360
+ def type_cast_calculated_value(value, type, operation = nil)
361
+ case operation
362
+ when "count" then value.to_i
363
+ when "sum" then type.deserialize(value || 0)
364
+ when "average" then value.respond_to?(:to_d) ? value.to_d : value
359
365
  else type.deserialize(value)
366
+ end
360
367
  end
361
- end
362
368
 
363
- def select_for_count
364
- if select_values.present?
365
- return select_values.first if select_values.one?
366
- select_values.join(", ")
367
- else
368
- :all
369
+ def select_for_count
370
+ if select_values.present?
371
+ return select_values.first if select_values.one?
372
+ select_values.join(", ")
373
+ else
374
+ :all
375
+ end
369
376
  end
370
- end
371
377
 
372
- def build_count_subquery(relation, column_name, distinct)
373
- column_alias = Arel.sql('count_column')
374
- subquery_alias = Arel.sql('subquery_for_count')
378
+ def build_count_subquery(relation, column_name, distinct)
379
+ if column_name == :all
380
+ relation.select_values = [ Arel.sql(FinderMethods::ONE_AS_ONE) ] unless distinct
381
+ else
382
+ column_alias = Arel.sql("count_column")
383
+ relation.select_values = [ aggregate_column(column_name).as(column_alias) ]
384
+ end
375
385
 
376
- aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
377
- relation.select_values = [aliased_column]
378
- subquery = relation.arel.as(subquery_alias)
386
+ subquery = relation.arel.as(Arel.sql("subquery_for_count"))
387
+ select_value = operation_over_aggregate_column(column_alias || Arel.star, "count", false)
379
388
 
380
- sm = Arel::SelectManager.new relation.engine
381
- select_value = operation_over_aggregate_column(column_alias, 'count', distinct)
382
- sm.project(select_value).from(subquery)
383
- end
389
+ Arel::SelectManager.new(subquery).project(select_value)
390
+ end
384
391
  end
385
392
  end
@@ -1,5 +1,3 @@
1
- require 'active_support/concern'
2
-
3
1
  module ActiveRecord
4
2
  module Delegation # :nodoc:
5
3
  module DelegateCache # :nodoc:
@@ -17,7 +15,10 @@ module ActiveRecord
17
15
  delegate = Class.new(klass) {
18
16
  include ClassSpecificRelation
19
17
  }
20
- const_set klass.name.gsub('::'.freeze, '_'.freeze), delegate
18
+ mangled_name = klass.name.gsub("::".freeze, "_".freeze)
19
+ const_set mangled_name, delegate
20
+ private_constant mangled_name
21
+
21
22
  cache[klass] = delegate
22
23
  end
23
24
  end
@@ -35,13 +36,13 @@ module ActiveRecord
35
36
  # may vary depending on the klass of a relation, so we create a subclass of Relation
36
37
  # for each different klass, and the delegations are compiled into that subclass only.
37
38
 
38
- delegate :to_xml, :encode_with, :length, :collect, :map, :each, :all?, :include?, :to_ary, :join,
39
+ delegate :to_xml, :encode_with, :length, :each, :uniq, :to_ary, :join,
39
40
  :[], :&, :|, :+, :-, :sample, :reverse, :compact, :in_groups, :in_groups_of,
40
- :to_sentence, :to_formatted_s,
41
+ :to_sentence, :to_formatted_s, :as_json,
41
42
  :shuffle, :split, :index, to: :records
42
43
 
43
44
  delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key,
44
- :connection, :columns_hash, :to => :klass
45
+ :connection, :columns_hash, to: :klass
45
46
 
46
47
  module ClassSpecificRelation # :nodoc:
47
48
  extend ActiveSupport::Concern
@@ -59,7 +60,7 @@ module ActiveRecord
59
60
  @delegation_mutex.synchronize do
60
61
  return if method_defined?(method)
61
62
 
62
- if method.to_s =~ /\A[a-zA-Z_]\w*[!?]?\z/
63
+ if /\A[a-zA-Z_]\w*[!?]?\z/.match?(method)
63
64
  module_eval <<-RUBY, __FILE__, __LINE__ + 1
64
65
  def #{method}(*args, &block)
65
66
  scoping { @klass.#{method}(*args, &block) }
@@ -81,19 +82,19 @@ module ActiveRecord
81
82
  end
82
83
  end
83
84
 
84
- protected
85
+ private
85
86
 
86
- def method_missing(method, *args, &block)
87
- if @klass.respond_to?(method)
88
- self.class.delegate_to_scoped_klass(method)
89
- scoping { @klass.public_send(method, *args, &block) }
90
- elsif arel.respond_to?(method)
91
- self.class.delegate method, :to => :arel
92
- arel.public_send(method, *args, &block)
93
- else
94
- super
87
+ def method_missing(method, *args, &block)
88
+ if @klass.respond_to?(method)
89
+ self.class.delegate_to_scoped_klass(method)
90
+ scoping { @klass.public_send(method, *args, &block) }
91
+ elsif arel.respond_to?(method)
92
+ self.class.delegate method, to: :arel
93
+ arel.public_send(method, *args, &block)
94
+ else
95
+ super
96
+ end
95
97
  end
96
- end
97
98
  end
98
99
 
99
100
  module ClassMethods # :nodoc:
@@ -103,26 +104,26 @@ module ActiveRecord
103
104
 
104
105
  private
105
106
 
106
- def relation_class_for(klass)
107
- klass.relation_delegate_class(self)
108
- end
107
+ def relation_class_for(klass)
108
+ klass.relation_delegate_class(self)
109
+ end
109
110
  end
110
111
 
111
- def respond_to?(method, include_private = false)
112
+ def respond_to_missing?(method, include_private = false)
112
113
  super || @klass.respond_to?(method, include_private) ||
113
114
  arel.respond_to?(method, include_private)
114
115
  end
115
116
 
116
- protected
117
+ private
117
118
 
118
- def method_missing(method, *args, &block)
119
- if @klass.respond_to?(method)
120
- scoping { @klass.public_send(method, *args, &block) }
121
- elsif arel.respond_to?(method)
122
- arel.public_send(method, *args, &block)
123
- else
124
- super
119
+ def method_missing(method, *args, &block)
120
+ if @klass.respond_to?(method)
121
+ scoping { @klass.public_send(method, *args, &block) }
122
+ elsif arel.respond_to?(method)
123
+ arel.public_send(method, *args, &block)
124
+ else
125
+ super
126
+ end
125
127
  end
126
- end
127
128
  end
128
129
  end