activerecord 3.2.22.5 → 5.2.8

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 (275) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -621
  3. data/MIT-LICENSE +2 -2
  4. data/README.rdoc +41 -46
  5. data/examples/performance.rb +55 -42
  6. data/examples/simple.rb +6 -5
  7. data/lib/active_record/aggregations.rb +264 -236
  8. data/lib/active_record/association_relation.rb +40 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -42
  10. data/lib/active_record/associations/association.rb +127 -75
  11. data/lib/active_record/associations/association_scope.rb +126 -92
  12. data/lib/active_record/associations/belongs_to_association.rb +78 -27
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -4
  14. data/lib/active_record/associations/builder/association.rb +117 -32
  15. data/lib/active_record/associations/builder/belongs_to.rb +135 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +61 -54
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +120 -42
  18. data/lib/active_record/associations/builder/has_many.rb +10 -64
  19. data/lib/active_record/associations/builder/has_one.rb +19 -51
  20. data/lib/active_record/associations/builder/singular_association.rb +28 -18
  21. data/lib/active_record/associations/collection_association.rb +226 -293
  22. data/lib/active_record/associations/collection_proxy.rb +1067 -69
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +83 -47
  25. data/lib/active_record/associations/has_many_through_association.rb +98 -65
  26. data/lib/active_record/associations/has_one_association.rb +57 -20
  27. data/lib/active_record/associations/has_one_through_association.rb +18 -9
  28. data/lib/active_record/associations/join_dependency/join_association.rb +48 -126
  29. data/lib/active_record/associations/join_dependency/join_base.rb +11 -12
  30. data/lib/active_record/associations/join_dependency/join_part.rb +35 -42
  31. data/lib/active_record/associations/join_dependency.rb +212 -164
  32. data/lib/active_record/associations/preloader/association.rb +95 -89
  33. data/lib/active_record/associations/preloader/through_association.rb +84 -44
  34. data/lib/active_record/associations/preloader.rb +123 -111
  35. data/lib/active_record/associations/singular_association.rb +33 -24
  36. data/lib/active_record/associations/through_association.rb +60 -26
  37. data/lib/active_record/associations.rb +1759 -1506
  38. data/lib/active_record/attribute_assignment.rb +60 -193
  39. data/lib/active_record/attribute_decorators.rb +90 -0
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +55 -8
  41. data/lib/active_record/attribute_methods/dirty.rb +113 -74
  42. data/lib/active_record/attribute_methods/primary_key.rb +106 -77
  43. data/lib/active_record/attribute_methods/query.rb +8 -5
  44. data/lib/active_record/attribute_methods/read.rb +63 -114
  45. data/lib/active_record/attribute_methods/serialization.rb +60 -90
  46. data/lib/active_record/attribute_methods/time_zone_conversion.rb +69 -43
  47. data/lib/active_record/attribute_methods/write.rb +43 -45
  48. data/lib/active_record/attribute_methods.rb +366 -149
  49. data/lib/active_record/attributes.rb +266 -0
  50. data/lib/active_record/autosave_association.rb +312 -225
  51. data/lib/active_record/base.rb +114 -505
  52. data/lib/active_record/callbacks.rb +145 -67
  53. data/lib/active_record/coders/json.rb +15 -0
  54. data/lib/active_record/coders/yaml_column.rb +32 -23
  55. data/lib/active_record/collection_cache_key.rb +53 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +883 -284
  57. data/lib/active_record/connection_adapters/abstract/database_limits.rb +16 -2
  58. data/lib/active_record/connection_adapters/abstract/database_statements.rb +350 -200
  59. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -27
  60. data/lib/active_record/connection_adapters/abstract/quoting.rb +150 -65
  61. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  62. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +477 -284
  64. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1100 -310
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +450 -118
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +657 -446
  69. data/lib/active_record/connection_adapters/column.rb +50 -255
  70. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  71. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  72. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +59 -210
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  84. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  114. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  115. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  116. data/lib/active_record/connection_adapters/postgresql_adapter.rb +620 -1080
  117. data/lib/active_record/connection_adapters/schema_cache.rb +85 -36
  118. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  119. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  120. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  121. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  125. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +545 -27
  126. data/lib/active_record/connection_adapters/statement_pool.rb +34 -13
  127. data/lib/active_record/connection_handling.rb +145 -0
  128. data/lib/active_record/core.rb +559 -0
  129. data/lib/active_record/counter_cache.rb +200 -105
  130. data/lib/active_record/define_callbacks.rb +22 -0
  131. data/lib/active_record/dynamic_matchers.rb +107 -69
  132. data/lib/active_record/enum.rb +244 -0
  133. data/lib/active_record/errors.rb +245 -60
  134. data/lib/active_record/explain.rb +35 -71
  135. data/lib/active_record/explain_registry.rb +32 -0
  136. data/lib/active_record/explain_subscriber.rb +18 -9
  137. data/lib/active_record/fixture_set/file.rb +82 -0
  138. data/lib/active_record/fixtures.rb +418 -275
  139. data/lib/active_record/gem_version.rb +17 -0
  140. data/lib/active_record/inheritance.rb +209 -100
  141. data/lib/active_record/integration.rb +116 -21
  142. data/lib/active_record/internal_metadata.rb +45 -0
  143. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  144. data/lib/active_record/locale/en.yml +9 -1
  145. data/lib/active_record/locking/optimistic.rb +107 -94
  146. data/lib/active_record/locking/pessimistic.rb +20 -8
  147. data/lib/active_record/log_subscriber.rb +99 -34
  148. data/lib/active_record/migration/command_recorder.rb +199 -64
  149. data/lib/active_record/migration/compatibility.rb +217 -0
  150. data/lib/active_record/migration/join_table.rb +17 -0
  151. data/lib/active_record/migration.rb +893 -296
  152. data/lib/active_record/model_schema.rb +328 -175
  153. data/lib/active_record/nested_attributes.rb +338 -242
  154. data/lib/active_record/no_touching.rb +58 -0
  155. data/lib/active_record/null_relation.rb +68 -0
  156. data/lib/active_record/persistence.rb +557 -170
  157. data/lib/active_record/query_cache.rb +14 -43
  158. data/lib/active_record/querying.rb +36 -24
  159. data/lib/active_record/railtie.rb +147 -52
  160. data/lib/active_record/railties/console_sandbox.rb +5 -4
  161. data/lib/active_record/railties/controller_runtime.rb +13 -6
  162. data/lib/active_record/railties/databases.rake +206 -488
  163. data/lib/active_record/readonly_attributes.rb +4 -6
  164. data/lib/active_record/reflection.rb +734 -228
  165. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  166. data/lib/active_record/relation/batches.rb +249 -52
  167. data/lib/active_record/relation/calculations.rb +330 -284
  168. data/lib/active_record/relation/delegation.rb +135 -37
  169. data/lib/active_record/relation/finder_methods.rb +450 -287
  170. data/lib/active_record/relation/from_clause.rb +26 -0
  171. data/lib/active_record/relation/merger.rb +193 -0
  172. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  173. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  174. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  179. data/lib/active_record/relation/predicate_builder.rb +132 -43
  180. data/lib/active_record/relation/query_attribute.rb +45 -0
  181. data/lib/active_record/relation/query_methods.rb +1037 -221
  182. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  183. data/lib/active_record/relation/spawn_methods.rb +48 -151
  184. data/lib/active_record/relation/where_clause.rb +186 -0
  185. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  186. data/lib/active_record/relation.rb +451 -359
  187. data/lib/active_record/result.rb +129 -20
  188. data/lib/active_record/runtime_registry.rb +24 -0
  189. data/lib/active_record/sanitization.rb +164 -136
  190. data/lib/active_record/schema.rb +31 -19
  191. data/lib/active_record/schema_dumper.rb +154 -107
  192. data/lib/active_record/schema_migration.rb +56 -0
  193. data/lib/active_record/scoping/default.rb +108 -98
  194. data/lib/active_record/scoping/named.rb +125 -112
  195. data/lib/active_record/scoping.rb +77 -123
  196. data/lib/active_record/secure_token.rb +40 -0
  197. data/lib/active_record/serialization.rb +10 -6
  198. data/lib/active_record/statement_cache.rb +121 -0
  199. data/lib/active_record/store.rb +175 -16
  200. data/lib/active_record/suppressor.rb +61 -0
  201. data/lib/active_record/table_metadata.rb +82 -0
  202. data/lib/active_record/tasks/database_tasks.rb +337 -0
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  206. data/lib/active_record/timestamp.rb +80 -41
  207. data/lib/active_record/touch_later.rb +64 -0
  208. data/lib/active_record/transactions.rb +240 -119
  209. data/lib/active_record/translation.rb +2 -0
  210. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  211. data/lib/active_record/type/date.rb +9 -0
  212. data/lib/active_record/type/date_time.rb +9 -0
  213. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  214. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  215. data/lib/active_record/type/internal/timezone.rb +17 -0
  216. data/lib/active_record/type/json.rb +30 -0
  217. data/lib/active_record/type/serialized.rb +71 -0
  218. data/lib/active_record/type/text.rb +11 -0
  219. data/lib/active_record/type/time.rb +21 -0
  220. data/lib/active_record/type/type_map.rb +62 -0
  221. data/lib/active_record/type/unsigned_integer.rb +17 -0
  222. data/lib/active_record/type.rb +79 -0
  223. data/lib/active_record/type_caster/connection.rb +33 -0
  224. data/lib/active_record/type_caster/map.rb +23 -0
  225. data/lib/active_record/type_caster.rb +9 -0
  226. data/lib/active_record/validations/absence.rb +25 -0
  227. data/lib/active_record/validations/associated.rb +35 -18
  228. data/lib/active_record/validations/length.rb +26 -0
  229. data/lib/active_record/validations/presence.rb +68 -0
  230. data/lib/active_record/validations/uniqueness.rb +133 -75
  231. data/lib/active_record/validations.rb +53 -43
  232. data/lib/active_record/version.rb +7 -7
  233. data/lib/active_record.rb +89 -57
  234. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  235. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  236. data/lib/rails/generators/active_record/migration/migration_generator.rb +61 -8
  237. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  238. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  239. data/lib/rails/generators/active_record/migration.rb +28 -8
  240. data/lib/rails/generators/active_record/model/model_generator.rb +23 -22
  241. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  242. data/lib/rails/generators/active_record/model/templates/{module.rb → module.rb.tt} +1 -1
  243. data/lib/rails/generators/active_record.rb +10 -16
  244. metadata +141 -62
  245. data/examples/associations.png +0 -0
  246. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -63
  247. data/lib/active_record/associations/join_helper.rb +0 -55
  248. data/lib/active_record/associations/preloader/belongs_to.rb +0 -17
  249. data/lib/active_record/associations/preloader/collection_association.rb +0 -24
  250. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  251. data/lib/active_record/associations/preloader/has_many.rb +0 -17
  252. data/lib/active_record/associations/preloader/has_many_through.rb +0 -15
  253. data/lib/active_record/associations/preloader/has_one.rb +0 -23
  254. data/lib/active_record/associations/preloader/has_one_through.rb +0 -9
  255. data/lib/active_record/associations/preloader/singular_association.rb +0 -21
  256. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +0 -32
  257. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -191
  258. data/lib/active_record/connection_adapters/mysql_adapter.rb +0 -441
  259. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -583
  260. data/lib/active_record/dynamic_finder_match.rb +0 -68
  261. data/lib/active_record/dynamic_scope_match.rb +0 -23
  262. data/lib/active_record/fixtures/file.rb +0 -65
  263. data/lib/active_record/identity_map.rb +0 -162
  264. data/lib/active_record/observer.rb +0 -121
  265. data/lib/active_record/railties/jdbcmysql_error.rb +0 -16
  266. data/lib/active_record/serializers/xml_serializer.rb +0 -203
  267. data/lib/active_record/session_store.rb +0 -360
  268. data/lib/active_record/test_case.rb +0 -73
  269. data/lib/rails/generators/active_record/migration/templates/migration.rb +0 -34
  270. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -15
  271. data/lib/rails/generators/active_record/model/templates/model.rb +0 -12
  272. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  273. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  274. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  275. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -12
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Batches
5
+ class BatchEnumerator
6
+ include Enumerable
7
+
8
+ def initialize(of: 1000, start: nil, finish: nil, relation:) #:nodoc:
9
+ @of = of
10
+ @relation = relation
11
+ @start = start
12
+ @finish = finish
13
+ end
14
+
15
+ # Looping through a collection of records from the database (using the
16
+ # +all+ method, for example) is very inefficient since it will try to
17
+ # instantiate all the objects at once.
18
+ #
19
+ # In that case, batch processing methods allow you to work with the
20
+ # records in batches, thereby greatly reducing memory consumption.
21
+ #
22
+ # Person.in_batches.each_record do |person|
23
+ # person.do_awesome_stuff
24
+ # end
25
+ #
26
+ # Person.where("age > 21").in_batches(of: 10).each_record do |person|
27
+ # person.party_all_night!
28
+ # end
29
+ #
30
+ # If you do not provide a block to #each_record, it will return an Enumerator
31
+ # for chaining with other methods:
32
+ #
33
+ # Person.in_batches.each_record.with_index do |person, index|
34
+ # person.award_trophy(index + 1)
35
+ # end
36
+ def each_record
37
+ return to_enum(:each_record) unless block_given?
38
+
39
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: true).each do |relation|
40
+ relation.records.each { |record| yield record }
41
+ end
42
+ end
43
+
44
+ # Delegates #delete_all, #update_all, #destroy_all methods to each batch.
45
+ #
46
+ # People.in_batches.delete_all
47
+ # People.where('age < 10').in_batches.destroy_all
48
+ # People.in_batches.update_all('age = age + 1')
49
+ [:delete_all, :update_all, :destroy_all].each do |method|
50
+ define_method(method) do |*args, &block|
51
+ @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false).each do |relation|
52
+ relation.send(method, *args, &block)
53
+ end
54
+ end
55
+ end
56
+
57
+ # Yields an ActiveRecord::Relation object for each batch of records.
58
+ #
59
+ # Person.in_batches.each do |relation|
60
+ # relation.update_all(awesome: true)
61
+ # end
62
+ def each
63
+ enum = @relation.to_enum(:in_batches, of: @of, start: @start, finish: @finish, load: false)
64
+ return enum.each { |relation| yield relation } if block_given?
65
+ enum
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,90 +1,287 @@
1
- require 'active_support/core_ext/object/blank'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_record/relation/batches/batch_enumerator"
2
4
 
3
5
  module ActiveRecord
4
6
  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).
7
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
8
+
9
+ # Looping through a collection of records from the database
10
+ # (using the Scoping::Named::ClassMethods.all method, for example)
11
+ # is very inefficient since it will try to instantiate all the objects at once.
12
+ #
13
+ # In that case, batch processing methods allow you to work
14
+ # with the records in batches, thereby greatly reducing memory consumption.
8
15
  #
9
- # Example:
16
+ # The #find_each method uses #find_in_batches with a batch size of 1000 (or as
17
+ # specified by the +:batch_size+ option).
18
+ #
19
+ # Person.find_each do |person|
20
+ # person.do_awesome_stuff
21
+ # end
10
22
  #
11
23
  # Person.where("age > 21").find_each do |person|
12
24
  # person.party_all_night!
13
25
  # end
14
26
  #
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.
27
+ # If you do not provide a block to #find_each, it will return an Enumerator
28
+ # for chaining with other methods:
29
+ #
30
+ # Person.find_each.with_index do |person, index|
31
+ # person.award_trophy(index + 1)
32
+ # end
28
33
  #
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).
34
+ # ==== Options
35
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
36
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
37
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
38
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
39
+ # an order is present in the relation.
35
40
  #
36
- # It's not possible to set the order. That is automatically set to
41
+ # Limits are honored, and if present there is no requirement for the batch
42
+ # size: it can be less than, equal to, or greater than the limit.
43
+ #
44
+ # The options +start+ and +finish+ are especially useful if you want
45
+ # multiple workers dealing with the same processing queue. You can make
46
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
47
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
48
+ # option on each worker.
49
+ #
50
+ # # In worker 1, let's process until 9999 records.
51
+ # Person.find_each(finish: 9_999) do |person|
52
+ # person.party_all_night!
53
+ # end
54
+ #
55
+ # # In worker 2, let's process from record 10_000 and onwards.
56
+ # Person.find_each(start: 10_000) do |person|
57
+ # person.party_all_night!
58
+ # end
59
+ #
60
+ # NOTE: It's not possible to set the order. That is automatically set to
37
61
  # 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.
62
+ # work. This also means that this method only works when the primary key is
63
+ # orderable (e.g. an integer or string).
41
64
  #
42
- # Example:
65
+ # NOTE: By its nature, batch processing is subject to race conditions if
66
+ # other processes are modifying the database.
67
+ def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
68
+ if block_given?
69
+ find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
70
+ records.each { |record| yield record }
71
+ end
72
+ else
73
+ enum_for(:find_each, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
74
+ relation = self
75
+ apply_limits(relation, start, finish).size
76
+ end
77
+ end
78
+ end
79
+
80
+ # Yields each batch of records that was found by the find options as
81
+ # an array.
43
82
  #
44
83
  # Person.where("age > 21").find_in_batches do |group|
45
84
  # sleep(50) # Make sure it doesn't get too crowded in there!
46
85
  # group.each { |person| person.party_all_night! }
47
86
  # end
48
- def find_in_batches(options = {})
87
+ #
88
+ # If you do not provide a block to #find_in_batches, it will return an Enumerator
89
+ # for chaining with other methods:
90
+ #
91
+ # Person.find_in_batches.with_index do |group, batch|
92
+ # puts "Processing group ##{batch}"
93
+ # group.each(&:recover_from_last_night!)
94
+ # end
95
+ #
96
+ # To be yielded each record one by one, use #find_each instead.
97
+ #
98
+ # ==== Options
99
+ # * <tt>:batch_size</tt> - Specifies the size of the batch. Defaults to 1000.
100
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
101
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
102
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
103
+ # an order is present in the relation.
104
+ #
105
+ # Limits are honored, and if present there is no requirement for the batch
106
+ # size: it can be less than, equal to, or greater than the limit.
107
+ #
108
+ # The options +start+ and +finish+ are especially useful if you want
109
+ # multiple workers dealing with the same processing queue. You can make
110
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
111
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
112
+ # option on each worker.
113
+ #
114
+ # # Let's process from record 10_000 on.
115
+ # Person.find_in_batches(start: 10_000) do |group|
116
+ # group.each { |person| person.party_all_night! }
117
+ # end
118
+ #
119
+ # NOTE: It's not possible to set the order. That is automatically set to
120
+ # ascending on the primary key ("id ASC") to make the batch ordering
121
+ # work. This also means that this method only works when the primary key is
122
+ # orderable (e.g. an integer or string).
123
+ #
124
+ # NOTE: By its nature, batch processing is subject to race conditions if
125
+ # other processes are modifying the database.
126
+ def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
49
127
  relation = self
128
+ unless block_given?
129
+ return to_enum(:find_in_batches, start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do
130
+ total = apply_limits(relation, start, finish).size
131
+ (total - 1).div(batch_size) + 1
132
+ end
133
+ end
134
+
135
+ in_batches(of: batch_size, start: start, finish: finish, load: true, error_on_ignore: error_on_ignore) do |batch|
136
+ yield batch.to_a
137
+ end
138
+ end
50
139
 
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")
140
+ # Yields ActiveRecord::Relation objects to work with a batch of records.
141
+ #
142
+ # Person.where("age > 21").in_batches do |relation|
143
+ # relation.delete_all
144
+ # sleep(10) # Throttle the delete queries
145
+ # end
146
+ #
147
+ # If you do not provide a block to #in_batches, it will return a
148
+ # BatchEnumerator which is enumerable.
149
+ #
150
+ # Person.in_batches.each_with_index do |relation, batch_index|
151
+ # puts "Processing relation ##{batch_index}"
152
+ # relation.delete_all
153
+ # end
154
+ #
155
+ # Examples of calling methods on the returned BatchEnumerator object:
156
+ #
157
+ # Person.in_batches.delete_all
158
+ # Person.in_batches.update_all(awesome: true)
159
+ # Person.in_batches.each_record(&:party_all_night!)
160
+ #
161
+ # ==== Options
162
+ # * <tt>:of</tt> - Specifies the size of the batch. Defaults to 1000.
163
+ # * <tt>:load</tt> - Specifies if the relation should be loaded. Defaults to false.
164
+ # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
165
+ # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
166
+ # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
167
+ # an order is present in the relation.
168
+ #
169
+ # Limits are honored, and if present there is no requirement for the batch
170
+ # size, it can be less than, equal, or greater than the limit.
171
+ #
172
+ # The options +start+ and +finish+ are especially useful if you want
173
+ # multiple workers dealing with the same processing queue. You can make
174
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
175
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
176
+ # option on each worker.
177
+ #
178
+ # # Let's process from record 10_000 on.
179
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
180
+ #
181
+ # An example of calling where query method on the relation:
182
+ #
183
+ # Person.in_batches.each do |relation|
184
+ # relation.update_all('age = age + 1')
185
+ # relation.where('age > 21').update_all(should_party: true)
186
+ # relation.where('age <= 21').delete_all
187
+ # end
188
+ #
189
+ # NOTE: If you are going to iterate through each record, you should call
190
+ # #each_record on the yielded BatchEnumerator:
191
+ #
192
+ # Person.in_batches.each_record(&:party_all_night!)
193
+ #
194
+ # NOTE: It's not possible to set the order. That is automatically set to
195
+ # ascending on the primary key ("id ASC") to make the batch ordering
196
+ # consistent. Therefore the primary key must be orderable, e.g. an integer
197
+ # or a string.
198
+ #
199
+ # NOTE: By its nature, batch processing is subject to race conditions if
200
+ # other processes are modifying the database.
201
+ def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
202
+ relation = self
203
+ unless block_given?
204
+ return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
53
205
  end
54
206
 
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?
207
+ if arel.orders.present?
208
+ act_on_ignored_order(error_on_ignore)
209
+ end
58
210
 
59
- relation = apply_finder_options(finder_options)
211
+ batch_limit = of
212
+ if limit_value
213
+ remaining = limit_value
214
+ batch_limit = remaining if remaining < batch_limit
60
215
  end
61
216
 
62
- start = options.delete(:start)
63
- batch_size = options.delete(:batch_size) || 1000
217
+ relation = relation.reorder(batch_order).limit(batch_limit)
218
+ relation = apply_limits(relation, start, finish)
219
+ relation.skip_query_cache! # Retaining the results in the query cache would undermine the point of batching
220
+ batch_relation = relation
221
+
222
+ loop do
223
+ if load
224
+ records = batch_relation.records
225
+ ids = records.map(&:id)
226
+ yielded_relation = where(primary_key => ids)
227
+ yielded_relation.load_records(records)
228
+ else
229
+ ids = batch_relation.pluck(primary_key)
230
+ yielded_relation = where(primary_key => ids)
231
+ end
232
+
233
+ break if ids.empty?
64
234
 
65
- relation = relation.reorder(batch_order).limit(batch_size)
66
- records = start ? relation.where(table[primary_key].gteq(start)).to_a : relation.to_a
235
+ primary_key_offset = ids.last
236
+ raise ArgumentError.new("Primary key not included in the custom select clause") unless primary_key_offset
67
237
 
68
- while records.any?
69
- records_size = records.size
70
- primary_key_offset = records.last.id
238
+ yield yielded_relation
71
239
 
72
- yield records
240
+ break if ids.length < batch_limit
73
241
 
74
- break if records_size < batch_size
242
+ if limit_value
243
+ remaining -= ids.length
75
244
 
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"
245
+ if remaining == 0
246
+ # Saves a useless iteration when the limit is a multiple of the
247
+ # batch size.
248
+ break
249
+ elsif remaining < batch_limit
250
+ relation = relation.limit(remaining)
251
+ end
80
252
  end
253
+
254
+ attr = Relation::QueryAttribute.new(primary_key, primary_key_offset, klass.type_for_attribute(primary_key))
255
+ batch_relation = relation.where(arel_attribute(primary_key).gt(Arel::Nodes::BindParam.new(attr)))
81
256
  end
82
257
  end
83
258
 
84
259
  private
85
260
 
86
- def batch_order
87
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
88
- end
261
+ def apply_limits(relation, start, finish)
262
+ if start
263
+ attr = Relation::QueryAttribute.new(primary_key, start, klass.type_for_attribute(primary_key))
264
+ relation = relation.where(arel_attribute(primary_key).gteq(Arel::Nodes::BindParam.new(attr)))
265
+ end
266
+ if finish
267
+ attr = Relation::QueryAttribute.new(primary_key, finish, klass.type_for_attribute(primary_key))
268
+ relation = relation.where(arel_attribute(primary_key).lteq(Arel::Nodes::BindParam.new(attr)))
269
+ end
270
+ relation
271
+ end
272
+
273
+ def batch_order
274
+ arel_attribute(primary_key).asc
275
+ end
276
+
277
+ def act_on_ignored_order(error_on_ignore)
278
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
279
+
280
+ if raise_error
281
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
282
+ elsif logger
283
+ logger.warn(ORDER_IGNORE_MESSAGE)
284
+ end
285
+ end
89
286
  end
90
287
  end