activerecord 5.2.6 → 6.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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +609 -622
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +4 -2
  7. data/lib/active_record/associations/association.rb +52 -19
  8. data/lib/active_record/associations/association_scope.rb +4 -6
  9. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  11. data/lib/active_record/associations/builder/association.rb +14 -18
  12. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  13. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  14. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  15. data/lib/active_record/associations/builder/has_many.rb +2 -0
  16. data/lib/active_record/associations/builder/has_one.rb +35 -1
  17. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  18. data/lib/active_record/associations/collection_association.rb +6 -21
  19. data/lib/active_record/associations/collection_proxy.rb +12 -15
  20. data/lib/active_record/associations/foreign_association.rb +7 -0
  21. data/lib/active_record/associations/has_many_association.rb +2 -10
  22. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  23. data/lib/active_record/associations/has_one_association.rb +28 -30
  24. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  25. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  26. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  27. data/lib/active_record/associations/join_dependency.rb +24 -28
  28. data/lib/active_record/associations/preloader/association.rb +38 -36
  29. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  30. data/lib/active_record/associations/preloader.rb +40 -32
  31. data/lib/active_record/associations/singular_association.rb +2 -16
  32. data/lib/active_record/associations.rb +19 -14
  33. data/lib/active_record/attribute_assignment.rb +7 -10
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  35. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  36. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  37. data/lib/active_record/attribute_methods/query.rb +2 -3
  38. data/lib/active_record/attribute_methods/read.rb +15 -53
  39. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  41. data/lib/active_record/attribute_methods/write.rb +17 -24
  42. data/lib/active_record/attribute_methods.rb +28 -100
  43. data/lib/active_record/attributes.rb +13 -0
  44. data/lib/active_record/autosave_association.rb +5 -9
  45. data/lib/active_record/base.rb +2 -3
  46. data/lib/active_record/callbacks.rb +5 -19
  47. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +94 -16
  48. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  49. data/lib/active_record/connection_adapters/abstract/database_statements.rb +95 -123
  50. data/lib/active_record/connection_adapters/abstract/query_cache.rb +17 -8
  51. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +180 -47
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +128 -194
  59. data/lib/active_record/connection_adapters/column.rb +17 -13
  60. data/lib/active_record/connection_adapters/connection_specification.rb +52 -42
  61. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  62. data/lib/active_record/connection_adapters/mysql/database_statements.rb +73 -13
  63. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  64. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  65. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  66. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  67. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  68. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  69. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  70. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  71. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +20 -1
  72. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  73. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  76. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  79. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  80. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  81. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  82. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  83. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  84. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  85. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  86. data/lib/active_record/connection_adapters/postgresql_adapter.rb +160 -74
  87. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  88. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  89. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +118 -0
  90. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +125 -141
  93. data/lib/active_record/connection_handling.rb +149 -27
  94. data/lib/active_record/core.rb +100 -60
  95. data/lib/active_record/counter_cache.rb +4 -29
  96. data/lib/active_record/database_configurations/database_config.rb +37 -0
  97. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  98. data/lib/active_record/database_configurations/url_config.rb +79 -0
  99. data/lib/active_record/database_configurations.rb +233 -0
  100. data/lib/active_record/dynamic_matchers.rb +1 -1
  101. data/lib/active_record/enum.rb +37 -7
  102. data/lib/active_record/errors.rb +15 -7
  103. data/lib/active_record/explain.rb +1 -1
  104. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  105. data/lib/active_record/fixture_set/render_context.rb +17 -0
  106. data/lib/active_record/fixture_set/table_row.rb +153 -0
  107. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  108. data/lib/active_record/fixtures.rb +145 -472
  109. data/lib/active_record/gem_version.rb +3 -3
  110. data/lib/active_record/inheritance.rb +13 -3
  111. data/lib/active_record/insert_all.rb +179 -0
  112. data/lib/active_record/integration.rb +68 -16
  113. data/lib/active_record/internal_metadata.rb +10 -2
  114. data/lib/active_record/locking/optimistic.rb +5 -6
  115. data/lib/active_record/locking/pessimistic.rb +3 -3
  116. data/lib/active_record/log_subscriber.rb +7 -26
  117. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  118. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/migration/command_recorder.rb +50 -6
  121. data/lib/active_record/migration/compatibility.rb +76 -49
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/model_schema.rb +30 -9
  124. data/lib/active_record/nested_attributes.rb +2 -2
  125. data/lib/active_record/no_touching.rb +7 -0
  126. data/lib/active_record/persistence.rb +228 -24
  127. data/lib/active_record/query_cache.rb +11 -4
  128. data/lib/active_record/querying.rb +32 -20
  129. data/lib/active_record/railtie.rb +80 -43
  130. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  131. data/lib/active_record/railties/controller_runtime.rb +30 -35
  132. data/lib/active_record/railties/databases.rake +196 -46
  133. data/lib/active_record/reflection.rb +32 -30
  134. data/lib/active_record/relation/batches.rb +13 -10
  135. data/lib/active_record/relation/calculations.rb +53 -47
  136. data/lib/active_record/relation/delegation.rb +26 -43
  137. data/lib/active_record/relation/finder_methods.rb +13 -26
  138. data/lib/active_record/relation/merger.rb +11 -20
  139. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  140. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  141. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  142. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  143. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  144. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  145. data/lib/active_record/relation/predicate_builder.rb +4 -6
  146. data/lib/active_record/relation/query_attribute.rb +13 -8
  147. data/lib/active_record/relation/query_methods.rb +189 -63
  148. data/lib/active_record/relation/spawn_methods.rb +1 -1
  149. data/lib/active_record/relation/where_clause.rb +14 -10
  150. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  151. data/lib/active_record/relation.rb +310 -80
  152. data/lib/active_record/result.rb +30 -11
  153. data/lib/active_record/sanitization.rb +32 -40
  154. data/lib/active_record/schema.rb +2 -11
  155. data/lib/active_record/schema_dumper.rb +22 -7
  156. data/lib/active_record/schema_migration.rb +5 -1
  157. data/lib/active_record/scoping/default.rb +4 -5
  158. data/lib/active_record/scoping/named.rb +19 -15
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/statement_cache.rb +30 -3
  161. data/lib/active_record/store.rb +87 -8
  162. data/lib/active_record/table_metadata.rb +10 -17
  163. data/lib/active_record/tasks/database_tasks.rb +194 -25
  164. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  165. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  166. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  167. data/lib/active_record/test_databases.rb +23 -0
  168. data/lib/active_record/test_fixtures.rb +224 -0
  169. data/lib/active_record/timestamp.rb +39 -25
  170. data/lib/active_record/touch_later.rb +4 -2
  171. data/lib/active_record/transactions.rb +57 -66
  172. data/lib/active_record/translation.rb +1 -1
  173. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  174. data/lib/active_record/type.rb +3 -4
  175. data/lib/active_record/type_caster/connection.rb +15 -14
  176. data/lib/active_record/type_caster/map.rb +1 -4
  177. data/lib/active_record/validations/uniqueness.rb +15 -27
  178. data/lib/active_record/validations.rb +1 -0
  179. data/lib/active_record.rb +9 -2
  180. data/lib/arel/alias_predication.rb +9 -0
  181. data/lib/arel/attributes/attribute.rb +37 -0
  182. data/lib/arel/attributes.rb +22 -0
  183. data/lib/arel/collectors/bind.rb +24 -0
  184. data/lib/arel/collectors/composite.rb +31 -0
  185. data/lib/arel/collectors/plain_string.rb +20 -0
  186. data/lib/arel/collectors/sql_string.rb +20 -0
  187. data/lib/arel/collectors/substitute_binds.rb +28 -0
  188. data/lib/arel/crud.rb +42 -0
  189. data/lib/arel/delete_manager.rb +18 -0
  190. data/lib/arel/errors.rb +9 -0
  191. data/lib/arel/expressions.rb +29 -0
  192. data/lib/arel/factory_methods.rb +49 -0
  193. data/lib/arel/insert_manager.rb +49 -0
  194. data/lib/arel/math.rb +45 -0
  195. data/lib/arel/nodes/and.rb +32 -0
  196. data/lib/arel/nodes/ascending.rb +23 -0
  197. data/lib/arel/nodes/binary.rb +52 -0
  198. data/lib/arel/nodes/bind_param.rb +36 -0
  199. data/lib/arel/nodes/case.rb +55 -0
  200. data/lib/arel/nodes/casted.rb +50 -0
  201. data/lib/arel/nodes/comment.rb +29 -0
  202. data/lib/arel/nodes/count.rb +12 -0
  203. data/lib/arel/nodes/delete_statement.rb +45 -0
  204. data/lib/arel/nodes/descending.rb +23 -0
  205. data/lib/arel/nodes/equality.rb +18 -0
  206. data/lib/arel/nodes/extract.rb +24 -0
  207. data/lib/arel/nodes/false.rb +16 -0
  208. data/lib/arel/nodes/full_outer_join.rb +8 -0
  209. data/lib/arel/nodes/function.rb +44 -0
  210. data/lib/arel/nodes/grouping.rb +8 -0
  211. data/lib/arel/nodes/in.rb +8 -0
  212. data/lib/arel/nodes/infix_operation.rb +80 -0
  213. data/lib/arel/nodes/inner_join.rb +8 -0
  214. data/lib/arel/nodes/insert_statement.rb +37 -0
  215. data/lib/arel/nodes/join_source.rb +20 -0
  216. data/lib/arel/nodes/matches.rb +18 -0
  217. data/lib/arel/nodes/named_function.rb +23 -0
  218. data/lib/arel/nodes/node.rb +50 -0
  219. data/lib/arel/nodes/node_expression.rb +13 -0
  220. data/lib/arel/nodes/outer_join.rb +8 -0
  221. data/lib/arel/nodes/over.rb +15 -0
  222. data/lib/arel/nodes/regexp.rb +16 -0
  223. data/lib/arel/nodes/right_outer_join.rb +8 -0
  224. data/lib/arel/nodes/select_core.rb +67 -0
  225. data/lib/arel/nodes/select_statement.rb +41 -0
  226. data/lib/arel/nodes/sql_literal.rb +16 -0
  227. data/lib/arel/nodes/string_join.rb +11 -0
  228. data/lib/arel/nodes/table_alias.rb +27 -0
  229. data/lib/arel/nodes/terminal.rb +16 -0
  230. data/lib/arel/nodes/true.rb +16 -0
  231. data/lib/arel/nodes/unary.rb +45 -0
  232. data/lib/arel/nodes/unary_operation.rb +20 -0
  233. data/lib/arel/nodes/unqualified_column.rb +22 -0
  234. data/lib/arel/nodes/update_statement.rb +41 -0
  235. data/lib/arel/nodes/values_list.rb +9 -0
  236. data/lib/arel/nodes/window.rb +126 -0
  237. data/lib/arel/nodes/with.rb +11 -0
  238. data/lib/arel/nodes.rb +68 -0
  239. data/lib/arel/order_predications.rb +13 -0
  240. data/lib/arel/predications.rb +257 -0
  241. data/lib/arel/select_manager.rb +271 -0
  242. data/lib/arel/table.rb +110 -0
  243. data/lib/arel/tree_manager.rb +72 -0
  244. data/lib/arel/update_manager.rb +34 -0
  245. data/lib/arel/visitors/depth_first.rb +204 -0
  246. data/lib/arel/visitors/dot.rb +297 -0
  247. data/lib/arel/visitors/ibm_db.rb +34 -0
  248. data/lib/arel/visitors/informix.rb +62 -0
  249. data/lib/arel/visitors/mssql.rb +157 -0
  250. data/lib/arel/visitors/mysql.rb +83 -0
  251. data/lib/arel/visitors/oracle.rb +159 -0
  252. data/lib/arel/visitors/oracle12.rb +66 -0
  253. data/lib/arel/visitors/postgresql.rb +110 -0
  254. data/lib/arel/visitors/sqlite.rb +39 -0
  255. data/lib/arel/visitors/to_sql.rb +889 -0
  256. data/lib/arel/visitors/visitor.rb +46 -0
  257. data/lib/arel/visitors/where_sql.rb +23 -0
  258. data/lib/arel/visitors.rb +20 -0
  259. data/lib/arel/window_predications.rb +9 -0
  260. data/lib/arel.rb +51 -0
  261. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  262. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  263. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  264. data/lib/rails/generators/active_record/migration.rb +14 -1
  265. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  266. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  267. metadata +108 -26
  268. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -4,33 +4,46 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class Association #:nodoc:
7
- attr_reader :preloaded_records
8
-
9
7
  def initialize(klass, owners, reflection, preload_scope)
10
8
  @klass = klass
11
9
  @owners = owners
12
10
  @reflection = reflection
13
11
  @preload_scope = preload_scope
14
12
  @model = owners.first && owners.first.class
15
- @preloaded_records = []
16
13
  end
17
14
 
18
- def run(preloader)
19
- records = load_records do |record|
20
- owner = owners_by_key[convert_key(record[association_key_name])]
21
- association = owner.association(reflection.name)
22
- association.set_inverse_instance(record)
15
+ def run
16
+ if !preload_scope || preload_scope.empty_scope?
17
+ owners.each do |owner|
18
+ associate_records_to_owner(owner, records_by_owner[owner] || [])
19
+ end
20
+ else
21
+ # Custom preload scope is used and
22
+ # the association can not be marked as loaded
23
+ # Loading into a Hash instead
24
+ records_by_owner
23
25
  end
26
+ self
27
+ end
24
28
 
25
- owners.each do |owner|
26
- associate_records_to_owner(owner, records[convert_key(owner[owner_key_name])] || [])
29
+ def records_by_owner
30
+ # owners can be duplicated when a relation has a collection association join
31
+ # #compare_by_identity makes such owners different hash keys
32
+ @records_by_owner ||= preloaded_records.each_with_object({}.compare_by_identity) do |record, result|
33
+ owners_by_key[convert_key(record[association_key_name])].each do |owner|
34
+ (result[owner] ||= []) << record
35
+ end
27
36
  end
28
37
  end
29
38
 
30
- protected
31
- attr_reader :owners, :reflection, :preload_scope, :model, :klass
39
+ def preloaded_records
40
+ return @preloaded_records if defined?(@preloaded_records)
41
+ @preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
42
+ end
32
43
 
33
44
  private
45
+ attr_reader :owners, :reflection, :preload_scope, :model, :klass
46
+
34
47
  # The name of the key on the associated records
35
48
  def association_key_name
36
49
  reflection.join_primary_key(klass)
@@ -43,11 +56,10 @@ module ActiveRecord
43
56
 
44
57
  def associate_records_to_owner(owner, records)
45
58
  association = owner.association(reflection.name)
46
- association.loaded!
47
59
  if reflection.collection?
48
- association.target.concat(records)
60
+ association.target = records
49
61
  else
50
- association.target = records.first unless records.empty?
62
+ association.target = records.first
51
63
  end
52
64
  end
53
65
 
@@ -56,13 +68,10 @@ module ActiveRecord
56
68
  end
57
69
 
58
70
  def owners_by_key
59
- unless defined?(@owners_by_key)
60
- @owners_by_key = owners.each_with_object({}) do |owner, h|
61
- key = convert_key(owner[owner_key_name])
62
- h[key] = owner if key
63
- end
71
+ @owners_by_key ||= owners.each_with_object({}) do |owner, result|
72
+ key = convert_key(owner[owner_key_name])
73
+ (result[key] ||= []) << owner if key
64
74
  end
65
- @owners_by_key
66
75
  end
67
76
 
68
77
  def key_conversion_required?
@@ -89,21 +98,14 @@ module ActiveRecord
89
98
  @model.type_for_attribute(owner_key_name).type
90
99
  end
91
100
 
92
- def load_records(&block)
93
- return {} if owner_keys.empty?
94
- # Some databases impose a limit on the number of ids in a list (in Oracle it's 1000)
95
- # Make several smaller queries if necessary or make one query if the adapter supports it
96
- slices = owner_keys.each_slice(klass.connection.in_clause_length || owner_keys.size)
97
- @preloaded_records = slices.flat_map do |slice|
98
- records_for(slice, &block)
101
+ def records_for(ids)
102
+ scope.where(association_key_name => ids).load do |record|
103
+ # Processing only the first owner
104
+ # because the record is modified but not an owner
105
+ owner = owners_by_key[convert_key(record[association_key_name])].first
106
+ association = owner.association(reflection.name)
107
+ association.set_inverse_instance(record)
99
108
  end
100
- @preloaded_records.group_by do |record|
101
- convert_key(record[association_key_name])
102
- end
103
- end
104
-
105
- def records_for(ids, &block)
106
- scope.where(association_key_name => ids).load(&block)
107
109
  end
108
110
 
109
111
  def scope
@@ -117,7 +119,7 @@ module ActiveRecord
117
119
  def build_scope
118
120
  scope = klass.scope_for_association
119
121
 
120
- if reflection.type
122
+ if reflection.type && !reflection.through_reflection?
121
123
  scope.where!(reflection.type => model.polymorphic_name)
122
124
  end
123
125
 
@@ -4,42 +4,57 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class ThroughAssociation < Association # :nodoc:
7
- def run(preloader)
8
- already_loaded = owners.first.association(through_reflection.name).loaded?
9
- through_scope = through_scope()
10
- reflection_scope = target_reflection_scope
11
- through_preloaders = preloader.preload(owners, through_reflection.name, through_scope)
12
- middle_records = through_preloaders.flat_map(&:preloaded_records)
13
- preloaders = preloader.preload(middle_records, source_reflection.name, reflection_scope)
14
- @preloaded_records = preloaders.flat_map(&:preloaded_records)
15
-
16
- owners.each do |owner|
17
- through_records = Array(owner.association(through_reflection.name).target)
18
- if already_loaded
7
+ PRELOADER = ActiveRecord::Associations::Preloader.new
8
+
9
+ def initialize(*)
10
+ super
11
+ @already_loaded = owners.first.association(through_reflection.name).loaded?
12
+ end
13
+
14
+ def preloaded_records
15
+ @preloaded_records ||= source_preloaders.flat_map(&:preloaded_records)
16
+ end
17
+
18
+ def records_by_owner
19
+ return @records_by_owner if defined?(@records_by_owner)
20
+ source_records_by_owner = source_preloaders.map(&:records_by_owner).reduce(:merge)
21
+ through_records_by_owner = through_preloaders.map(&:records_by_owner).reduce(:merge)
22
+
23
+ @records_by_owner = owners.each_with_object({}) do |owner, result|
24
+ through_records = through_records_by_owner[owner] || []
25
+
26
+ if @already_loaded
19
27
  if source_type = reflection.options[:source_type]
20
28
  through_records = through_records.select do |record|
21
29
  record[reflection.foreign_type] == source_type
22
30
  end
23
31
  end
24
- else
25
- owner.association(through_reflection.name).reset if through_scope
26
- end
27
- result = through_records.flat_map do |record|
28
- association = record.association(source_reflection.name)
29
- target = association.target
30
- association.reset if preload_scope
31
- target
32
32
  end
33
- result.compact!
34
- if reflection_scope
35
- result.sort_by! { |rhs| preload_index[rhs] } if reflection_scope.order_values.any?
36
- result.uniq! if reflection_scope.distinct_value
33
+
34
+ records = through_records.flat_map do |record|
35
+ source_records_by_owner[record]
37
36
  end
38
- associate_records_to_owner(owner, result)
37
+
38
+ records.compact!
39
+ records.sort_by! { |rhs| preload_index[rhs] } if scope.order_values.any?
40
+ records.uniq! if scope.distinct_value
41
+ result[owner] = records
39
42
  end
40
43
  end
41
44
 
42
45
  private
46
+ def source_preloaders
47
+ @source_preloaders ||= PRELOADER.preload(middle_records, source_reflection.name, scope)
48
+ end
49
+
50
+ def middle_records
51
+ through_preloaders.flat_map(&:preloaded_records)
52
+ end
53
+
54
+ def through_preloaders
55
+ @through_preloaders ||= PRELOADER.preload(owners, through_reflection.name, through_scope)
56
+ end
57
+
43
58
  def through_reflection
44
59
  reflection.through_reflection
45
60
  end
@@ -49,8 +64,8 @@ module ActiveRecord
49
64
  end
50
65
 
51
66
  def preload_index
52
- @preload_index ||= @preloaded_records.each_with_object({}).with_index do |(id, result), index|
53
- result[id] = index
67
+ @preload_index ||= preloaded_records.each_with_object({}).with_index do |(record, result), index|
68
+ result[record] = index
54
69
  end
55
70
  end
56
71
 
@@ -58,11 +73,15 @@ module ActiveRecord
58
73
  scope = through_reflection.klass.unscoped
59
74
  options = reflection.options
60
75
 
76
+ values = reflection_scope.values
77
+ if annotations = values[:annotate]
78
+ scope.annotate!(*annotations)
79
+ end
80
+
61
81
  if options[:source_type]
62
82
  scope.where! reflection.foreign_type => options[:source_type]
63
83
  elsif !reflection_scope.where_clause.empty?
64
84
  scope.where_clause = reflection_scope.where_clause
65
- values = reflection_scope.values
66
85
 
67
86
  if includes = values[:includes]
68
87
  scope.includes!(source_reflection.name => includes)
@@ -89,17 +108,7 @@ module ActiveRecord
89
108
  end
90
109
  end
91
110
 
92
- scope unless scope.empty_scope?
93
- end
94
-
95
- def target_reflection_scope
96
- if preload_scope
97
- reflection_scope.merge(preload_scope)
98
- elsif reflection.scope
99
- reflection_scope
100
- else
101
- nil
102
- end
111
+ scope
103
112
  end
104
113
  end
105
114
  end
@@ -88,7 +88,6 @@ module ActiveRecord
88
88
  if records.empty?
89
89
  []
90
90
  else
91
- records.uniq!
92
91
  Array.wrap(associations).flat_map { |association|
93
92
  preloaders_on association, records, preload_scope
94
93
  }
@@ -98,34 +97,34 @@ module ActiveRecord
98
97
  private
99
98
 
100
99
  # Loads all the given data into +records+ for the +association+.
101
- def preloaders_on(association, records, scope)
100
+ def preloaders_on(association, records, scope, polymorphic_parent = false)
102
101
  case association
103
102
  when Hash
104
- preloaders_for_hash(association, records, scope)
105
- when Symbol
106
- preloaders_for_one(association, records, scope)
107
- when String
108
- preloaders_for_one(association.to_sym, records, scope)
103
+ preloaders_for_hash(association, records, scope, polymorphic_parent)
104
+ when Symbol, String
105
+ preloaders_for_one(association, records, scope, polymorphic_parent)
109
106
  else
110
107
  raise ArgumentError, "#{association.inspect} was not recognized for preload"
111
108
  end
112
109
  end
113
110
 
114
- def preloaders_for_hash(association, records, scope)
111
+ def preloaders_for_hash(association, records, scope, polymorphic_parent)
115
112
  association.flat_map { |parent, child|
116
- loaders = preloaders_for_one parent, records, scope
117
-
118
- recs = loaders.flat_map(&:preloaded_records).uniq
119
- loaders.concat Array.wrap(child).flat_map { |assoc|
120
- preloaders_on assoc, recs, scope
121
- }
122
- loaders
113
+ grouped_records(parent, records, polymorphic_parent).flat_map do |reflection, reflection_records|
114
+ loaders = preloaders_for_reflection(reflection, reflection_records, scope)
115
+ recs = loaders.flat_map(&:preloaded_records)
116
+ child_polymorphic_parent = reflection && reflection.options[:polymorphic]
117
+ loaders.concat Array.wrap(child).flat_map { |assoc|
118
+ preloaders_on assoc, recs, scope, child_polymorphic_parent
119
+ }
120
+ loaders
121
+ end
123
122
  }
124
123
  end
125
124
 
126
125
  # Loads all the given data into +records+ for a singular +association+.
127
126
  #
128
- # Functions by instantiating a preloader class such as Preloader::HasManyThrough and
127
+ # Functions by instantiating a preloader class such as Preloader::Association and
129
128
  # call the +run+ method for each passed in class in the +records+ argument.
130
129
  #
131
130
  # Not all records have the same class, so group then preload group on the reflection
@@ -135,24 +134,25 @@ module ActiveRecord
135
134
  # Additionally, polymorphic belongs_to associations can have multiple associated
136
135
  # classes, depending on the polymorphic_type field. So we group by the classes as
137
136
  # well.
138
- def preloaders_for_one(association, records, scope)
139
- grouped_records(association, records).flat_map do |reflection, klasses|
140
- klasses.map do |rhs_klass, rs|
141
- loader = preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope)
142
- loader.run self
143
- loader
137
+ def preloaders_for_one(association, records, scope, polymorphic_parent)
138
+ grouped_records(association, records, polymorphic_parent)
139
+ .flat_map do |reflection, reflection_records|
140
+ preloaders_for_reflection reflection, reflection_records, scope
144
141
  end
142
+ end
143
+
144
+ def preloaders_for_reflection(reflection, records, scope)
145
+ records.group_by { |record| record.association(reflection.name).klass }.map do |rhs_klass, rs|
146
+ preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope).run
145
147
  end
146
148
  end
147
149
 
148
- def grouped_records(association, records)
150
+ def grouped_records(association, records, polymorphic_parent)
149
151
  h = {}
150
152
  records.each do |record|
151
- next unless record
152
- assoc = record.association(association)
153
- next unless assoc.klass
154
- klasses = h[assoc.reflection] ||= {}
155
- (klasses[assoc.klass] ||= []) << record
153
+ reflection = record.class._reflect_on_association(association)
154
+ next if polymorphic_parent && !reflection || !record.association(association).klass
155
+ (h[reflection] ||= []) << record
156
156
  end
157
157
  h
158
158
  end
@@ -163,13 +163,21 @@ module ActiveRecord
163
163
  @reflection = reflection
164
164
  end
165
165
 
166
- def run(preloader); end
166
+ def run
167
+ self
168
+ end
167
169
 
168
170
  def preloaded_records
169
- owners.flat_map { |owner| owner.association(reflection.name).target }
171
+ @preloaded_records ||= records_by_owner.flat_map(&:last)
172
+ end
173
+
174
+ def records_by_owner
175
+ @records_by_owner ||= owners.each_with_object({}) do |owner, result|
176
+ result[owner] = Array(owner.association(reflection.name).target)
177
+ end
170
178
  end
171
179
 
172
- protected
180
+ private
173
181
  attr_reader :owners, :reflection
174
182
  end
175
183
 
@@ -177,7 +185,7 @@ module ActiveRecord
177
185
  # and attach it to a relation. The class returned implements a `run` method
178
186
  # that accepts a preloader.
179
187
  def preloader_for(reflection, owners)
180
- if owners.all? { |o| o.association(reflection.name).loaded? }
188
+ if owners.first.association(reflection.name).loaded?
181
189
  return AlreadyLoaded
182
190
  end
183
191
  reflection.check_preloadable!
@@ -26,7 +26,7 @@ module ActiveRecord
26
26
  # Implements the reload reader method, e.g. foo.reload_bar for
27
27
  # Foo.has_one :bar
28
28
  def force_reload_reader
29
- klass.uncached { reload }
29
+ reload(true)
30
30
  target
31
31
  end
32
32
 
@@ -36,21 +36,7 @@ module ActiveRecord
36
36
  end
37
37
 
38
38
  def find_target
39
- scope = self.scope
40
- return scope.take if skip_statement_cache?(scope)
41
-
42
- conn = klass.connection
43
- sc = reflection.association_scope_cache(conn, owner) do |params|
44
- as = AssociationScope.create { params.bind }
45
- target_scope.merge!(as.scope(self)).limit(1)
46
- end
47
-
48
- binds = AssociationScope.get_bind_values(owner, reflection.chain)
49
- sc.execute(binds, conn) do |record|
50
- set_inverse_instance record
51
- end.first
52
- rescue ::RangeError
53
- nil
39
+ super.first
54
40
  end
55
41
 
56
42
  def replace(record)
@@ -292,13 +292,13 @@ module ActiveRecord
292
292
  #
293
293
  # The project class now has the following methods (and more) to ease the traversal and
294
294
  # manipulation of its relationships:
295
- # * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
296
- # * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
297
- # * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
298
- # <tt>Project#milestones.delete(milestone), Project#milestones.destroy(milestone), Project#milestones.find(milestone_id),</tt>
299
- # <tt>Project#milestones.build, Project#milestones.create</tt>
300
- # * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
301
- # <tt>Project#categories.delete(category1), Project#categories.destroy(category1)</tt>
295
+ # * <tt>Project#portfolio</tt>, <tt>Project#portfolio=(portfolio)</tt>, <tt>Project#reload_portfolio</tt>
296
+ # * <tt>Project#project_manager</tt>, <tt>Project#project_manager=(project_manager)</tt>, <tt>Project#reload_project_manager</tt>
297
+ # * <tt>Project#milestones.empty?</tt>, <tt>Project#milestones.size</tt>, <tt>Project#milestones</tt>, <tt>Project#milestones<<(milestone)</tt>,
298
+ # <tt>Project#milestones.delete(milestone)</tt>, <tt>Project#milestones.destroy(milestone)</tt>, <tt>Project#milestones.find(milestone_id)</tt>,
299
+ # <tt>Project#milestones.build</tt>, <tt>Project#milestones.create</tt>
300
+ # * <tt>Project#categories.empty?</tt>, <tt>Project#categories.size</tt>, <tt>Project#categories</tt>, <tt>Project#categories<<(category1)</tt>,
301
+ # <tt>Project#categories.delete(category1)</tt>, <tt>Project#categories.destroy(category1)</tt>
302
302
  #
303
303
  # === A word of warning
304
304
  #
@@ -703,8 +703,9 @@ module ActiveRecord
703
703
  # #belongs_to associations.
704
704
  #
705
705
  # Extra options on the associations, as defined in the
706
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt> constant, will
707
- # also prevent the association's inverse from being found automatically.
706
+ # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt>
707
+ # constant, or a custom scope, will also prevent the association's inverse
708
+ # from being found automatically.
708
709
  #
709
710
  # The automatic guessing of the inverse association uses a heuristic based
710
711
  # on the name of the class, so it may not work for all associations,
@@ -1293,8 +1294,9 @@ module ActiveRecord
1293
1294
  #
1294
1295
  # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1295
1296
  # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
1296
- # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Callbacks are not executed.
1297
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there are any associated records.
1297
+ # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
1298
+ # on polymorphic associations. Callbacks are not executed.
1299
+ # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there are any associated records.
1298
1300
  # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there are any associated objects.
1299
1301
  #
1300
1302
  # If using with the <tt>:through</tt> option, the association on the join model must be
@@ -1436,8 +1438,9 @@ module ActiveRecord
1436
1438
  #
1437
1439
  # * <tt>:destroy</tt> causes the associated object to also be destroyed
1438
1440
  # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
1439
- # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Callbacks are not executed.
1440
- # * <tt>:restrict_with_exception</tt> causes an exception to be raised if there is an associated record
1441
+ # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
1442
+ # on polymorphic associations. Callbacks are not executed.
1443
+ # * <tt>:restrict_with_exception</tt> causes an <tt>ActiveRecord::DeleteRestrictionError</tt> exception to be raised if there is an associated record
1441
1444
  # * <tt>:restrict_with_error</tt> causes an error to be added to the owner if there is an associated object
1442
1445
  #
1443
1446
  # Note that <tt>:dependent</tt> option is ignored when using <tt>:through</tt> option.
@@ -1524,6 +1527,7 @@ module ActiveRecord
1524
1527
  # Returns the associated object. +nil+ is returned if none is found.
1525
1528
  # [association=(associate)]
1526
1529
  # Assigns the associate object, extracts the primary key, and sets it as the foreign key.
1530
+ # No modification or deletion of existing records takes place.
1527
1531
  # [build_association(attributes = {})]
1528
1532
  # Returns a new object of the associated type that has been instantiated
1529
1533
  # with +attributes+ and linked to this object through a foreign key, but has not yet been saved.
@@ -1581,7 +1585,7 @@ module ActiveRecord
1581
1585
  # association will use "taggable_type" as the default <tt>:foreign_type</tt>.
1582
1586
  # [:primary_key]
1583
1587
  # Specify the method that returns the primary key of associated object used for the association.
1584
- # By default this is id.
1588
+ # By default this is +id+.
1585
1589
  # [:dependent]
1586
1590
  # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
1587
1591
  # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
@@ -1761,6 +1765,7 @@ module ActiveRecord
1761
1765
  # has_and_belongs_to_many :projects, -> { includes(:milestones, :manager) }
1762
1766
  # has_and_belongs_to_many :categories, ->(post) {
1763
1767
  # where("default_category = ?", post.default_category)
1768
+ # }
1764
1769
  #
1765
1770
  # === Extensions
1766
1771
  #
@@ -4,7 +4,6 @@ require "active_model/forbidden_attributes_protection"
4
4
 
5
5
  module ActiveRecord
6
6
  module AttributeAssignment
7
- extend ActiveSupport::Concern
8
7
  include ActiveModel::AttributeAssignment
9
8
 
10
9
  private
@@ -46,16 +45,14 @@ module ActiveRecord
46
45
  def execute_callstack_for_multiparameter_attributes(callstack)
47
46
  errors = []
48
47
  callstack.each do |name, values_with_empty_parameters|
49
- begin
50
- if values_with_empty_parameters.each_value.all?(&:nil?)
51
- values = nil
52
- else
53
- values = values_with_empty_parameters
54
- end
55
- send("#{name}=", values)
56
- rescue => ex
57
- errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
48
+ if values_with_empty_parameters.each_value.all?(&:nil?)
49
+ values = nil
50
+ else
51
+ values = values_with_empty_parameters
58
52
  end
53
+ send("#{name}=", values)
54
+ rescue => ex
55
+ errors << AttributeAssignmentError.new("error on assignment #{values_with_empty_parameters.values.inspect} to #{name} (#{ex.message})", ex, name)
59
56
  end
60
57
  unless errors.empty?
61
58
  error_descriptions = errors.map(&:message).join(",")
@@ -46,6 +46,7 @@ module ActiveRecord
46
46
  # task.read_attribute_before_type_cast('completed_on') # => "2012-10-21"
47
47
  # task.read_attribute_before_type_cast(:completed_on) # => "2012-10-21"
48
48
  def read_attribute_before_type_cast(attr_name)
49
+ sync_with_transaction_state if @transaction_state&.finalized?
49
50
  @attributes[attr_name.to_s].value_before_type_cast
50
51
  end
51
52
 
@@ -60,17 +61,19 @@ module ActiveRecord
60
61
  # task.attributes_before_type_cast
61
62
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
62
63
  def attributes_before_type_cast
64
+ sync_with_transaction_state if @transaction_state&.finalized?
63
65
  @attributes.values_before_type_cast
64
66
  end
65
67
 
66
68
  private
67
69
 
68
- # Handle *_before_type_cast for method_missing.
70
+ # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
69
71
  def attribute_before_type_cast(attribute_name)
70
72
  read_attribute_before_type_cast(attribute_name)
71
73
  end
72
74
 
73
75
  def attribute_came_from_user?(attribute_name)
76
+ sync_with_transaction_state if @transaction_state&.finalized?
74
77
  @attributes[attribute_name].came_from_user?
75
78
  end
76
79
  end