activerecord 5.0.7.2 → 5.1.0.beta1

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 (216) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +389 -2252
  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.rb +20 -20
  8. data/lib/active_record/aggregations.rb +244 -244
  9. data/lib/active_record/association_relation.rb +5 -5
  10. data/lib/active_record/associations.rb +1579 -1569
  11. data/lib/active_record/associations/alias_tracker.rb +1 -1
  12. data/lib/active_record/associations/association.rb +23 -15
  13. data/lib/active_record/associations/association_scope.rb +83 -81
  14. data/lib/active_record/associations/belongs_to_association.rb +0 -1
  15. data/lib/active_record/associations/builder/belongs_to.rb +16 -14
  16. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  18. data/lib/active_record/associations/collection_association.rb +74 -241
  19. data/lib/active_record/associations/collection_proxy.rb +144 -70
  20. data/lib/active_record/associations/has_many_association.rb +15 -19
  21. data/lib/active_record/associations/has_many_through_association.rb +12 -5
  22. data/lib/active_record/associations/has_one_association.rb +22 -28
  23. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  24. data/lib/active_record/associations/join_dependency.rb +117 -115
  25. data/lib/active_record/associations/join_dependency/join_association.rb +16 -13
  26. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  27. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  28. data/lib/active_record/associations/preloader.rb +94 -94
  29. data/lib/active_record/associations/preloader/association.rb +87 -64
  30. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  31. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  32. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  33. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  34. data/lib/active_record/associations/preloader/through_association.rb +34 -41
  35. data/lib/active_record/associations/singular_association.rb +8 -25
  36. data/lib/active_record/associations/through_association.rb +3 -6
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  39. data/lib/active_record/attribute_assignment.rb +61 -61
  40. data/lib/active_record/attribute_decorators.rb +35 -13
  41. data/lib/active_record/attribute_methods.rb +56 -65
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  43. data/lib/active_record/attribute_methods/dirty.rb +216 -34
  44. data/lib/active_record/attribute_methods/primary_key.rb +78 -73
  45. data/lib/active_record/attribute_methods/read.rb +39 -35
  46. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  48. data/lib/active_record/attribute_methods/write.rb +36 -30
  49. data/lib/active_record/attribute_mutation_tracker.rb +53 -10
  50. data/lib/active_record/attribute_set.rb +9 -6
  51. data/lib/active_record/attribute_set/builder.rb +41 -49
  52. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  53. data/lib/active_record/attributes.rb +21 -21
  54. data/lib/active_record/autosave_association.rb +13 -13
  55. data/lib/active_record/base.rb +24 -22
  56. data/lib/active_record/callbacks.rb +52 -14
  57. data/lib/active_record/coders/yaml_column.rb +9 -11
  58. data/lib/active_record/collection_cache_key.rb +6 -17
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +320 -278
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -34
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -27
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +44 -57
  64. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +9 -19
  65. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +78 -79
  66. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  67. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +99 -93
  68. data/lib/active_record/connection_adapters/abstract/transaction.rb +1 -5
  69. data/lib/active_record/connection_adapters/abstract_adapter.rb +156 -128
  70. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +424 -382
  71. data/lib/active_record/connection_adapters/column.rb +27 -5
  72. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  73. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +45 -43
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +49 -31
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +5 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +24 -26
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +1 -28
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +46 -35
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  86. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +9 -9
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  94. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  95. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  97. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +28 -30
  98. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  99. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +38 -36
  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 +161 -170
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +4 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -7
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +179 -152
  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 -20
  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_adapter.rb +187 -130
  116. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  117. data/lib/active_record/connection_handling.rb +14 -26
  118. data/lib/active_record/core.rb +110 -93
  119. data/lib/active_record/counter_cache.rb +62 -13
  120. data/lib/active_record/define_callbacks.rb +20 -0
  121. data/lib/active_record/dynamic_matchers.rb +80 -79
  122. data/lib/active_record/enum.rb +8 -6
  123. data/lib/active_record/errors.rb +58 -15
  124. data/lib/active_record/explain.rb +1 -2
  125. data/lib/active_record/explain_registry.rb +1 -1
  126. data/lib/active_record/explain_subscriber.rb +7 -4
  127. data/lib/active_record/fixture_set/file.rb +11 -8
  128. data/lib/active_record/fixtures.rb +66 -53
  129. data/lib/active_record/gem_version.rb +3 -3
  130. data/lib/active_record/inheritance.rb +93 -79
  131. data/lib/active_record/integration.rb +7 -7
  132. data/lib/active_record/internal_metadata.rb +3 -16
  133. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  134. data/lib/active_record/locking/optimistic.rb +64 -56
  135. data/lib/active_record/locking/pessimistic.rb +10 -1
  136. data/lib/active_record/log_subscriber.rb +29 -29
  137. data/lib/active_record/migration.rb +155 -172
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +76 -37
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/model_schema.rb +85 -119
  142. data/lib/active_record/nested_attributes.rb +200 -199
  143. data/lib/active_record/null_relation.rb +10 -33
  144. data/lib/active_record/persistence.rb +45 -38
  145. data/lib/active_record/query_cache.rb +4 -8
  146. data/lib/active_record/querying.rb +2 -3
  147. data/lib/active_record/railtie.rb +16 -17
  148. data/lib/active_record/railties/controller_runtime.rb +6 -2
  149. data/lib/active_record/railties/databases.rake +125 -140
  150. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  151. data/lib/active_record/readonly_attributes.rb +2 -2
  152. data/lib/active_record/reflection.rb +79 -96
  153. data/lib/active_record/relation.rb +72 -115
  154. data/lib/active_record/relation/batches.rb +87 -58
  155. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  156. data/lib/active_record/relation/calculations.rb +154 -160
  157. data/lib/active_record/relation/delegation.rb +30 -29
  158. data/lib/active_record/relation/finder_methods.rb +195 -226
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder.rb +92 -89
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  165. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  166. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +247 -295
  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 +79 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/result.rb +29 -31
  174. data/lib/active_record/runtime_registry.rb +3 -3
  175. data/lib/active_record/sanitization.rb +182 -197
  176. data/lib/active_record/schema.rb +3 -3
  177. data/lib/active_record/schema_dumper.rb +14 -37
  178. data/lib/active_record/schema_migration.rb +3 -3
  179. data/lib/active_record/scoping.rb +9 -10
  180. data/lib/active_record/scoping/default.rb +87 -91
  181. data/lib/active_record/scoping/named.rb +16 -28
  182. data/lib/active_record/secure_token.rb +2 -2
  183. data/lib/active_record/statement_cache.rb +13 -15
  184. data/lib/active_record/store.rb +31 -32
  185. data/lib/active_record/suppressor.rb +2 -1
  186. data/lib/active_record/table_metadata.rb +9 -5
  187. data/lib/active_record/tasks/database_tasks.rb +72 -65
  188. data/lib/active_record/tasks/mysql_database_tasks.rb +75 -72
  189. data/lib/active_record/tasks/postgresql_database_tasks.rb +53 -48
  190. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  191. data/lib/active_record/timestamp.rb +39 -25
  192. data/lib/active_record/touch_later.rb +1 -2
  193. data/lib/active_record/transactions.rb +98 -110
  194. data/lib/active_record/type.rb +17 -13
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +9 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/serialized.rb +8 -8
  199. data/lib/active_record/type/text.rb +9 -0
  200. data/lib/active_record/type/time.rb +0 -1
  201. data/lib/active_record/type/type_map.rb +11 -15
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type_caster.rb +2 -2
  204. data/lib/active_record/type_caster/connection.rb +8 -6
  205. data/lib/active_record/type_caster/map.rb +3 -1
  206. data/lib/active_record/validations.rb +4 -4
  207. data/lib/active_record/validations/associated.rb +1 -1
  208. data/lib/active_record/validations/presence.rb +2 -2
  209. data/lib/active_record/validations/uniqueness.rb +8 -39
  210. data/lib/active_record/version.rb +1 -1
  211. data/lib/rails/generators/active_record.rb +4 -4
  212. data/lib/rails/generators/active_record/migration.rb +2 -2
  213. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  214. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  215. metadata +22 -13
  216. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -2,7 +2,7 @@ require "active_record/relation/batches/batch_enumerator"
2
2
 
3
3
  module ActiveRecord
4
4
  module Batches
5
- ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
5
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
6
6
 
7
7
  # Looping through a collection of records from the database
8
8
  # (using the Scoping::Named::ClassMethods.all method, for example)
@@ -30,19 +30,23 @@ module ActiveRecord
30
30
  # end
31
31
  #
32
32
  # ==== Options
33
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
33
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
34
34
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
35
35
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
36
36
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
37
- # the order and limit have to be ignored due to batching.
37
+ # an order is present in the relation.
38
38
  #
39
- # This is especially useful if you want multiple workers dealing with
40
- # the same processing queue. You can make worker 1 handle all the records
41
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
42
- # (by setting the +:start+ and +:finish+ option on each worker).
39
+ # Limits are honored, and if present there is no requirement for the batch
40
+ # size, it can be less than, equal, or greater than the limit.
43
41
  #
44
- # # Let's process for a batch of 2000 records, skipping the first 2000 rows
45
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
42
+ # The options +start+ and +finish+ are especially useful if you want
43
+ # multiple workers dealing with the same processing queue. You can make
44
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
45
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
46
+ # option on each worker.
47
+ #
48
+ # # Let's process from record 10_000 on.
49
+ # Person.find_each(start: 10_000) do |person|
46
50
  # person.party_all_night!
47
51
  # end
48
52
  #
@@ -51,8 +55,8 @@ module ActiveRecord
51
55
  # work. This also means that this method only works when the primary key is
52
56
  # orderable (e.g. an integer or string).
53
57
  #
54
- # NOTE: You can't set the limit either, that's used to control
55
- # the batch sizes.
58
+ # NOTE: By its nature, batch processing is subject to race conditions if
59
+ # other processes are modifying the database.
56
60
  def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
57
61
  if block_given?
58
62
  find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
@@ -85,19 +89,23 @@ module ActiveRecord
85
89
  # To be yielded each record one by one, use #find_each instead.
86
90
  #
87
91
  # ==== Options
88
- # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
92
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
89
93
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
90
94
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
95
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
92
- # the order and limit have to be ignored due to batching.
96
+ # an order is present in the relation.
97
+ #
98
+ # Limits are honored, and if present there is no requirement for the batch
99
+ # size, it can be less than, equal, or greater than the limit.
93
100
  #
94
- # This is especially useful if you want multiple workers dealing with
95
- # the same processing queue. You can make worker 1 handle all the records
96
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
97
- # (by setting the +:start+ and +:finish+ option on each worker).
101
+ # The options +start+ and +finish+ are especially useful if you want
102
+ # multiple workers dealing with the same processing queue. You can make
103
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
104
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
105
+ # option on each worker.
98
106
  #
99
- # # Let's process the next 2000 records
100
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
107
+ # # Let's process from record 10_000 on.
108
+ # Person.find_in_batches(start: 10_000) do |group|
101
109
  # group.each { |person| person.party_all_night! }
102
110
  # end
103
111
  #
@@ -106,8 +114,8 @@ module ActiveRecord
106
114
  # work. This also means that this method only works when the primary key is
107
115
  # orderable (e.g. an integer or string).
108
116
  #
109
- # NOTE: You can't set the limit either, that's used to control
110
- # the batch sizes.
117
+ # NOTE: By its nature, batch processing is subject to race conditions if
118
+ # other processes are modifying the database.
111
119
  def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
112
120
  relation = self
113
121
  unless block_given?
@@ -132,9 +140,9 @@ module ActiveRecord
132
140
  # If you do not provide a block to #in_batches, it will return a
133
141
  # BatchEnumerator which is enumerable.
134
142
  #
135
- # Person.in_batches.each_with_index do |relation, batch_index|
143
+ # Person.in_batches.with_index do |relation, batch_index|
136
144
  # puts "Processing relation ##{batch_index}"
137
- # relation.delete_all
145
+ # relation.each { |relation| relation.delete_all }
138
146
  # end
139
147
  #
140
148
  # Examples of calling methods on the returned BatchEnumerator object:
@@ -144,22 +152,24 @@ module ActiveRecord
144
152
  # Person.in_batches.each_record(&:party_all_night!)
145
153
  #
146
154
  # ==== Options
147
- # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
148
- # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
155
+ # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
156
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
149
157
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
150
158
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
159
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
152
- # the order and limit have to be ignored due to batching.
160
+ # an order is present in the relation.
161
+ #
162
+ # Limits are honored, and if present there is no requirement for the batch
163
+ # size, it can be less than, equal, or greater than the limit.
153
164
  #
154
- # This is especially useful if you want to work with the
155
- # ActiveRecord::Relation object instead of the array of records, or if
156
- # you want multiple workers dealing with the same processing queue. You can
157
- # make worker 1 handle all the records between id 0 and 10,000 and worker 2
158
- # handle from 10,000 and beyond (by setting the +:start+ and +:finish+
159
- # option on each worker).
165
+ # The options +start+ and +finish+ are especially useful if you want
166
+ # multiple workers dealing with the same processing queue. You can make
167
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
168
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
169
+ # option on each worker.
160
170
  #
161
- # # Let's process the next 2000 records
162
- # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
171
+ # # Let's process from record 10_000 on.
172
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
163
173
  #
164
174
  # An example of calling where query method on the relation:
165
175
  #
@@ -176,22 +186,28 @@ module ActiveRecord
176
186
  #
177
187
  # NOTE: It's not possible to set the order. That is automatically set to
178
188
  # ascending on the primary key ("id ASC") to make the batch ordering
179
- # consistent. Therefore the primary key must be orderable, e.g. an integer
189
+ # consistent. Therefore the primary key must be orderable, e.g an integer
180
190
  # or a string.
181
191
  #
182
- # NOTE: You can't set the limit either, that's used to control the batch
183
- # sizes.
192
+ # NOTE: By its nature, batch processing is subject to race conditions if
193
+ # other processes are modifying the database.
184
194
  def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
185
195
  relation = self
186
196
  unless block_given?
187
197
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
188
198
  end
189
199
 
190
- if arel.orders.present? || arel.taken.present?
191
- act_on_order_or_limit_ignored(error_on_ignore)
200
+ if arel.orders.present?
201
+ act_on_ignored_order(error_on_ignore)
192
202
  end
193
203
 
194
- relation = relation.reorder(batch_order).limit(of)
204
+ batch_limit = of
205
+ if limit_value
206
+ remaining = limit_value
207
+ batch_limit = remaining if remaining < batch_limit
208
+ end
209
+
210
+ relation = relation.reorder(batch_order).limit(batch_limit)
195
211
  relation = apply_limits(relation, start, finish)
196
212
  batch_relation = relation
197
213
 
@@ -199,11 +215,11 @@ module ActiveRecord
199
215
  if load
200
216
  records = batch_relation.records
201
217
  ids = records.map(&:id)
202
- yielded_relation = self.where(primary_key => ids)
218
+ yielded_relation = where(primary_key => ids)
203
219
  yielded_relation.load_records(records)
204
220
  else
205
221
  ids = batch_relation.pluck(primary_key)
206
- yielded_relation = self.where(primary_key => ids)
222
+ yielded_relation = where(primary_key => ids)
207
223
  end
208
224
 
209
225
  break if ids.empty?
@@ -213,31 +229,44 @@ module ActiveRecord
213
229
 
214
230
  yield yielded_relation
215
231
 
216
- break if ids.length < of
232
+ break if ids.length < batch_limit
233
+
234
+ if limit_value
235
+ remaining -= ids.length
236
+
237
+ if remaining == 0
238
+ # Saves a useless iteration when the limit is a multiple of the
239
+ # batch size.
240
+ break
241
+ elsif remaining < batch_limit
242
+ relation = relation.limit(remaining)
243
+ end
244
+ end
245
+
217
246
  batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
218
247
  end
219
248
  end
220
249
 
221
250
  private
222
251
 
223
- def apply_limits(relation, start, finish)
224
- relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
225
- relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
226
- relation
227
- end
252
+ def apply_limits(relation, start, finish)
253
+ relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
254
+ relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
255
+ relation
256
+ end
228
257
 
229
- def batch_order
230
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
231
- end
258
+ def batch_order
259
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
260
+ end
232
261
 
233
- def act_on_order_or_limit_ignored(error_on_ignore)
234
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
262
+ def act_on_ignored_order(error_on_ignore)
263
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
235
264
 
236
- if raise_error
237
- raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
- elsif logger
239
- logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
265
+ if raise_error
266
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
267
+ elsif logger
268
+ logger.warn(ORDER_IGNORE_MESSAGE)
269
+ end
240
270
  end
241
- end
242
271
  end
243
272
  end
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  @of = of
8
8
  @relation = relation
9
9
  @start = start
10
- @finish = finish
10
+ @finish = finish
11
11
  end
12
12
 
13
13
  # Looping through a collection of records from the database (using the
@@ -37,7 +37,11 @@ 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
- calculate(:count, column_name)
40
+ if block_given?
41
+ to_a.count { |*block_args| yield(*block_args) }
42
+ else
43
+ calculate(:count, column_name)
44
+ end
41
45
  end
42
46
 
43
47
  # Calculates the average value on a given column. Returns +nil+ if there's
@@ -108,10 +112,6 @@ module ActiveRecord
108
112
  # ...
109
113
  # end
110
114
  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
115
  if has_include?(column_name)
116
116
  relation = construct_relation_for_association_calculations
117
117
  relation = relation.distinct if operation.to_s.downcase == "count"
@@ -184,202 +184,196 @@ module ActiveRecord
184
184
 
185
185
  private
186
186
 
187
- def has_include?(column_name)
188
- eager_loading? || (includes_values.present? && column_name && column_name != :all)
189
- end
187
+ def has_include?(column_name)
188
+ eager_loading? || (includes_values.present? && column_name && column_name != :all)
189
+ end
190
190
 
191
- def perform_calculation(operation, column_name)
192
- operation = operation.to_s.downcase
191
+ def perform_calculation(operation, column_name)
192
+ operation = operation.to_s.downcase
193
193
 
194
- # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
195
- # considered distinct.
196
- distinct = self.distinct_value
194
+ # If #count is used with #distinct (i.e. `relation.distinct.count`) it is
195
+ # considered distinct.
196
+ distinct = distinct_value
197
197
 
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
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
203
203
 
204
- if group_values.any?
205
- execute_grouped_calculation(operation, column_name, distinct)
206
- else
207
- execute_simple_calculation(operation, column_name, distinct)
204
+ if group_values.any?
205
+ execute_grouped_calculation(operation, column_name, distinct)
206
+ else
207
+ execute_simple_calculation(operation, column_name, distinct)
208
+ end
208
209
  end
209
- end
210
210
 
211
- def aggregate_column(column_name)
212
- return column_name if Arel::Expressions === column_name
211
+ def aggregate_column(column_name)
212
+ return column_name if Arel::Expressions === column_name
213
213
 
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)
214
+ if @klass.has_attribute?(column_name.to_s) || @klass.attribute_alias?(column_name.to_s)
215
+ @klass.arel_attribute(column_name)
216
+ else
217
+ Arel.sql(column_name == :all ? "*" : column_name.to_s)
218
+ end
218
219
  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
220
 
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
221
+ def operation_over_aggregate_column(column, operation, distinct)
222
+ operation == "count" ? column.count(distinct) : column.send(operation)
223
+ end
234
224
 
235
- query_builder = build_count_subquery(relation, column_name, distinct)
236
- else
237
- column = aggregate_column(column_name)
225
+ def execute_simple_calculation(operation, column_name, distinct) #:nodoc:
226
+ column_alias = column_name
238
227
 
239
- select_value = operation_over_aggregate_column(column, operation, distinct)
228
+ if operation == "count" && (limit_value || offset_value)
229
+ # Shortcut when limit is zero.
230
+ return 0 if limit_value == 0
240
231
 
241
- if operation == "sum" && distinct
242
- select_value.distinct = true
243
- end
232
+ query_builder = build_count_subquery(spawn, column_name, distinct)
233
+ else
234
+ # PostgreSQL doesn't like ORDER BY when there are no GROUP BY
235
+ relation = unscope(:order)
244
236
 
245
- column_alias = select_value.alias
246
- column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
247
- relation.select_values = [select_value]
237
+ column = aggregate_column(column_name)
248
238
 
249
- query_builder = relation.arel
250
- end
239
+ select_value = operation_over_aggregate_column(column, operation, distinct)
251
240
 
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
241
+ column_alias = select_value.alias
242
+ column_alias ||= @klass.connection.column_name_for_operation(operation, select_value)
243
+ relation.select_values = [select_value]
258
244
 
259
- type_cast_calculated_value(value, column, operation)
260
- end
245
+ query_builder = relation.arel
246
+ end
261
247
 
262
- def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
263
- group_attrs = group_values
248
+ result = @klass.connection.select_all(query_builder, nil, bound_attributes)
249
+ row = result.first
250
+ value = row && row.values.first
251
+ type = result.column_types.fetch(column_alias) do
252
+ type_for(column_name)
253
+ end
264
254
 
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
255
+ type_cast_calculated_value(value, type, operation)
271
256
  end
272
- group_fields = arel_columns(group_fields)
273
-
274
- group_aliases = group_fields.map { |field| column_alias_for(field) }
275
- group_columns = group_aliases.zip(group_fields)
276
257
 
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
258
+ def execute_grouped_calculation(operation, column_name, distinct) #:nodoc:
259
+ group_attrs = group_values
282
260
 
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)
261
+ if group_attrs.first.respond_to?(:to_sym)
262
+ association = @klass._reflect_on_association(group_attrs.first)
263
+ associated = group_attrs.size == 1 && association && association.belongs_to? # only count belongs_to associations
264
+ group_fields = Array(associated ? association.foreign_key : group_attrs)
294
265
  else
295
- "#{field} AS #{aliaz}"
266
+ group_fields = group_attrs
296
267
  end
297
- }
268
+ group_fields = arel_columns(group_fields)
298
269
 
299
- relation = except(:group)
300
- relation.group_values = group_fields
301
- relation.select_values = select_values
270
+ group_aliases = group_fields.map { |field| column_alias_for(field) }
271
+ group_columns = group_aliases.zip(group_fields)
302
272
 
303
- calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
304
-
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
273
+ if operation == "count" && column_name == :all
274
+ aggregate_alias = "count_all"
275
+ else
276
+ aggregate_alias = column_alias_for([operation, column_name].join(" "))
277
+ end
310
278
 
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
279
+ select_values = [
280
+ operation_over_aggregate_column(
281
+ aggregate_column(column_name),
282
+ operation,
283
+ distinct).as(aggregate_alias)
284
+ ]
285
+ select_values += select_values unless having_clause.empty?
286
+
287
+ select_values.concat group_columns.map { |aliaz, field|
288
+ if field.respond_to?(:as)
289
+ field.as(aliaz)
290
+ else
291
+ "#{field} AS #{aliaz}"
317
292
  end
318
- type_cast_calculated_value(row[aliaz], column)
319
293
  }
320
- key = key.first if key.size == 1
321
- key = key_records[key] if associated
322
294
 
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
295
+ relation = except(:group)
296
+ relation.group_values = group_fields
297
+ relation.select_values = select_values
327
298
 
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}"
299
+ calculated_data = @klass.connection.select_all(relation, nil, relation.bound_attributes)
300
+
301
+ if association
302
+ key_ids = calculated_data.collect { |row| row[group_aliases.first] }
303
+ key_records = association.klass.base_class.where(association.klass.base_class.primary_key => key_ids)
304
+ key_records = Hash[key_records.map { |r| [r.id, r] }]
305
+ end
306
+
307
+ Hash[calculated_data.map do |row|
308
+ key = group_columns.map { |aliaz, col_name|
309
+ type = type_for(col_name) do
310
+ calculated_data.column_types.fetch(aliaz, Type.default_value)
311
+ end
312
+ type_cast_calculated_value(row[aliaz], type)
313
+ }
314
+ key = key.first if key.size == 1
315
+ key = key_records[key] if associated
316
+
317
+ type = calculated_data.column_types.fetch(aggregate_alias) { type_for(column_name) }
318
+ [key, type_cast_calculated_value(row[aggregate_alias], type, operation)]
319
+ end]
338
320
  end
339
321
 
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!(/ +/, '_')
322
+ # Converts the given keys to the value that the database adapter returns as
323
+ # a usable column name:
324
+ #
325
+ # column_alias_for("users.id") # => "users_id"
326
+ # column_alias_for("sum(id)") # => "sum_id"
327
+ # column_alias_for("count(distinct users.id)") # => "count_distinct_users_id"
328
+ # column_alias_for("count(*)") # => "count_all"
329
+ def column_alias_for(keys)
330
+ if keys.respond_to? :name
331
+ keys = "#{keys.relation.name}.#{keys.name}"
332
+ end
345
333
 
346
- @klass.connection.table_alias_for(table_name)
347
- end
334
+ table_name = keys.to_s.downcase
335
+ table_name.gsub!(/\*/, "all")
336
+ table_name.gsub!(/\W+/, " ")
337
+ table_name.strip!
338
+ table_name.gsub!(/ +/, "_")
348
339
 
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
340
+ @klass.connection.table_alias_for(table_name)
341
+ end
353
342
 
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
343
+ def type_for(field, &block)
344
+ field_name = field.respond_to?(:name) ? field.name.to_s : field.to_s.split(".").last
345
+ @klass.type_for_attribute(field_name, &block)
346
+ end
347
+
348
+ def type_cast_calculated_value(value, type, operation = nil)
349
+ case operation
350
+ when "count" then value.to_i
351
+ when "sum" then type.deserialize(value || 0)
352
+ when "average" then value.respond_to?(:to_d) ? value.to_d : value
359
353
  else type.deserialize(value)
354
+ end
360
355
  end
361
- end
362
356
 
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
357
+ def select_for_count
358
+ if select_values.present?
359
+ return select_values.first if select_values.one?
360
+ select_values.join(", ")
361
+ else
362
+ :all
363
+ end
369
364
  end
370
- end
371
365
 
372
- def build_count_subquery(relation, column_name, distinct)
373
- column_alias = Arel.sql('count_column')
374
- subquery_alias = Arel.sql('subquery_for_count')
366
+ def build_count_subquery(relation, column_name, distinct)
367
+ column_alias = Arel.sql("count_column")
368
+ subquery_alias = Arel.sql("subquery_for_count")
375
369
 
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)
370
+ aliased_column = aggregate_column(column_name == :all ? 1 : column_name).as(column_alias)
371
+ relation.select_values = [aliased_column]
372
+ subquery = relation.arel.as(subquery_alias)
379
373
 
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
374
+ sm = Arel::SelectManager.new relation.engine
375
+ select_value = operation_over_aggregate_column(column_alias, "count", distinct)
376
+ sm.project(select_value).from(subquery)
377
+ end
384
378
  end
385
379
  end