activerecord 5.0.7 → 5.1.7

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

Potentially problematic release.


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

Files changed (219) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +657 -2080
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/examples/performance.rb +28 -28
  6. data/examples/simple.rb +3 -3
  7. data/lib/active_record/aggregations.rb +244 -244
  8. data/lib/active_record/association_relation.rb +5 -5
  9. data/lib/active_record/associations/alias_tracker.rb +10 -11
  10. data/lib/active_record/associations/association.rb +23 -5
  11. data/lib/active_record/associations/association_scope.rb +95 -81
  12. data/lib/active_record/associations/belongs_to_association.rb +7 -4
  13. data/lib/active_record/associations/builder/belongs_to.rb +30 -16
  14. data/lib/active_record/associations/builder/collection_association.rb +1 -2
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +27 -27
  16. data/lib/active_record/associations/collection_association.rb +36 -205
  17. data/lib/active_record/associations/collection_proxy.rb +132 -63
  18. data/lib/active_record/associations/has_many_association.rb +10 -19
  19. data/lib/active_record/associations/has_many_through_association.rb +12 -4
  20. data/lib/active_record/associations/has_one_association.rb +24 -28
  21. data/lib/active_record/associations/has_one_through_association.rb +5 -1
  22. data/lib/active_record/associations/join_dependency/join_association.rb +4 -28
  23. data/lib/active_record/associations/join_dependency/join_base.rb +1 -1
  24. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  25. data/lib/active_record/associations/join_dependency.rb +121 -118
  26. data/lib/active_record/associations/preloader/association.rb +64 -64
  27. data/lib/active_record/associations/preloader/belongs_to.rb +0 -2
  28. data/lib/active_record/associations/preloader/collection_association.rb +6 -6
  29. data/lib/active_record/associations/preloader/has_many.rb +0 -2
  30. data/lib/active_record/associations/preloader/singular_association.rb +6 -8
  31. data/lib/active_record/associations/preloader/through_association.rb +41 -41
  32. data/lib/active_record/associations/preloader.rb +94 -94
  33. data/lib/active_record/associations/singular_association.rb +8 -25
  34. data/lib/active_record/associations/through_association.rb +2 -5
  35. data/lib/active_record/associations.rb +1591 -1562
  36. data/lib/active_record/attribute/user_provided_default.rb +4 -2
  37. data/lib/active_record/attribute.rb +98 -71
  38. data/lib/active_record/attribute_assignment.rb +61 -61
  39. data/lib/active_record/attribute_decorators.rb +35 -13
  40. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -7
  41. data/lib/active_record/attribute_methods/dirty.rb +229 -46
  42. data/lib/active_record/attribute_methods/primary_key.rb +74 -73
  43. data/lib/active_record/attribute_methods/read.rb +39 -35
  44. data/lib/active_record/attribute_methods/serialization.rb +7 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +35 -58
  46. data/lib/active_record/attribute_methods/write.rb +30 -33
  47. data/lib/active_record/attribute_methods.rb +56 -65
  48. data/lib/active_record/attribute_mutation_tracker.rb +63 -11
  49. data/lib/active_record/attribute_set/builder.rb +27 -33
  50. data/lib/active_record/attribute_set/yaml_encoder.rb +41 -0
  51. data/lib/active_record/attribute_set.rb +9 -6
  52. data/lib/active_record/attributes.rb +22 -22
  53. data/lib/active_record/autosave_association.rb +18 -13
  54. data/lib/active_record/base.rb +24 -22
  55. data/lib/active_record/callbacks.rb +56 -14
  56. data/lib/active_record/coders/yaml_column.rb +9 -11
  57. data/lib/active_record/collection_cache_key.rb +3 -4
  58. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +330 -284
  59. data/lib/active_record/connection_adapters/abstract/database_limits.rb +1 -3
  60. data/lib/active_record/connection_adapters/abstract/database_statements.rb +39 -37
  61. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -27
  62. data/lib/active_record/connection_adapters/abstract/quoting.rb +62 -51
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +10 -20
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +74 -79
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +53 -41
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +120 -100
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +49 -43
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -135
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +404 -424
  70. data/lib/active_record/connection_adapters/column.rb +26 -4
  71. data/lib/active_record/connection_adapters/connection_specification.rb +128 -118
  72. data/lib/active_record/connection_adapters/mysql/column.rb +6 -31
  73. data/lib/active_record/connection_adapters/mysql/database_statements.rb +36 -49
  74. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +22 -22
  75. data/lib/active_record/connection_adapters/mysql/quoting.rb +6 -12
  76. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +49 -45
  77. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +16 -19
  78. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +54 -28
  79. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +43 -0
  80. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +7 -6
  81. data/lib/active_record/connection_adapters/mysql2_adapter.rb +23 -27
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +32 -53
  83. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +3 -3
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +19 -9
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +5 -3
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +3 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +16 -16
  91. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +0 -10
  92. data/lib/active_record/connection_adapters/postgresql/oid/{rails_5_1_point.rb → legacy_point.rb} +9 -16
  93. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +28 -8
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +32 -30
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +2 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +51 -51
  99. data/lib/active_record/connection_adapters/postgresql/oid.rb +22 -21
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +40 -35
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +37 -24
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +19 -23
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +182 -222
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +6 -4
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +7 -5
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +198 -167
  108. data/lib/active_record/connection_adapters/schema_cache.rb +16 -7
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +3 -3
  110. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +1 -1
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +16 -19
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +1 -8
  113. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +28 -0
  114. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +17 -0
  115. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +32 -0
  116. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +184 -167
  117. data/lib/active_record/connection_adapters/statement_pool.rb +7 -7
  118. data/lib/active_record/connection_handling.rb +14 -26
  119. data/lib/active_record/core.rb +109 -93
  120. data/lib/active_record/counter_cache.rb +60 -13
  121. data/lib/active_record/define_callbacks.rb +20 -0
  122. data/lib/active_record/dynamic_matchers.rb +80 -79
  123. data/lib/active_record/enum.rb +8 -6
  124. data/lib/active_record/errors.rb +64 -15
  125. data/lib/active_record/explain.rb +1 -2
  126. data/lib/active_record/explain_registry.rb +1 -1
  127. data/lib/active_record/explain_subscriber.rb +7 -4
  128. data/lib/active_record/fixture_set/file.rb +11 -8
  129. data/lib/active_record/fixtures.rb +66 -53
  130. data/lib/active_record/gem_version.rb +1 -1
  131. data/lib/active_record/inheritance.rb +93 -79
  132. data/lib/active_record/integration.rb +7 -7
  133. data/lib/active_record/internal_metadata.rb +3 -16
  134. data/lib/active_record/legacy_yaml_adapter.rb +1 -1
  135. data/lib/active_record/locking/optimistic.rb +69 -74
  136. data/lib/active_record/locking/pessimistic.rb +10 -1
  137. data/lib/active_record/log_subscriber.rb +23 -28
  138. data/lib/active_record/migration/command_recorder.rb +94 -94
  139. data/lib/active_record/migration/compatibility.rb +100 -47
  140. data/lib/active_record/migration/join_table.rb +6 -6
  141. data/lib/active_record/migration.rb +153 -155
  142. data/lib/active_record/model_schema.rb +94 -107
  143. data/lib/active_record/nested_attributes.rb +200 -199
  144. data/lib/active_record/null_relation.rb +11 -34
  145. data/lib/active_record/persistence.rb +65 -50
  146. data/lib/active_record/query_cache.rb +2 -6
  147. data/lib/active_record/querying.rb +3 -4
  148. data/lib/active_record/railtie.rb +16 -17
  149. data/lib/active_record/railties/controller_runtime.rb +6 -2
  150. data/lib/active_record/railties/databases.rake +105 -133
  151. data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
  152. data/lib/active_record/readonly_attributes.rb +2 -2
  153. data/lib/active_record/reflection.rb +154 -108
  154. data/lib/active_record/relation/batches/batch_enumerator.rb +1 -1
  155. data/lib/active_record/relation/batches.rb +80 -51
  156. data/lib/active_record/relation/calculations.rb +169 -162
  157. data/lib/active_record/relation/delegation.rb +32 -31
  158. data/lib/active_record/relation/finder_methods.rb +197 -231
  159. data/lib/active_record/relation/merger.rb +58 -62
  160. data/lib/active_record/relation/predicate_builder/array_handler.rb +7 -5
  161. data/lib/active_record/relation/predicate_builder/association_query_handler.rb +23 -23
  162. data/lib/active_record/relation/predicate_builder/base_handler.rb +3 -1
  163. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +0 -8
  164. data/lib/active_record/relation/predicate_builder/polymorphic_array_handler.rb +12 -10
  165. data/lib/active_record/relation/predicate_builder/range_handler.rb +0 -8
  166. data/lib/active_record/relation/predicate_builder.rb +92 -89
  167. data/lib/active_record/relation/query_attribute.rb +1 -1
  168. data/lib/active_record/relation/query_methods.rb +255 -293
  169. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  170. data/lib/active_record/relation/spawn_methods.rb +4 -5
  171. data/lib/active_record/relation/where_clause.rb +80 -65
  172. data/lib/active_record/relation/where_clause_factory.rb +47 -8
  173. data/lib/active_record/relation.rb +93 -119
  174. data/lib/active_record/result.rb +41 -32
  175. data/lib/active_record/runtime_registry.rb +3 -3
  176. data/lib/active_record/sanitization.rb +176 -192
  177. data/lib/active_record/schema.rb +3 -3
  178. data/lib/active_record/schema_dumper.rb +15 -38
  179. data/lib/active_record/schema_migration.rb +8 -4
  180. data/lib/active_record/scoping/default.rb +90 -90
  181. data/lib/active_record/scoping/named.rb +11 -11
  182. data/lib/active_record/scoping.rb +6 -6
  183. data/lib/active_record/secure_token.rb +2 -2
  184. data/lib/active_record/statement_cache.rb +13 -15
  185. data/lib/active_record/store.rb +31 -32
  186. data/lib/active_record/suppressor.rb +2 -1
  187. data/lib/active_record/table_metadata.rb +9 -5
  188. data/lib/active_record/tasks/database_tasks.rb +65 -55
  189. data/lib/active_record/tasks/mysql_database_tasks.rb +76 -73
  190. data/lib/active_record/tasks/postgresql_database_tasks.rb +72 -47
  191. data/lib/active_record/tasks/sqlite_database_tasks.rb +18 -16
  192. data/lib/active_record/timestamp.rb +46 -25
  193. data/lib/active_record/touch_later.rb +1 -2
  194. data/lib/active_record/transactions.rb +97 -109
  195. data/lib/active_record/type/adapter_specific_registry.rb +46 -42
  196. data/lib/active_record/type/decimal_without_scale.rb +13 -0
  197. data/lib/active_record/type/hash_lookup_type_map.rb +3 -3
  198. data/lib/active_record/type/internal/abstract_json.rb +4 -0
  199. data/lib/active_record/type/serialized.rb +14 -8
  200. data/lib/active_record/type/text.rb +9 -0
  201. data/lib/active_record/type/time.rb +0 -1
  202. data/lib/active_record/type/type_map.rb +11 -15
  203. data/lib/active_record/type/unsigned_integer.rb +15 -0
  204. data/lib/active_record/type.rb +17 -13
  205. data/lib/active_record/type_caster/connection.rb +8 -6
  206. data/lib/active_record/type_caster/map.rb +3 -1
  207. data/lib/active_record/type_caster.rb +2 -2
  208. data/lib/active_record/validations/associated.rb +1 -1
  209. data/lib/active_record/validations/presence.rb +2 -2
  210. data/lib/active_record/validations/uniqueness.rb +8 -39
  211. data/lib/active_record/validations.rb +4 -4
  212. data/lib/active_record/version.rb +1 -1
  213. data/lib/active_record.rb +20 -20
  214. data/lib/rails/generators/active_record/migration/migration_generator.rb +37 -34
  215. data/lib/rails/generators/active_record/migration.rb +1 -1
  216. data/lib/rails/generators/active_record/model/model_generator.rb +9 -9
  217. data/lib/rails/generators/active_record.rb +4 -4
  218. metadata +24 -13
  219. data/lib/active_record/relation/predicate_builder/class_handler.rb +0 -27
@@ -1,5 +1,6 @@
1
- require 'thread'
2
- require 'active_support/core_ext/string/filters'
1
+ require "thread"
2
+ require "active_support/core_ext/string/filters"
3
+ require "active_support/deprecation"
3
4
 
4
5
  module ActiveRecord
5
6
  # = Active Record Reflection
@@ -14,18 +15,19 @@ module ActiveRecord
14
15
  end
15
16
 
16
17
  def self.create(macro, name, scope, options, ar)
17
- klass = case macro
18
- when :composed_of
19
- AggregateReflection
20
- when :has_many
21
- HasManyReflection
22
- when :has_one
23
- HasOneReflection
24
- when :belongs_to
25
- BelongsToReflection
26
- else
27
- raise "Unsupported Macro: #{macro}"
28
- end
18
+ klass = \
19
+ case macro
20
+ when :composed_of
21
+ AggregateReflection
22
+ when :has_many
23
+ HasManyReflection
24
+ when :has_one
25
+ HasOneReflection
26
+ when :belongs_to
27
+ BelongsToReflection
28
+ else
29
+ raise "Unsupported Macro: #{macro}"
30
+ end
29
31
 
30
32
  reflection = klass.new(name, scope, options, ar)
31
33
  options[:through] ? ThroughReflection.new(reflection) : reflection
@@ -33,7 +35,8 @@ module ActiveRecord
33
35
 
34
36
  def self.add_reflection(ar, name, reflection)
35
37
  ar.clear_reflections_cache
36
- ar._reflections = ar._reflections.merge(name.to_s => reflection)
38
+ name = name.to_s
39
+ ar._reflections = ar._reflections.except(name).merge!(name => reflection)
37
40
  end
38
41
 
39
42
  def self.add_aggregate_reflection(ar, name, reflection)
@@ -135,8 +138,8 @@ module ActiveRecord
135
138
  # BelongsToReflection
136
139
  # HasAndBelongsToManyReflection
137
140
  # ThroughReflection
138
- # PolymorphicReflection
139
- # RuntimeReflection
141
+ # PolymorphicReflection
142
+ # RuntimeReflection
140
143
  class AbstractReflection # :nodoc:
141
144
  def through_reflection?
142
145
  false
@@ -170,12 +173,44 @@ module ActiveRecord
170
173
 
171
174
  JoinKeys = Struct.new(:key, :foreign_key) # :nodoc:
172
175
 
173
- def join_keys(association_klass)
174
- JoinKeys.new(foreign_key, active_record_primary_key)
176
+ def join_keys
177
+ get_join_keys klass
178
+ end
179
+
180
+ # Returns a list of scopes that should be applied for this Reflection
181
+ # object when querying the database.
182
+ def scopes
183
+ scope ? [scope] : []
184
+ end
185
+
186
+ def scope_chain
187
+ chain.map(&:scopes)
188
+ end
189
+ deprecate :scope_chain
190
+
191
+ def join_scope(table)
192
+ predicate_builder = predicate_builder(table)
193
+ scope_chain_items = join_scopes(table, predicate_builder)
194
+ klass_scope = klass_join_scope(table, predicate_builder)
195
+
196
+ scope_chain_items.inject(klass_scope || scope_chain_items.shift, &:merge!)
197
+ end
198
+
199
+ def join_scopes(table, predicate_builder) # :nodoc:
200
+ if scope
201
+ [build_scope(table, predicate_builder).instance_exec(&scope)]
202
+ else
203
+ []
204
+ end
205
+ end
206
+
207
+ def klass_join_scope(table, predicate_builder) # :nodoc:
208
+ relation = build_scope(table, predicate_builder)
209
+ klass.scope_for_association(relation)
175
210
  end
176
211
 
177
212
  def constraints
178
- scope_chain.flatten
213
+ chain.map(&:scopes).flatten
179
214
  end
180
215
 
181
216
  def counter_cache_column
@@ -247,6 +282,27 @@ module ActiveRecord
247
282
  def chain
248
283
  collect_join_chain
249
284
  end
285
+
286
+ def get_join_keys(association_klass)
287
+ JoinKeys.new(join_pk(association_klass), join_fk)
288
+ end
289
+
290
+ def build_scope(table, predicate_builder = predicate_builder(table))
291
+ Relation.create(klass, table, predicate_builder)
292
+ end
293
+
294
+ private
295
+ def predicate_builder(table)
296
+ PredicateBuilder.new(TableMetadata.new(klass, table))
297
+ end
298
+
299
+ def join_pk(_)
300
+ foreign_key
301
+ end
302
+
303
+ def join_fk
304
+ active_record_primary_key
305
+ end
250
306
  end
251
307
 
252
308
  # Base class for AggregateReflection and AssociationReflection. Objects of
@@ -281,7 +337,6 @@ module ActiveRecord
281
337
  end
282
338
 
283
339
  def autosave=(autosave)
284
- @automatic_inverse_of = false
285
340
  @options[:autosave] = autosave
286
341
  parent_reflection = self.parent_reflection
287
342
  if parent_reflection
@@ -321,8 +376,7 @@ module ActiveRecord
321
376
  end
322
377
  end
323
378
 
324
-
325
- # Holds all the meta-data about an aggregation as it was specified in the
379
+ # Holds all the metadata about an aggregation as it was specified in the
326
380
  # Active Record class.
327
381
  class AggregateReflection < MacroReflection #:nodoc:
328
382
  def mapping
@@ -331,7 +385,7 @@ module ActiveRecord
331
385
  end
332
386
  end
333
387
 
334
- # Holds all the meta-data about an association as it was specified in the
388
+ # Holds all the metadata about an association as it was specified in the
335
389
  # Active Record class.
336
390
  class AssociationReflection < MacroReflection #:nodoc:
337
391
  # Returns the target association's class.
@@ -359,12 +413,22 @@ module ActiveRecord
359
413
 
360
414
  def initialize(name, scope, options, active_record)
361
415
  super
362
- @automatic_inverse_of = nil
363
416
  @type = options[:as] && (options[:foreign_type] || "#{options[:as]}_type")
364
417
  @foreign_type = options[:foreign_type] || "#{name}_type"
365
418
  @constructable = calculate_constructable(macro, options)
366
419
  @association_scope_cache = {}
367
420
  @scope_lock = Mutex.new
421
+
422
+ if options[:class_name] && options[:class_name].class == Class
423
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
424
+ Passing a class to the `class_name` is deprecated and will raise
425
+ an ArgumentError in Rails 5.2. It eagerloads more classes than
426
+ necessary and potentially creates circular dependencies.
427
+
428
+ Please pass the class name as a string:
429
+ `#{macro} :#{name}, class_name: '#{options[:class_name]}'`
430
+ MSG
431
+ end
368
432
  end
369
433
 
370
434
  def association_scope_cache(conn, owner)
@@ -451,12 +515,6 @@ module ActiveRecord
451
515
  false
452
516
  end
453
517
 
454
- # An array of arrays of scopes. Each item in the outside array corresponds to a reflection
455
- # in the #chain.
456
- def scope_chain
457
- scope ? [[scope]] : [[]]
458
- end
459
-
460
518
  def has_scope?
461
519
  scope
462
520
  end
@@ -545,18 +603,16 @@ module ActiveRecord
545
603
 
546
604
  # Attempts to find the inverse association name automatically.
547
605
  # If it cannot find a suitable inverse association name, it returns
548
- # nil.
606
+ # +nil+.
549
607
  def inverse_name
550
- options.fetch(:inverse_of) do
551
- if @automatic_inverse_of == false
552
- nil
553
- else
554
- @automatic_inverse_of ||= automatic_inverse_of
555
- end
608
+ unless defined?(@inverse_name)
609
+ @inverse_name = options.fetch(:inverse_of) { automatic_inverse_of }
556
610
  end
611
+
612
+ @inverse_name
557
613
  end
558
614
 
559
- # returns either false or the inverse association name that it finds.
615
+ # returns either +nil+ or the inverse association name that it finds.
560
616
  def automatic_inverse_of
561
617
  if can_find_inverse_of_automatically?(self)
562
618
  inverse_name = ActiveSupport::Inflector.underscore(options[:as] || active_record.name.demodulize).to_sym
@@ -573,8 +629,6 @@ module ActiveRecord
573
629
  return inverse_name
574
630
  end
575
631
  end
576
-
577
- false
578
632
  end
579
633
 
580
634
  # Checks if the inverse reflection that is returned from the
@@ -683,11 +737,6 @@ module ActiveRecord
683
737
  end
684
738
  end
685
739
 
686
- def join_keys(association_klass)
687
- key = polymorphic? ? association_primary_key(association_klass) : association_primary_key
688
- JoinKeys.new(key, foreign_key)
689
- end
690
-
691
740
  def join_id_for(owner) # :nodoc:
692
741
  owner[foreign_key]
693
742
  end
@@ -697,6 +746,14 @@ module ActiveRecord
697
746
  def calculate_constructable(macro, options)
698
747
  !polymorphic?
699
748
  end
749
+
750
+ def join_fk
751
+ foreign_key
752
+ end
753
+
754
+ def join_pk(klass)
755
+ polymorphic? ? association_primary_key(klass) : association_primary_key
756
+ end
700
757
  end
701
758
 
702
759
  class HasAndBelongsToManyReflection < AssociationReflection # :nodoc:
@@ -711,16 +768,16 @@ module ActiveRecord
711
768
  end
712
769
  end
713
770
 
714
- # Holds all the meta-data about a :through association as it was specified
771
+ # Holds all the metadata about a :through association as it was specified
715
772
  # in the Active Record class.
716
773
  class ThroughReflection < AbstractReflection #:nodoc:
717
774
  attr_reader :delegate_reflection
718
775
  delegate :foreign_key, :foreign_type, :association_foreign_key,
719
- :active_record_primary_key, :type, :to => :source_reflection
776
+ :active_record_primary_key, :type, :get_join_keys, to: :source_reflection
720
777
 
721
778
  def initialize(delegate_reflection)
722
779
  @delegate_reflection = delegate_reflection
723
- @klass = delegate_reflection.options[:anonymous_class]
780
+ @klass = delegate_reflection.options[:anonymous_class]
724
781
  @source_reflection_name = delegate_reflection.options[:source]
725
782
  end
726
783
 
@@ -798,45 +855,16 @@ module ActiveRecord
798
855
  through_reflection.clear_association_scope_cache
799
856
  end
800
857
 
801
- # Consider the following example:
802
- #
803
- # class Person
804
- # has_many :articles
805
- # has_many :comment_tags, through: :articles
806
- # end
807
- #
808
- # class Article
809
- # has_many :comments
810
- # has_many :comment_tags, through: :comments, source: :tags
811
- # end
812
- #
813
- # class Comment
814
- # has_many :tags
815
- # end
816
- #
817
- # There may be scopes on Person.comment_tags, Article.comment_tags and/or Comment.tags,
818
- # but only Comment.tags will be represented in the #chain. So this method creates an array
819
- # of scopes corresponding to the chain.
820
- def scope_chain
821
- @scope_chain ||= begin
822
- scope_chain = source_reflection.scope_chain.map(&:dup)
823
-
824
- # Add to it the scope from this reflection (if any)
825
- scope_chain.first << scope if scope
826
-
827
- through_scope_chain = through_reflection.scope_chain.map(&:dup)
858
+ def scopes
859
+ source_reflection.scopes + super
860
+ end
828
861
 
829
- if options[:source_type]
830
- type = foreign_type
831
- source_type = options[:source_type]
832
- through_scope_chain.first << lambda { |object|
833
- where(type => source_type)
834
- }
835
- end
862
+ def join_scopes(table, predicate_builder) # :nodoc:
863
+ source_reflection.join_scopes(table, predicate_builder) + super
864
+ end
836
865
 
837
- # Recursively fill out the rest of the array from the through reflection
838
- scope_chain + through_scope_chain
839
- end
866
+ def source_type_scope
867
+ through_reflection.klass.where(foreign_type => options[:source_type])
840
868
  end
841
869
 
842
870
  def has_scope?
@@ -845,10 +873,6 @@ module ActiveRecord
845
873
  through_reflection.has_scope?
846
874
  end
847
875
 
848
- def join_keys(association_klass)
849
- source_reflection.join_keys(association_klass)
850
- end
851
-
852
876
  # A through association is nested if there would be more than one join table
853
877
  def nested?
854
878
  source_reflection.through_reflection? || through_reflection.through_reflection?
@@ -891,15 +915,13 @@ module ActiveRecord
891
915
  }
892
916
 
893
917
  if names.length > 1
894
- example_options = options.dup
895
- example_options[:source] = source_reflection_names.first
896
- ActiveSupport::Deprecation.warn \
897
- "Ambiguous source reflection for through association. Please " \
898
- "specify a :source directive on your declaration like:\n" \
899
- "\n" \
900
- " class #{active_record.name} < ActiveRecord::Base\n" \
901
- " #{macro} :#{name}, #{example_options}\n" \
902
- " end"
918
+ raise AmbiguousSourceReflectionForThroughAssociation.new(
919
+ active_record.name,
920
+ macro,
921
+ name,
922
+ options,
923
+ source_reflection_names
924
+ )
903
925
  end
904
926
 
905
927
  @source_reflection_name = names.first
@@ -946,6 +968,14 @@ module ActiveRecord
946
968
  raise HasOneThroughCantAssociateThroughCollection.new(active_record.name, self, through_reflection)
947
969
  end
948
970
 
971
+ if parent_reflection.nil?
972
+ reflections = active_record.reflections.keys.map(&:to_sym)
973
+
974
+ if reflections.index(through_reflection.name) > reflections.index(name)
975
+ raise HasManyThroughOrderError.new(active_record.name, self, through_reflection)
976
+ end
977
+ end
978
+
949
979
  check_validity_of_inverse!
950
980
  end
951
981
 
@@ -976,7 +1006,7 @@ module ActiveRecord
976
1006
  end
977
1007
  end
978
1008
 
979
- protected
1009
+ private
980
1010
 
981
1011
  def actual_source_reflection # FIXME: this is a horrible name
982
1012
  source_reflection.send(:actual_source_reflection)
@@ -988,7 +1018,6 @@ module ActiveRecord
988
1018
 
989
1019
  def inverse_name; delegate_reflection.send(:inverse_name); end
990
1020
 
991
- private
992
1021
  def derive_class_name
993
1022
  # get the class_name of the belongs_to association of the through reflection
994
1023
  options[:source_type] || source_reflection.class_name
@@ -998,15 +1027,32 @@ module ActiveRecord
998
1027
  public_instance_methods
999
1028
 
1000
1029
  delegate(*delegate_methods, to: :delegate_reflection)
1001
-
1002
1030
  end
1003
1031
 
1004
- class PolymorphicReflection < ThroughReflection # :nodoc:
1032
+ class PolymorphicReflection < AbstractReflection # :nodoc:
1005
1033
  def initialize(reflection, previous_reflection)
1006
1034
  @reflection = reflection
1007
1035
  @previous_reflection = previous_reflection
1008
1036
  end
1009
1037
 
1038
+ def scopes
1039
+ scopes = @previous_reflection.scopes
1040
+ if @previous_reflection.options[:source_type]
1041
+ scopes + [@previous_reflection.source_type_scope]
1042
+ else
1043
+ scopes
1044
+ end
1045
+ end
1046
+
1047
+ def join_scopes(table, predicate_builder) # :nodoc:
1048
+ scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
1049
+ if @previous_reflection.options[:source_type]
1050
+ scopes + [@previous_reflection.source_type_scope]
1051
+ else
1052
+ scopes
1053
+ end
1054
+ end
1055
+
1010
1056
  def klass
1011
1057
  @reflection.klass
1012
1058
  end
@@ -1023,10 +1069,6 @@ module ActiveRecord
1023
1069
  @reflection.plural_name
1024
1070
  end
1025
1071
 
1026
- def join_keys(association_klass)
1027
- @reflection.join_keys(association_klass)
1028
- end
1029
-
1030
1072
  def type
1031
1073
  @reflection.type
1032
1074
  end
@@ -1040,6 +1082,10 @@ module ActiveRecord
1040
1082
  source_type = @previous_reflection.options[:source_type]
1041
1083
  lambda { |object| where(type => source_type) }
1042
1084
  end
1085
+
1086
+ def get_join_keys(association_klass)
1087
+ @reflection.get_join_keys(association_klass)
1088
+ end
1043
1089
  end
1044
1090
 
1045
1091
  class RuntimeReflection < PolymorphicReflection # :nodoc:
@@ -1071,7 +1117,7 @@ module ActiveRecord
1071
1117
  end
1072
1118
 
1073
1119
  def alias_name
1074
- Arel::Table.new(table_name)
1120
+ Arel::Table.new(table_name, type_caster: klass.type_caster)
1075
1121
  end
1076
1122
 
1077
1123
  def all_includes; yield; end
@@ -7,7 +7,7 @@ module ActiveRecord
7
7
  @of = of
8
8
  @relation = relation
9
9
  @start = start
10
- @finish = finish
10
+ @finish = finish
11
11
  end
12
12
 
13
13
  # Looping through a collection of records from the database (using the
@@ -2,7 +2,7 @@ require "active_record/relation/batches/batch_enumerator"
2
2
 
3
3
  module ActiveRecord
4
4
  module Batches
5
- ORDER_OR_LIMIT_IGNORED_MESSAGE = "Scoped order and limit are ignored, it's forced to be batch order and batch size."
5
+ ORDER_IGNORE_MESSAGE = "Scoped order is ignored, it's forced to be batch order."
6
6
 
7
7
  # Looping through a collection of records from the database
8
8
  # (using the Scoping::Named::ClassMethods.all method, for example)
@@ -34,15 +34,19 @@ module ActiveRecord
34
34
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
35
35
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
36
36
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
37
- # the order and limit have to be ignored due to batching.
37
+ # an order is present in the relation.
38
38
  #
39
- # This is especially useful if you want multiple workers dealing with
40
- # the same processing queue. You can make worker 1 handle all the records
41
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
42
- # (by setting the +:start+ and +:finish+ option on each worker).
39
+ # Limits are honored, and if present there is no requirement for the batch
40
+ # size: it can be less than, equal to, or greater than the limit.
43
41
  #
44
- # # Let's process for a batch of 2000 records, skipping the first 2000 rows
45
- # Person.find_each(start: 2000, batch_size: 2000) do |person|
42
+ # The options +start+ and +finish+ are especially useful if you want
43
+ # multiple workers dealing with the same processing queue. You can make
44
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
45
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
46
+ # option on each worker.
47
+ #
48
+ # # Let's process from record 10_000 on.
49
+ # Person.find_each(start: 10_000) do |person|
46
50
  # person.party_all_night!
47
51
  # end
48
52
  #
@@ -51,8 +55,8 @@ module ActiveRecord
51
55
  # work. This also means that this method only works when the primary key is
52
56
  # orderable (e.g. an integer or string).
53
57
  #
54
- # NOTE: You can't set the limit either, that's used to control
55
- # the batch sizes.
58
+ # NOTE: By its nature, batch processing is subject to race conditions if
59
+ # other processes are modifying the database.
56
60
  def find_each(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
57
61
  if block_given?
58
62
  find_in_batches(start: start, finish: finish, batch_size: batch_size, error_on_ignore: error_on_ignore) do |records|
@@ -89,15 +93,19 @@ module ActiveRecord
89
93
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
90
94
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
91
95
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
92
- # the order and limit have to be ignored due to batching.
96
+ # an order is present in the relation.
97
+ #
98
+ # Limits are honored, and if present there is no requirement for the batch
99
+ # size: it can be less than, equal to, or greater than the limit.
93
100
  #
94
- # This is especially useful if you want multiple workers dealing with
95
- # the same processing queue. You can make worker 1 handle all the records
96
- # between id 0 and 10,000 and worker 2 handle from 10,000 and beyond
97
- # (by setting the +:start+ and +:finish+ option on each worker).
101
+ # The options +start+ and +finish+ are especially useful if you want
102
+ # multiple workers dealing with the same processing queue. You can make
103
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
104
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
105
+ # option on each worker.
98
106
  #
99
- # # Let's process the next 2000 records
100
- # Person.find_in_batches(start: 2000, batch_size: 2000) do |group|
107
+ # # Let's process from record 10_000 on.
108
+ # Person.find_in_batches(start: 10_000) do |group|
101
109
  # group.each { |person| person.party_all_night! }
102
110
  # end
103
111
  #
@@ -106,8 +114,8 @@ module ActiveRecord
106
114
  # work. This also means that this method only works when the primary key is
107
115
  # orderable (e.g. an integer or string).
108
116
  #
109
- # NOTE: You can't set the limit either, that's used to control
110
- # the batch sizes.
117
+ # NOTE: By its nature, batch processing is subject to race conditions if
118
+ # other processes are modifying the database.
111
119
  def find_in_batches(start: nil, finish: nil, batch_size: 1000, error_on_ignore: nil)
112
120
  relation = self
113
121
  unless block_given?
@@ -149,17 +157,19 @@ module ActiveRecord
149
157
  # * <tt>:start</tt> - Specifies the primary key value to start from, inclusive of the value.
150
158
  # * <tt>:finish</tt> - Specifies the primary key value to end at, inclusive of the value.
151
159
  # * <tt>:error_on_ignore</tt> - Overrides the application config to specify if an error should be raised when
152
- # the order and limit have to be ignored due to batching.
160
+ # an order is present in the relation.
153
161
  #
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).
162
+ # Limits are honored, and if present there is no requirement for the batch
163
+ # size, it can be less than, equal, or greater than the limit.
160
164
  #
161
- # # Let's process the next 2000 records
162
- # Person.in_batches(of: 2000, start: 2000).update_all(awesome: true)
165
+ # The options +start+ and +finish+ are especially useful if you want
166
+ # multiple workers dealing with the same processing queue. You can make
167
+ # worker 1 handle all the records between id 1 and 9999 and worker 2
168
+ # handle from 10000 and beyond by setting the +:start+ and +:finish+
169
+ # option on each worker.
170
+ #
171
+ # # Let's process from record 10_000 on.
172
+ # Person.in_batches(start: 10_000).update_all(awesome: true)
163
173
  #
164
174
  # An example of calling where query method on the relation:
165
175
  #
@@ -179,19 +189,25 @@ module ActiveRecord
179
189
  # consistent. Therefore the primary key must be orderable, e.g. an integer
180
190
  # or a string.
181
191
  #
182
- # NOTE: You can't set the limit either, that's used to control the batch
183
- # sizes.
192
+ # NOTE: By its nature, batch processing is subject to race conditions if
193
+ # other processes are modifying the database.
184
194
  def in_batches(of: 1000, start: nil, finish: nil, load: false, error_on_ignore: nil)
185
195
  relation = self
186
196
  unless block_given?
187
197
  return BatchEnumerator.new(of: of, start: start, finish: finish, relation: self)
188
198
  end
189
199
 
190
- if arel.orders.present? || arel.taken.present?
191
- act_on_order_or_limit_ignored(error_on_ignore)
200
+ if arel.orders.present?
201
+ act_on_ignored_order(error_on_ignore)
202
+ end
203
+
204
+ batch_limit = of
205
+ if limit_value
206
+ remaining = limit_value
207
+ batch_limit = remaining if remaining < batch_limit
192
208
  end
193
209
 
194
- relation = relation.reorder(batch_order).limit(of)
210
+ relation = relation.reorder(batch_order).limit(batch_limit)
195
211
  relation = apply_limits(relation, start, finish)
196
212
  batch_relation = relation
197
213
 
@@ -199,11 +215,11 @@ module ActiveRecord
199
215
  if load
200
216
  records = batch_relation.records
201
217
  ids = records.map(&:id)
202
- yielded_relation = self.where(primary_key => ids)
218
+ yielded_relation = where(primary_key => ids)
203
219
  yielded_relation.load_records(records)
204
220
  else
205
221
  ids = batch_relation.pluck(primary_key)
206
- yielded_relation = self.where(primary_key => ids)
222
+ yielded_relation = where(primary_key => ids)
207
223
  end
208
224
 
209
225
  break if ids.empty?
@@ -213,31 +229,44 @@ module ActiveRecord
213
229
 
214
230
  yield yielded_relation
215
231
 
216
- break if ids.length < of
232
+ break if ids.length < batch_limit
233
+
234
+ if limit_value
235
+ remaining -= ids.length
236
+
237
+ if remaining == 0
238
+ # Saves a useless iteration when the limit is a multiple of the
239
+ # batch size.
240
+ break
241
+ elsif remaining < batch_limit
242
+ relation = relation.limit(remaining)
243
+ end
244
+ end
245
+
217
246
  batch_relation = relation.where(arel_attribute(primary_key).gt(primary_key_offset))
218
247
  end
219
248
  end
220
249
 
221
250
  private
222
251
 
223
- def apply_limits(relation, start, finish)
224
- relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
225
- relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
226
- relation
227
- end
252
+ def apply_limits(relation, start, finish)
253
+ relation = relation.where(arel_attribute(primary_key).gteq(start)) if start
254
+ relation = relation.where(arel_attribute(primary_key).lteq(finish)) if finish
255
+ relation
256
+ end
228
257
 
229
- def batch_order
230
- "#{quoted_table_name}.#{quoted_primary_key} ASC"
231
- end
258
+ def batch_order
259
+ "#{quoted_table_name}.#{quoted_primary_key} ASC"
260
+ end
232
261
 
233
- def act_on_order_or_limit_ignored(error_on_ignore)
234
- raise_error = (error_on_ignore.nil? ? self.klass.error_on_ignored_order_or_limit : error_on_ignore)
262
+ def act_on_ignored_order(error_on_ignore)
263
+ raise_error = (error_on_ignore.nil? ? klass.error_on_ignored_order : error_on_ignore)
235
264
 
236
- if raise_error
237
- raise ArgumentError.new(ORDER_OR_LIMIT_IGNORED_MESSAGE)
238
- elsif logger
239
- logger.warn(ORDER_OR_LIMIT_IGNORED_MESSAGE)
265
+ if raise_error
266
+ raise ArgumentError.new(ORDER_IGNORE_MESSAGE)
267
+ elsif logger
268
+ logger.warn(ORDER_IGNORE_MESSAGE)
269
+ end
240
270
  end
241
- end
242
271
  end
243
272
  end