activerecord 4.2.0 → 5.0.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1537 -789
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +7 -8
  5. data/examples/performance.rb +2 -3
  6. data/examples/simple.rb +0 -1
  7. data/lib/active_record/aggregations.rb +37 -23
  8. data/lib/active_record/association_relation.rb +16 -3
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +23 -9
  11. data/lib/active_record/associations/association_scope.rb +74 -102
  12. data/lib/active_record/associations/belongs_to_association.rb +26 -29
  13. data/lib/active_record/associations/builder/association.rb +28 -34
  14. data/lib/active_record/associations/builder/belongs_to.rb +43 -18
  15. data/lib/active_record/associations/builder/collection_association.rb +12 -20
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +22 -15
  17. data/lib/active_record/associations/builder/has_many.rb +4 -4
  18. data/lib/active_record/associations/builder/has_one.rb +11 -6
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -10
  20. data/lib/active_record/associations/collection_association.rb +61 -33
  21. data/lib/active_record/associations/collection_proxy.rb +81 -35
  22. data/lib/active_record/associations/foreign_association.rb +11 -0
  23. data/lib/active_record/associations/has_many_association.rb +21 -57
  24. data/lib/active_record/associations/has_many_through_association.rb +15 -45
  25. data/lib/active_record/associations/has_one_association.rb +13 -5
  26. data/lib/active_record/associations/join_dependency/join_association.rb +20 -8
  27. data/lib/active_record/associations/join_dependency.rb +37 -21
  28. data/lib/active_record/associations/preloader/association.rb +51 -53
  29. data/lib/active_record/associations/preloader/collection_association.rb +0 -6
  30. data/lib/active_record/associations/preloader/has_many_through.rb +1 -1
  31. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  32. data/lib/active_record/associations/preloader/through_association.rb +27 -14
  33. data/lib/active_record/associations/preloader.rb +18 -8
  34. data/lib/active_record/associations/singular_association.rb +8 -8
  35. data/lib/active_record/associations/through_association.rb +22 -9
  36. data/lib/active_record/associations.rb +321 -212
  37. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  38. data/lib/active_record/attribute.rb +79 -15
  39. data/lib/active_record/attribute_assignment.rb +20 -141
  40. data/lib/active_record/attribute_decorators.rb +6 -5
  41. data/lib/active_record/attribute_methods/before_type_cast.rb +6 -1
  42. data/lib/active_record/attribute_methods/dirty.rb +51 -81
  43. data/lib/active_record/attribute_methods/primary_key.rb +2 -2
  44. data/lib/active_record/attribute_methods/query.rb +2 -2
  45. data/lib/active_record/attribute_methods/read.rb +31 -59
  46. data/lib/active_record/attribute_methods/serialization.rb +13 -16
  47. data/lib/active_record/attribute_methods/time_zone_conversion.rb +65 -14
  48. data/lib/active_record/attribute_methods/write.rb +14 -38
  49. data/lib/active_record/attribute_methods.rb +70 -45
  50. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  51. data/lib/active_record/attribute_set/builder.rb +37 -15
  52. data/lib/active_record/attribute_set.rb +34 -3
  53. data/lib/active_record/attributes.rb +199 -73
  54. data/lib/active_record/autosave_association.rb +73 -25
  55. data/lib/active_record/base.rb +35 -27
  56. data/lib/active_record/callbacks.rb +39 -43
  57. data/lib/active_record/coders/json.rb +1 -1
  58. data/lib/active_record/coders/yaml_column.rb +20 -8
  59. data/lib/active_record/collection_cache_key.rb +40 -0
  60. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +457 -181
  61. data/lib/active_record/connection_adapters/abstract/database_limits.rb +3 -3
  62. data/lib/active_record/connection_adapters/abstract/database_statements.rb +83 -59
  63. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -3
  64. data/lib/active_record/connection_adapters/abstract/quoting.rb +74 -9
  65. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -4
  66. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +61 -39
  67. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +246 -185
  68. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +72 -17
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +438 -136
  70. data/lib/active_record/connection_adapters/abstract/transaction.rb +53 -40
  71. data/lib/active_record/connection_adapters/abstract_adapter.rb +166 -66
  72. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +429 -335
  73. data/lib/active_record/connection_adapters/column.rb +28 -43
  74. data/lib/active_record/connection_adapters/connection_specification.rb +15 -27
  75. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  76. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  77. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  78. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  79. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  81. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  82. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  83. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  84. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -177
  85. data/lib/active_record/connection_adapters/postgresql/column.rb +5 -10
  86. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +11 -73
  87. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +27 -56
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +2 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +2 -1
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +7 -13
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -1
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +3 -3
  95. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +1 -26
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +0 -4
  98. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +4 -4
  99. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +31 -17
  101. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +17 -5
  102. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +2 -2
  103. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +1 -1
  104. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +1 -1
  105. data/lib/active_record/connection_adapters/postgresql/oid.rb +1 -6
  106. data/lib/active_record/connection_adapters/postgresql/quoting.rb +26 -18
  107. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +29 -10
  108. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -79
  109. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +248 -154
  111. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  112. data/lib/active_record/connection_adapters/postgresql_adapter.rb +258 -170
  113. data/lib/active_record/connection_adapters/schema_cache.rb +36 -23
  114. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  115. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  116. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  117. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  118. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +150 -209
  119. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  120. data/lib/active_record/connection_handling.rb +38 -15
  121. data/lib/active_record/core.rb +109 -114
  122. data/lib/active_record/counter_cache.rb +14 -25
  123. data/lib/active_record/dynamic_matchers.rb +1 -20
  124. data/lib/active_record/enum.rb +115 -79
  125. data/lib/active_record/errors.rb +88 -48
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +2 -2
  128. data/lib/active_record/fixture_set/file.rb +26 -5
  129. data/lib/active_record/fixtures.rb +84 -46
  130. data/lib/active_record/gem_version.rb +2 -2
  131. data/lib/active_record/inheritance.rb +32 -40
  132. data/lib/active_record/integration.rb +4 -4
  133. data/lib/active_record/internal_metadata.rb +56 -0
  134. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  135. data/lib/active_record/locale/en.yml +3 -2
  136. data/lib/active_record/locking/optimistic.rb +27 -25
  137. data/lib/active_record/locking/pessimistic.rb +1 -1
  138. data/lib/active_record/log_subscriber.rb +43 -21
  139. data/lib/active_record/migration/command_recorder.rb +59 -18
  140. data/lib/active_record/migration/compatibility.rb +126 -0
  141. data/lib/active_record/migration.rb +372 -114
  142. data/lib/active_record/model_schema.rb +128 -38
  143. data/lib/active_record/nested_attributes.rb +71 -32
  144. data/lib/active_record/no_touching.rb +1 -1
  145. data/lib/active_record/null_relation.rb +16 -8
  146. data/lib/active_record/persistence.rb +124 -80
  147. data/lib/active_record/query_cache.rb +15 -18
  148. data/lib/active_record/querying.rb +10 -9
  149. data/lib/active_record/railtie.rb +28 -19
  150. data/lib/active_record/railties/controller_runtime.rb +1 -1
  151. data/lib/active_record/railties/databases.rake +67 -51
  152. data/lib/active_record/readonly_attributes.rb +1 -1
  153. data/lib/active_record/reflection.rb +318 -139
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  155. data/lib/active_record/relation/batches.rb +139 -34
  156. data/lib/active_record/relation/calculations.rb +80 -102
  157. data/lib/active_record/relation/delegation.rb +7 -20
  158. data/lib/active_record/relation/finder_methods.rb +167 -97
  159. data/lib/active_record/relation/from_clause.rb +32 -0
  160. data/lib/active_record/relation/merger.rb +38 -41
  161. data/lib/active_record/relation/predicate_builder/array_handler.rb +12 -16
  162. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  163. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  164. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  165. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  166. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  167. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  168. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  169. data/lib/active_record/relation/predicate_builder.rb +124 -82
  170. data/lib/active_record/relation/query_attribute.rb +19 -0
  171. data/lib/active_record/relation/query_methods.rb +323 -257
  172. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  173. data/lib/active_record/relation/spawn_methods.rb +11 -10
  174. data/lib/active_record/relation/where_clause.rb +174 -0
  175. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  176. data/lib/active_record/relation.rb +176 -115
  177. data/lib/active_record/result.rb +4 -3
  178. data/lib/active_record/runtime_registry.rb +1 -1
  179. data/lib/active_record/sanitization.rb +95 -66
  180. data/lib/active_record/schema.rb +26 -22
  181. data/lib/active_record/schema_dumper.rb +62 -38
  182. data/lib/active_record/schema_migration.rb +11 -17
  183. data/lib/active_record/scoping/default.rb +24 -9
  184. data/lib/active_record/scoping/named.rb +49 -28
  185. data/lib/active_record/scoping.rb +32 -15
  186. data/lib/active_record/secure_token.rb +38 -0
  187. data/lib/active_record/serialization.rb +2 -4
  188. data/lib/active_record/statement_cache.rb +16 -14
  189. data/lib/active_record/store.rb +8 -3
  190. data/lib/active_record/suppressor.rb +58 -0
  191. data/lib/active_record/table_metadata.rb +68 -0
  192. data/lib/active_record/tasks/database_tasks.rb +59 -42
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -26
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +29 -9
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  196. data/lib/active_record/timestamp.rb +20 -9
  197. data/lib/active_record/touch_later.rb +58 -0
  198. data/lib/active_record/transactions.rb +159 -67
  199. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  200. data/lib/active_record/type/date.rb +2 -41
  201. data/lib/active_record/type/date_time.rb +2 -38
  202. data/lib/active_record/type/hash_lookup_type_map.rb +8 -2
  203. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  204. data/lib/active_record/type/internal/timezone.rb +15 -0
  205. data/lib/active_record/type/serialized.rb +21 -14
  206. data/lib/active_record/type/time.rb +10 -16
  207. data/lib/active_record/type/type_map.rb +4 -4
  208. data/lib/active_record/type.rb +66 -17
  209. data/lib/active_record/type_caster/connection.rb +29 -0
  210. data/lib/active_record/type_caster/map.rb +19 -0
  211. data/lib/active_record/type_caster.rb +7 -0
  212. data/lib/active_record/validations/absence.rb +23 -0
  213. data/lib/active_record/validations/associated.rb +10 -3
  214. data/lib/active_record/validations/length.rb +24 -0
  215. data/lib/active_record/validations/presence.rb +11 -12
  216. data/lib/active_record/validations/uniqueness.rb +29 -18
  217. data/lib/active_record/validations.rb +33 -32
  218. data/lib/active_record.rb +9 -2
  219. data/lib/rails/generators/active_record/migration/migration_generator.rb +7 -4
  220. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +8 -6
  221. data/lib/rails/generators/active_record/migration/templates/migration.rb +8 -7
  222. data/lib/rails/generators/active_record/migration.rb +7 -0
  223. data/lib/rails/generators/active_record/model/model_generator.rb +32 -15
  224. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  225. data/lib/rails/generators/active_record/model/templates/model.rb +3 -0
  226. metadata +60 -34
  227. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -491
  228. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +0 -93
  229. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +0 -11
  230. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +0 -21
  231. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +0 -13
  232. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +0 -11
  233. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +0 -11
  234. data/lib/active_record/serializers/xml_serializer.rb +0 -193
  235. data/lib/active_record/type/big_integer.rb +0 -13
  236. data/lib/active_record/type/binary.rb +0 -50
  237. data/lib/active_record/type/boolean.rb +0 -30
  238. data/lib/active_record/type/decimal.rb +0 -40
  239. data/lib/active_record/type/decimal_without_scale.rb +0 -11
  240. data/lib/active_record/type/decorator.rb +0 -14
  241. data/lib/active_record/type/float.rb +0 -19
  242. data/lib/active_record/type/integer.rb +0 -55
  243. data/lib/active_record/type/mutable.rb +0 -16
  244. data/lib/active_record/type/numeric.rb +0 -36
  245. data/lib/active_record/type/string.rb +0 -36
  246. data/lib/active_record/type/text.rb +0 -11
  247. data/lib/active_record/type/time_value.rb +0 -38
  248. data/lib/active_record/type/unsigned_integer.rb +0 -15
  249. data/lib/active_record/type/value.rb +0 -101
@@ -0,0 +1,67 @@
1
+ module ActiveRecord
2
+ module Batches
3
+ class BatchEnumerator
4
+ include Enumerable
5
+
6
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
7
+ @of = of
8
+ @relation = relation
9
+ @start = start
10
+ @finish = finish
11
+ end
12
+
13
+ # Looping through a collection of records from the database (using the
14
+ # +all+ method, for example) is very inefficient since it will try to
15
+ # instantiate all the objects at once.
16
+ #
17
+ # In that case, batch processing methods allow you to work with the
18
+ # records in batches, thereby greatly reducing memory consumption.
19
+ #
20
+ # Person.in_batches.each_record do |person|
21
+ # person.do_awesome_stuff
22
+ # end
23
+ #
24
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
25
+ # person.party_all_night!
26
+ # end
27
+ #
28
+ # If you do not provide a block to #each_record, it will return an Enumerator
29
+ # for chaining with other methods:
30
+ #
31
+ # Person.in_batches.each_record.with_index do |person, index|
32
+ # person.award_trophy(index + 1)
33
+ # end
34
+ def each_record
35
+ return to_enum(:each_record) unless block_given?
36
+
37
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
38
+ relation.records.each { |record| yield record }
39
+ end
40
+ end
41
+
42
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
43
+ #
44
+ # People.in_batches.delete_all
45
+ # People.where('age < 10').in_batches.destroy_all
46
+ # People.in_batches.update_all('age = age + 1')
47
+ [:delete_all, :update_all, :destroy_all].each do |method|
48
+ define_method(method) do |*args, &block|
49
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
50
+ relation.send(method, *args, &block)
51
+ end
52
+ end
53
+ end
54
+
55
+ # Yields an ActiveRecord::Relation object for each batch of records.
56
+ #
57
+ # Person.in_batches.each do |relation|
58
+ # relation.update_all(awesome: true)
59
+ # end
60
+ def each
61
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
62
+ return enum.each { |relation| yield relation } if block_given?
63
+ enum
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,8 +1,12 @@
1
+ require "active_record/relation/batches/batch_enumerator"
2
+
1
3
  module ActiveRecord
2
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."
6
+
3
7
  # Looping through a collection of records from the database
4
- # (using the +all+ method, for example) is very inefficient
5
- # since it will try to instantiate all the objects at once.
8
+ # (using the Scoping::Named::ClassMethods.all method, for example)
9
+ # is very inefficient since it will try to instantiate all the objects at once.
6
10
  #
7
11
  # In that case, batch processing methods allow you to work
8
12
  # with the records in batches, thereby greatly reducing memory consumption.
@@ -27,11 +31,15 @@ module ActiveRecord
27
31
  #
28
32
  # ==== Options
29
33
  # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
30
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
34
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
35
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
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.
38
+ #
31
39
  # This is especially useful if you want multiple workers dealing with
32
40
  # the same processing queue. You can make worker 1 handle all the records
33
41
  # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
34
- # (by setting the +:start+ option on that worker).
42
+ # (by setting the +:start+ and +:finish+ option on each worker).
35
43
  #
36
44
  # # Let's process for a batch of 2000 records, skipping the first 2000 rows
37
45
  # Person.find_each(start: 2000, batch_size: 2000) do |person|
@@ -40,24 +48,25 @@ module ActiveRecord
40
48
  #
41
49
  # NOTE: It's not possible to set the order. That is automatically set to
42
50
  # ascending on the primary key ("id ASC") to make the batch ordering
43
- # work. This also means that this method only works with integer-based
44
- # primary keys.
51
+ # work. This also means that this method only works when the primary key is
52
+ # orderable (e.g. an integer or string).
45
53
  #
46
54
  # NOTE: You can't set the limit either, that's used to control
47
55
  # the batch sizes.
48
- def find_each(options = {})
56
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
49
57
  if block_given?
50
- find_in_batches(options) do |records|
58
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
51
59
  records.each { |record| yield record }
52
60
  end
53
61
  else
54
- enum_for :find_each, options do
55
- options[:start] ? where(table[primary_key].gteq(options[:start])).size : size
62
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
63
+ relation = self
64
+ apply_limits(relation, start, finish).size
56
65
  end
57
66
  end
58
67
  end
59
68
 
60
- # Yields each batch of records that was found by the find +options+ as
69
+ # Yields each batch of records that was found by the find options as
61
70
  # an array.
62
71
  #
63
72
  # Person.where("age > 21").find_in_batches do |group|
@@ -77,11 +86,15 @@ module ActiveRecord
77
86
  #
78
87
  # ==== Options
79
88
  # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
80
- # * <tt>:start</tt> - Specifies the starting point for the batch processing.
89
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
90
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
+ # * <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.
93
+ #
81
94
  # This is especially useful if you want multiple workers dealing with
82
95
  # the same processing queue. You can make worker 1 handle all the records
83
96
  # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
84
- # (by setting the +:start+ option on that worker).
97
+ # (by setting the +:start+ and +:finish+ option on each worker).
85
98
  #
86
99
  # # Let's process the next 2000 records
87
100
  # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
@@ -90,49 +103,141 @@ module ActiveRecord
90
103
  #
91
104
  # NOTE: It's not possible to set the order. That is automatically set to
92
105
  # ascending on the primary key ("id ASC") to make the batch ordering
93
- # work. This also means that this method only works with integer-based
94
- # primary keys.
106
+ # work. This also means that this method only works when the primary key is
107
+ # orderable (e.g. an integer or string).
95
108
  #
96
109
  # NOTE: You can't set the limit either, that's used to control
97
110
  # the batch sizes.
98
- def find_in_batches(options = {})
99
- options.assert_valid_keys(:start, :batch_size)
100
-
111
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
101
112
  relation = self
102
- start = options[:start]
103
- batch_size = options[:batch_size] || 1000
104
-
105
113
  unless block_given?
106
- return to_enum(:find_in_batches, options) do
107
- total = start ? where(table[primary_key].gteq(start)).size : size
114
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
115
+ total = apply_limits(relation, start, finish).size
108
116
  (total - 1).div(batch_size) + 1
109
117
  end
110
118
  end
111
119
 
112
- if logger && (arel.orders.present? || arel.taken.present?)
113
- logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
120
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
121
+ yield batch.to_a
114
122
  end
123
+ end
115
124
 
116
- relation = relation.reorder(batch_order).limit(batch_size)
117
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
125
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
126
+ #
127
+ # Person.where("age > 21").in_batches do |relation|
128
+ # relation.delete_all
129
+ # sleep(10) # Throttle the delete queries
130
+ # end
131
+ #
132
+ # If you do not provide a block to #in_batches, it will return a
133
+ # BatchEnumerator which is enumerable.
134
+ #
135
+ # Person.in_batches.with_index do |relation, batch_index|
136
+ # puts "Processing relation ##{batch_index}"
137
+ # relation.each { |relation| relation.delete_all }
138
+ # end
139
+ #
140
+ # Examples of calling methods on the returned BatchEnumerator object:
141
+ #
142
+ # Person.in_batches.delete_all
143
+ # Person.in_batches.update_all(awesome: true)
144
+ # Person.in_batches.each_record(&:party_all_night!)
145
+ #
146
+ # ==== Options
147
+ # * <tt>:of</tt> - Specifies the size of the batch. Default to 1000.
148
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Default to false.
149
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
150
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
+ # * <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.
153
+ #
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).
160
+ #
161
+ # # Let's process the next 2000 records
162
+ # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
163
+ #
164
+ # An example of calling where query method on the relation:
165
+ #
166
+ # Person.in_batches.each do |relation|
167
+ # relation.update_all('age = age + 1')
168
+ # relation.where('age > 21').update_all(should_party: true)
169
+ # relation.where('age <= 21').delete_all
170
+ # end
171
+ #
172
+ # NOTE: If you are going to iterate through each record, you should call
173
+ # #each_record on the yielded BatchEnumerator:
174
+ #
175
+ # Person.in_batches.each_record(&:party_all_night!)
176
+ #
177
+ # NOTE: It's not possible to set the order. That is automatically set to
178
+ # 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
180
+ # or a string.
181
+ #
182
+ # NOTE: You can't set the limit either, that's used to control the batch
183
+ # sizes.
184
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
185
+ relation = self
186
+ unless block_given?
187
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
188
+ end
189
+
190
+ if arel.orders.present? || arel.taken.present?
191
+ act_on_order_or_limit_ignored(error_on_ignore)
192
+ end
193
+
194
+ relation = relation.reorder(batch_order).limit(of)
195
+ relation = apply_limits(relation, start, finish)
196
+ batch_relation = relation
118
197
 
119
- while records.any?
120
- records_size = records.size
121
- primary_key_offset = records.last.id
122
- raise "Primary key not included in the custom select clause" unless primary_key_offset
198
+ loop do
199
+ if load
200
+ records = batch_relation.records
201
+ ids = records.map(&:id)
202
+ yielded_relation = self.where(primary_key => ids)
203
+ yielded_relation.load_records(records)
204
+ else
205
+ ids = batch_relation.pluck(primary_key)
206
+ yielded_relation = self.where(primary_key => ids)
207
+ end
123
208
 
124
- yield records
209
+ break if ids.empty?
125
210
 
126
- break if records_size < batch_size
211
+ primary_key_offset = ids.last
212
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
127
213
 
128
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
214
+ yield yielded_relation
215
+
216
+ break if ids.length < of
217
+ batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
129
218
  end
130
219
  end
131
220
 
132
221
  private
133
222
 
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
228
+
134
229
  def batch_order
135
230
  "#{quoted_table_name}.#{quoted_primary_key} ASC"
136
231
  end
232
+
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)
235
+
236
+ if raise_error
237
+ raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
+ elsif logger
239
+ logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
240
+ end
241
+ end
137
242
  end
138
243
  end