activerecord 3.2.19 → 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 (264) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1715 -604
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +40 -45
  5. data/examples/performance.rb +33 -22
  6. data/examples/simple.rb +3 -4
  7. data/lib/active_record/aggregations.rb +76 -51
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +54 -40
  10. data/lib/active_record/associations/association.rb +76 -56
  11. data/lib/active_record/associations/association_scope.rb +125 -93
  12. data/lib/active_record/associations/belongs_to_association.rb +57 -28
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -2
  14. data/lib/active_record/associations/builder/association.rb +120 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +115 -62
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -53
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +117 -43
  18. data/lib/active_record/associations/builder/has_many.rb +9 -65
  19. data/lib/active_record/associations/builder/has_one.rb +18 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +18 -19
  21. data/lib/active_record/associations/collection_association.rb +268 -186
  22. data/lib/active_record/associations/collection_proxy.rb +1003 -63
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +81 -41
  25. data/lib/active_record/associations/has_many_through_association.rb +76 -55
  26. data/lib/active_record/associations/has_one_association.rb +51 -21
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +83 -108
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +239 -155
  32. data/lib/active_record/associations/preloader/association.rb +97 -62
  33. data/lib/active_record/associations/preloader/collection_association.rb +2 -8
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +0 -8
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +75 -33
  38. data/lib/active_record/associations/preloader.rb +111 -79
  39. data/lib/active_record/associations/singular_association.rb +35 -13
  40. data/lib/active_record/associations/through_association.rb +41 -19
  41. data/lib/active_record/associations.rb +727 -501
  42. data/lib/active_record/attribute/user_provided_default.rb +28 -0
  43. data/lib/active_record/attribute.rb +213 -0
  44. data/lib/active_record/attribute_assignment.rb +32 -162
  45. data/lib/active_record/attribute_decorators.rb +67 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  47. data/lib/active_record/attribute_methods/dirty.rb +101 -61
  48. data/lib/active_record/attribute_methods/primary_key.rb +50 -36
  49. data/lib/active_record/attribute_methods/query.rb +7 -6
  50. data/lib/active_record/attribute_methods/read.rb +56 -117
  51. data/lib/active_record/attribute_methods/serialization.rb +43 -96
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +93 -42
  53. data/lib/active_record/attribute_methods/write.rb +34 -45
  54. data/lib/active_record/attribute_methods.rb +333 -144
  55. data/lib/active_record/attribute_mutation_tracker.rb +70 -0
  56. data/lib/active_record/attribute_set/builder.rb +108 -0
  57. data/lib/active_record/attribute_set.rb +108 -0
  58. data/lib/active_record/attributes.rb +265 -0
  59. data/lib/active_record/autosave_association.rb +285 -223
  60. data/lib/active_record/base.rb +95 -490
  61. data/lib/active_record/callbacks.rb +95 -61
  62. data/lib/active_record/coders/json.rb +13 -0
  63. data/lib/active_record/coders/yaml_column.rb +28 -19
  64. data/lib/active_record/collection_cache_key.rb +40 -0
  65. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +724 -277
  66. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  67. data/lib/active_record/connection_adapters/abstract/database_statements.rb +199 -192
  68. data/lib/active_record/connection_adapters/abstract/query_cache.rb +31 -26
  69. data/lib/active_record/connection_adapters/abstract/quoting.rb +140 -57
  70. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +147 -0
  72. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +419 -276
  73. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +105 -0
  74. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +963 -276
  75. data/lib/active_record/connection_adapters/abstract/transaction.rb +232 -0
  76. data/lib/active_record/connection_adapters/abstract_adapter.rb +397 -106
  77. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +643 -342
  78. data/lib/active_record/connection_adapters/column.rb +30 -259
  79. data/lib/active_record/connection_adapters/connection_specification.rb +263 -0
  80. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +22 -0
  81. data/lib/active_record/connection_adapters/mysql/column.rb +50 -0
  82. data/lib/active_record/connection_adapters/mysql/database_statements.rb +125 -0
  83. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +70 -0
  84. data/lib/active_record/connection_adapters/mysql/quoting.rb +51 -0
  85. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +67 -0
  86. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +93 -0
  87. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -0
  88. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +32 -0
  89. data/lib/active_record/connection_adapters/mysql2_adapter.rb +47 -196
  90. data/lib/active_record/connection_adapters/postgresql/column.rb +15 -0
  91. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +170 -0
  92. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +42 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +70 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +48 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +21 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +10 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +39 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/rails_5_1_point.rb +50 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +93 -0
  109. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  110. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  111. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  112. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  113. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  114. data/lib/active_record/connection_adapters/postgresql/oid.rb +31 -0
  115. data/lib/active_record/connection_adapters/postgresql/quoting.rb +116 -0
  116. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +49 -0
  117. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +180 -0
  118. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +47 -0
  119. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +682 -0
  120. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +35 -0
  121. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  122. data/lib/active_record/connection_adapters/postgresql_adapter.rb +558 -1039
  123. data/lib/active_record/connection_adapters/schema_cache.rb +74 -36
  124. data/lib/active_record/connection_adapters/sql_type_metadata.rb +32 -0
  125. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +19 -0
  126. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +48 -0
  127. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  128. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +538 -24
  129. data/lib/active_record/connection_adapters/statement_pool.rb +31 -12
  130. data/lib/active_record/connection_handling.rb +155 -0
  131. data/lib/active_record/core.rb +561 -0
  132. data/lib/active_record/counter_cache.rb +146 -105
  133. data/lib/active_record/dynamic_matchers.rb +101 -64
  134. data/lib/active_record/enum.rb +234 -0
  135. data/lib/active_record/errors.rb +153 -56
  136. data/lib/active_record/explain.rb +15 -63
  137. data/lib/active_record/explain_registry.rb +30 -0
  138. data/lib/active_record/explain_subscriber.rb +10 -6
  139. data/lib/active_record/fixture_set/file.rb +77 -0
  140. data/lib/active_record/fixtures.rb +355 -232
  141. data/lib/active_record/gem_version.rb +15 -0
  142. data/lib/active_record/inheritance.rb +144 -79
  143. data/lib/active_record/integration.rb +66 -13
  144. data/lib/active_record/internal_metadata.rb +56 -0
  145. data/lib/active_record/legacy_yaml_adapter.rb +46 -0
  146. data/lib/active_record/locale/en.yml +9 -1
  147. data/lib/active_record/locking/optimistic.rb +77 -56
  148. data/lib/active_record/locking/pessimistic.rb +6 -6
  149. data/lib/active_record/log_subscriber.rb +53 -28
  150. data/lib/active_record/migration/command_recorder.rb +166 -33
  151. data/lib/active_record/migration/compatibility.rb +126 -0
  152. data/lib/active_record/migration/join_table.rb +15 -0
  153. data/lib/active_record/migration.rb +792 -264
  154. data/lib/active_record/model_schema.rb +192 -130
  155. data/lib/active_record/nested_attributes.rb +238 -145
  156. data/lib/active_record/no_touching.rb +52 -0
  157. data/lib/active_record/null_relation.rb +89 -0
  158. data/lib/active_record/persistence.rb +357 -157
  159. data/lib/active_record/query_cache.rb +22 -43
  160. data/lib/active_record/querying.rb +34 -23
  161. data/lib/active_record/railtie.rb +88 -48
  162. data/lib/active_record/railties/console_sandbox.rb +3 -4
  163. data/lib/active_record/railties/controller_runtime.rb +5 -4
  164. data/lib/active_record/railties/databases.rake +170 -422
  165. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  166. data/lib/active_record/readonly_attributes.rb +2 -5
  167. data/lib/active_record/reflection.rb +715 -189
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +67 -0
  169. data/lib/active_record/relation/batches.rb +203 -50
  170. data/lib/active_record/relation/calculations.rb +203 -194
  171. data/lib/active_record/relation/delegation.rb +103 -25
  172. data/lib/active_record/relation/finder_methods.rb +457 -261
  173. data/lib/active_record/relation/from_clause.rb +32 -0
  174. data/lib/active_record/relation/merger.rb +167 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +43 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +88 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +17 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +17 -0
  179. data/lib/active_record/relation/predicate_builder/class_handler.rb +27 -0
  180. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +57 -0
  181. data/lib/active_record/relation/predicate_builder/range_handler.rb +33 -0
  182. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  183. data/lib/active_record/relation/predicate_builder.rb +153 -48
  184. data/lib/active_record/relation/query_attribute.rb +19 -0
  185. data/lib/active_record/relation/query_methods.rb +1019 -194
  186. data/lib/active_record/relation/record_fetch_warning.rb +49 -0
  187. data/lib/active_record/relation/spawn_methods.rb +46 -150
  188. data/lib/active_record/relation/where_clause.rb +174 -0
  189. data/lib/active_record/relation/where_clause_factory.rb +38 -0
  190. data/lib/active_record/relation.rb +450 -245
  191. data/lib/active_record/result.rb +104 -12
  192. data/lib/active_record/runtime_registry.rb +22 -0
  193. data/lib/active_record/sanitization.rb +120 -94
  194. data/lib/active_record/schema.rb +28 -18
  195. data/lib/active_record/schema_dumper.rb +141 -74
  196. data/lib/active_record/schema_migration.rb +50 -0
  197. data/lib/active_record/scoping/default.rb +64 -57
  198. data/lib/active_record/scoping/named.rb +93 -108
  199. data/lib/active_record/scoping.rb +73 -121
  200. data/lib/active_record/secure_token.rb +38 -0
  201. data/lib/active_record/serialization.rb +7 -5
  202. data/lib/active_record/statement_cache.rb +113 -0
  203. data/lib/active_record/store.rb +173 -15
  204. data/lib/active_record/suppressor.rb +58 -0
  205. data/lib/active_record/table_metadata.rb +68 -0
  206. data/lib/active_record/tasks/database_tasks.rb +313 -0
  207. data/lib/active_record/tasks/mysql_database_tasks.rb +151 -0
  208. data/lib/active_record/tasks/postgresql_database_tasks.rb +110 -0
  209. data/lib/active_record/tasks/sqlite_database_tasks.rb +59 -0
  210. data/lib/active_record/timestamp.rb +42 -24
  211. data/lib/active_record/touch_later.rb +58 -0
  212. data/lib/active_record/transactions.rb +233 -105
  213. data/lib/active_record/type/adapter_specific_registry.rb +130 -0
  214. data/lib/active_record/type/date.rb +7 -0
  215. data/lib/active_record/type/date_time.rb +7 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  217. data/lib/active_record/type/internal/abstract_json.rb +29 -0
  218. data/lib/active_record/type/internal/timezone.rb +15 -0
  219. data/lib/active_record/type/serialized.rb +63 -0
  220. data/lib/active_record/type/time.rb +20 -0
  221. data/lib/active_record/type/type_map.rb +64 -0
  222. data/lib/active_record/type.rb +72 -0
  223. data/lib/active_record/type_caster/connection.rb +29 -0
  224. data/lib/active_record/type_caster/map.rb +19 -0
  225. data/lib/active_record/type_caster.rb +7 -0
  226. data/lib/active_record/validations/absence.rb +23 -0
  227. data/lib/active_record/validations/associated.rb +33 -18
  228. data/lib/active_record/validations/length.rb +24 -0
  229. data/lib/active_record/validations/presence.rb +66 -0
  230. data/lib/active_record/validations/uniqueness.rb +128 -68
  231. data/lib/active_record/validations.rb +48 -40
  232. data/lib/active_record/version.rb +5 -7
  233. data/lib/active_record.rb +71 -47
  234. data/lib/rails/generators/active_record/migration/migration_generator.rb +56 -8
  235. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +24 -0
  236. data/lib/rails/generators/active_record/migration/templates/migration.rb +28 -16
  237. data/lib/rails/generators/active_record/migration.rb +18 -8
  238. data/lib/rails/generators/active_record/model/model_generator.rb +38 -16
  239. data/lib/rails/generators/active_record/model/templates/application_record.rb +5 -0
  240. data/lib/rails/generators/active_record/model/templates/model.rb +7 -6
  241. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  242. data/lib/rails/generators/active_record.rb +3 -11
  243. metadata +188 -134
  244. data/examples/associations.png +0 -0
  245. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  246. data/lib/active_record/associations/join_helper.rb +0 -55
  247. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  248. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  249. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  250. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  251. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  252. data/lib/active_record/dynamic_finder_match.rb +0 -68
  253. data/lib/active_record/dynamic_scope_match.rb +0 -23
  254. data/lib/active_record/fixtures/file.rb +0 -65
  255. data/lib/active_record/identity_map.rb +0 -162
  256. data/lib/active_record/observer.rb +0 -121
  257. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  258. data/lib/active_record/session_store.rb +0 -360
  259. data/lib/active_record/test_case.rb +0 -73
  260. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  261. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  262. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  263. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  264. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -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,90 +1,243 @@
1
- require 'active_support/core_ext/object/blank'
1
+ require "active_record/relation/batches/batch_enumerator"
2
2
 
3
3
  module ActiveRecord
4
4
  module Batches
5
- # Yields each record that was found by the find +options+. The find is
6
- # performed by find_in_batches with a batch size of 1000 (or as
7
- # specified by the <tt>:batch_size</tt> option).
5
+ ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
6
+
7
+ # Looping through a collection of records from the database
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.
10
+ #
11
+ # In that case, batch processing methods allow you to work
12
+ # with the records in batches, thereby greatly reducing memory consumption.
8
13
  #
9
- # Example:
14
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
15
+ # specified by the +:batch_size+ option).
16
+ #
17
+ # Person.find_each do |person|
18
+ # person.do_awesome_stuff
19
+ # end
10
20
  #
11
21
  # Person.where("age > 21").find_each do |person|
12
22
  # person.party_all_night!
13
23
  # end
14
24
  #
15
- # Note: This method is only intended to use for batch processing of
16
- # large amounts of records that wouldn't fit in memory all at once. If
17
- # you just need to loop over less than 1000 records, it's probably
18
- # better just to use the regular find methods.
19
- def find_each(options = {})
20
- find_in_batches(options) do |records|
21
- records.each { |record| yield record }
22
- end
23
- end
24
-
25
- # Yields each batch of records that was found by the find +options+ as
26
- # an array. The size of each batch is set by the <tt>:batch_size</tt>
27
- # option; the default is 1000.
25
+ # If you do not provide a block to #find_each, it will return an Enumerator
26
+ # for chaining with other methods:
27
+ #
28
+ # Person.find_each.with_index do |person, index|
29
+ # person.award_trophy(index + 1)
30
+ # end
28
31
  #
29
- # You can control the starting point for the batch processing by
30
- # supplying the <tt>:start</tt> option. This is especially useful if you
31
- # want multiple workers dealing with the same processing queue. You can
32
- # make worker 1 handle all the records between id 0 and 10,000 and
33
- # worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
34
- # option on that worker).
32
+ # ==== Options
33
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
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.
35
38
  #
36
- # It's not possible to set the order. That is automatically set to
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).
43
+ #
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|
46
+ # person.party_all_night!
47
+ # end
48
+ #
49
+ # NOTE: It's not possible to set the order. That is automatically set to
37
50
  # ascending on the primary key ("id ASC") to make the batch ordering
38
- # work. This also mean that this method only works with integer-based
39
- # primary keys. You can't set the limit either, that's used to control
40
- # the batch sizes.
51
+ # work. This also means that this method only works when the primary key is
52
+ # orderable (e.g. an integer or string).
41
53
  #
42
- # Example:
54
+ # NOTE: You can't set the limit either, that's used to control
55
+ # the batch sizes.
56
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
57
+ if block_given?
58
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
59
+ records.each { |record| yield record }
60
+ end
61
+ else
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
65
+ end
66
+ end
67
+ end
68
+
69
+ # Yields each batch of records that was found by the find options as
70
+ # an array.
43
71
  #
44
72
  # Person.where("age > 21").find_in_batches do |group|
45
73
  # sleep(50) # Make sure it doesn't get too crowded in there!
46
74
  # group.each { |person| person.party_all_night! }
47
75
  # end
48
- def find_in_batches(options = {})
76
+ #
77
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
78
+ # for chaining with other methods:
79
+ #
80
+ # Person.find_in_batches.with_index do |group, batch|
81
+ # puts "Processing group ##{batch}"
82
+ # group.each(&:recover_from_last_night!)
83
+ # end
84
+ #
85
+ # To be yielded each record one by one, use #find_each instead.
86
+ #
87
+ # ==== Options
88
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Default to 1000.
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
+ #
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).
98
+ #
99
+ # # Let's process the next 2000 records
100
+ # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
101
+ # group.each { |person| person.party_all_night! }
102
+ # end
103
+ #
104
+ # NOTE: It's not possible to set the order. That is automatically set to
105
+ # ascending on the primary key ("id ASC") to make the batch ordering
106
+ # work. This also means that this method only works when the primary key is
107
+ # orderable (e.g. an integer or string).
108
+ #
109
+ # NOTE: You can't set the limit either, that's used to control
110
+ # the batch sizes.
111
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
49
112
  relation = self
113
+ unless block_given?
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
116
+ (total - 1).div(batch_size) + 1
117
+ end
118
+ end
50
119
 
51
- unless arel.orders.blank? && arel.taken.blank?
52
- ActiveRecord::Base.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
53
122
  end
123
+ end
54
124
 
55
- if (finder_options = options.except(:start, :batch_size)).present?
56
- raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
57
- raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
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
58
189
 
59
- relation = apply_finder_options(finder_options)
190
+ if arel.orders.present? || arel.taken.present?
191
+ act_on_order_or_limit_ignored(error_on_ignore)
60
192
  end
61
193
 
62
- start = options.delete(:start)
63
- batch_size = options.delete(:batch_size) || 1000
194
+ relation = relation.reorder(batch_order).limit(of)
195
+ relation = apply_limits(relation, start, finish)
196
+ batch_relation = relation
64
197
 
65
- relation = relation.reorder(batch_order).limit(batch_size)
66
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
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
67
208
 
68
- while records.any?
69
- records_size = records.size
70
- primary_key_offset = records.last.id
209
+ break if ids.empty?
71
210
 
72
- yield records
211
+ primary_key_offset = ids.last
212
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
73
213
 
74
- break if records_size < batch_size
214
+ yield yielded_relation
75
215
 
76
- if primary_key_offset
77
- records = relation.where(table[primary_key].gt(primary_key_offset)).to_a
78
- else
79
- raise "Primary key not included in the custom select clause"
80
- end
216
+ break if ids.length < of
217
+ batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
81
218
  end
82
219
  end
83
220
 
84
221
  private
85
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
+
86
229
  def batch_order
87
230
  "#{quoted_table_name}.#{quoted_primary_key} ASC"
88
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
89
242
  end
90
243
  end