activerecord 6.0.1 → 6.1.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 +843 -626
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record/aggregations.rb +1 -2
  6. data/lib/active_record/association_relation.rb +18 -17
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +49 -37
  9. data/lib/active_record/associations/association_scope.rb +17 -15
  10. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  12. data/lib/active_record/associations/builder/association.rb +9 -3
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +25 -8
  20. data/lib/active_record/associations/collection_proxy.rb +14 -7
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -3
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  26. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  27. data/lib/active_record/associations/join_dependency.rb +73 -42
  28. data/lib/active_record/associations/preloader/association.rb +51 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +12 -7
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +115 -12
  34. data/lib/active_record/attribute_assignment.rb +10 -9
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  36. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  37. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  38. data/lib/active_record/attribute_methods/query.rb +3 -6
  39. data/lib/active_record/attribute_methods/read.rb +8 -12
  40. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  42. data/lib/active_record/attribute_methods/write.rb +12 -21
  43. data/lib/active_record/attribute_methods.rb +64 -54
  44. data/lib/active_record/attributes.rb +32 -8
  45. data/lib/active_record/autosave_association.rb +56 -41
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +153 -24
  48. data/lib/active_record/coders/yaml_column.rb +1 -2
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +190 -136
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +82 -37
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -8
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -52
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +60 -73
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +136 -111
  62. data/lib/active_record/connection_adapters/column.rb +15 -1
  63. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  64. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  65. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +28 -36
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  75. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +19 -56
  79. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  89. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  91. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  93. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  94. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  98. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  99. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  100. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  101. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  102. data/lib/active_record/connection_adapters/postgresql_adapter.rb +77 -57
  103. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  104. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  105. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +36 -12
  106. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  107. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  108. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  109. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +57 -57
  110. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  111. data/lib/active_record/connection_adapters.rb +50 -0
  112. data/lib/active_record/connection_handling.rb +210 -87
  113. data/lib/active_record/core.rb +229 -65
  114. data/lib/active_record/counter_cache.rb +4 -1
  115. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  116. data/lib/active_record/database_configurations/database_config.rb +52 -9
  117. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  118. data/lib/active_record/database_configurations/url_config.rb +15 -41
  119. data/lib/active_record/database_configurations.rb +124 -85
  120. data/lib/active_record/delegated_type.rb +209 -0
  121. data/lib/active_record/destroy_association_async_job.rb +36 -0
  122. data/lib/active_record/dynamic_matchers.rb +2 -3
  123. data/lib/active_record/enum.rb +40 -16
  124. data/lib/active_record/errors.rb +47 -12
  125. data/lib/active_record/explain.rb +9 -5
  126. data/lib/active_record/explain_subscriber.rb +1 -1
  127. data/lib/active_record/fixture_set/file.rb +10 -17
  128. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  129. data/lib/active_record/fixture_set/render_context.rb +1 -1
  130. data/lib/active_record/fixture_set/table_row.rb +2 -3
  131. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  132. data/lib/active_record/fixtures.rb +54 -11
  133. data/lib/active_record/gem_version.rb +2 -2
  134. data/lib/active_record/inheritance.rb +40 -21
  135. data/lib/active_record/insert_all.rb +38 -9
  136. data/lib/active_record/integration.rb +3 -5
  137. data/lib/active_record/internal_metadata.rb +16 -7
  138. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  139. data/lib/active_record/locking/optimistic.rb +22 -17
  140. data/lib/active_record/locking/pessimistic.rb +6 -2
  141. data/lib/active_record/log_subscriber.rb +27 -9
  142. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  143. data/lib/active_record/middleware/database_selector/resolver.rb +6 -2
  144. data/lib/active_record/middleware/database_selector.rb +4 -2
  145. data/lib/active_record/migration/command_recorder.rb +53 -45
  146. data/lib/active_record/migration/compatibility.rb +70 -20
  147. data/lib/active_record/migration/join_table.rb +0 -1
  148. data/lib/active_record/migration.rb +114 -84
  149. data/lib/active_record/model_schema.rb +117 -15
  150. data/lib/active_record/nested_attributes.rb +2 -5
  151. data/lib/active_record/no_touching.rb +1 -1
  152. data/lib/active_record/null_relation.rb +0 -1
  153. data/lib/active_record/persistence.rb +50 -46
  154. data/lib/active_record/query_cache.rb +15 -5
  155. data/lib/active_record/querying.rb +12 -7
  156. data/lib/active_record/railtie.rb +65 -45
  157. data/lib/active_record/railties/databases.rake +267 -93
  158. data/lib/active_record/readonly_attributes.rb +4 -0
  159. data/lib/active_record/reflection.rb +77 -63
  160. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  161. data/lib/active_record/relation/batches.rb +38 -32
  162. data/lib/active_record/relation/calculations.rb +102 -45
  163. data/lib/active_record/relation/delegation.rb +9 -7
  164. data/lib/active_record/relation/finder_methods.rb +45 -16
  165. data/lib/active_record/relation/from_clause.rb +5 -1
  166. data/lib/active_record/relation/merger.rb +27 -26
  167. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  168. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  169. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  170. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  171. data/lib/active_record/relation/predicate_builder.rb +55 -35
  172. data/lib/active_record/relation/query_methods.rb +335 -187
  173. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  174. data/lib/active_record/relation/spawn_methods.rb +8 -8
  175. data/lib/active_record/relation/where_clause.rb +104 -58
  176. data/lib/active_record/relation.rb +108 -68
  177. data/lib/active_record/result.rb +41 -34
  178. data/lib/active_record/runtime_registry.rb +2 -2
  179. data/lib/active_record/sanitization.rb +6 -17
  180. data/lib/active_record/schema_dumper.rb +34 -4
  181. data/lib/active_record/schema_migration.rb +2 -8
  182. data/lib/active_record/scoping/default.rb +0 -1
  183. data/lib/active_record/scoping/named.rb +7 -18
  184. data/lib/active_record/scoping.rb +0 -1
  185. data/lib/active_record/secure_token.rb +16 -8
  186. data/lib/active_record/serialization.rb +5 -3
  187. data/lib/active_record/signed_id.rb +116 -0
  188. data/lib/active_record/statement_cache.rb +20 -4
  189. data/lib/active_record/store.rb +3 -3
  190. data/lib/active_record/suppressor.rb +2 -2
  191. data/lib/active_record/table_metadata.rb +39 -36
  192. data/lib/active_record/tasks/database_tasks.rb +139 -113
  193. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  194. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  195. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  196. data/lib/active_record/test_databases.rb +5 -4
  197. data/lib/active_record/test_fixtures.rb +38 -16
  198. data/lib/active_record/timestamp.rb +4 -7
  199. data/lib/active_record/touch_later.rb +20 -21
  200. data/lib/active_record/transactions.rb +21 -70
  201. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  202. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  203. data/lib/active_record/type/serialized.rb +6 -3
  204. data/lib/active_record/type/time.rb +10 -0
  205. data/lib/active_record/type/type_map.rb +0 -1
  206. data/lib/active_record/type/unsigned_integer.rb +0 -1
  207. data/lib/active_record/type.rb +8 -2
  208. data/lib/active_record/type_caster/connection.rb +0 -1
  209. data/lib/active_record/type_caster/map.rb +8 -5
  210. data/lib/active_record/validations/associated.rb +1 -2
  211. data/lib/active_record/validations/numericality.rb +35 -0
  212. data/lib/active_record/validations/uniqueness.rb +24 -4
  213. data/lib/active_record/validations.rb +3 -3
  214. data/lib/active_record.rb +7 -13
  215. data/lib/arel/attributes/attribute.rb +4 -0
  216. data/lib/arel/collectors/bind.rb +5 -0
  217. data/lib/arel/collectors/composite.rb +8 -0
  218. data/lib/arel/collectors/sql_string.rb +7 -0
  219. data/lib/arel/collectors/substitute_binds.rb +7 -0
  220. data/lib/arel/nodes/binary.rb +82 -8
  221. data/lib/arel/nodes/bind_param.rb +8 -0
  222. data/lib/arel/nodes/casted.rb +21 -9
  223. data/lib/arel/nodes/equality.rb +6 -9
  224. data/lib/arel/nodes/grouping.rb +3 -0
  225. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  226. data/lib/arel/nodes/in.rb +8 -1
  227. data/lib/arel/nodes/infix_operation.rb +13 -1
  228. data/lib/arel/nodes/join_source.rb +1 -1
  229. data/lib/arel/nodes/node.rb +7 -6
  230. data/lib/arel/nodes/ordering.rb +27 -0
  231. data/lib/arel/nodes/sql_literal.rb +3 -0
  232. data/lib/arel/nodes/table_alias.rb +7 -3
  233. data/lib/arel/nodes/unary.rb +0 -1
  234. data/lib/arel/nodes.rb +3 -1
  235. data/lib/arel/predications.rb +17 -24
  236. data/lib/arel/select_manager.rb +1 -2
  237. data/lib/arel/table.rb +13 -5
  238. data/lib/arel/visitors/dot.rb +14 -3
  239. data/lib/arel/visitors/mysql.rb +11 -1
  240. data/lib/arel/visitors/postgresql.rb +15 -5
  241. data/lib/arel/visitors/sqlite.rb +0 -1
  242. data/lib/arel/visitors/to_sql.rb +89 -79
  243. data/lib/arel/visitors/visitor.rb +0 -1
  244. data/lib/arel/visitors.rb +0 -7
  245. data/lib/arel.rb +5 -9
  246. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  247. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  248. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  249. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  250. data/lib/rails/generators/active_record/migration.rb +6 -2
  251. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  252. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  253. metadata +26 -26
  254. data/lib/active_record/attribute_decorators.rb +0 -90
  255. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  256. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  257. data/lib/active_record/define_callbacks.rb +0 -22
  258. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  259. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  260. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  261. data/lib/arel/attributes.rb +0 -22
  262. data/lib/arel/visitors/depth_first.rb +0 -204
  263. data/lib/arel/visitors/ibm_db.rb +0 -34
  264. data/lib/arel/visitors/informix.rb +0 -62
  265. data/lib/arel/visitors/mssql.rb +0 -157
  266. data/lib/arel/visitors/oracle.rb +0 -159
  267. data/lib/arel/visitors/oracle12.rb +0 -66
  268. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -4,46 +4,62 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class Association #:nodoc:
7
- def initialize(klass, owners, reflection, preload_scope)
7
+ def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
8
8
  @klass = klass
9
- @owners = owners
9
+ @owners = owners.uniq(&:__id__)
10
10
  @reflection = reflection
11
11
  @preload_scope = preload_scope
12
+ @associate = associate_by_default || !preload_scope || preload_scope.empty_scope?
12
13
  @model = owners.first && owners.first.class
13
14
  end
14
15
 
15
16
  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
25
- end
17
+ records = records_by_owner
18
+
19
+ owners.each do |owner|
20
+ associate_records_to_owner(owner, records[owner] || [])
21
+ end if @associate
22
+
26
23
  self
27
24
  end
28
25
 
29
26
  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
36
- end
27
+ load_records unless defined?(@records_by_owner)
28
+
29
+ @records_by_owner
37
30
  end
38
31
 
39
32
  def preloaded_records
40
- return @preloaded_records if defined?(@preloaded_records)
41
- @preloaded_records = owner_keys.empty? ? [] : records_for(owner_keys)
33
+ load_records unless defined?(@preloaded_records)
34
+
35
+ @preloaded_records
42
36
  end
43
37
 
44
38
  private
45
39
  attr_reader :owners, :reflection, :preload_scope, :model, :klass
46
40
 
41
+ def load_records
42
+ # owners can be duplicated when a relation has a collection association join
43
+ # #compare_by_identity makes such owners different hash keys
44
+ @records_by_owner = {}.compare_by_identity
45
+ raw_records = owner_keys.empty? ? [] : records_for(owner_keys)
46
+
47
+ @preloaded_records = raw_records.select do |record|
48
+ assignments = false
49
+
50
+ owners_by_key[convert_key(record[association_key_name])].each do |owner|
51
+ entries = (@records_by_owner[owner] ||= [])
52
+
53
+ if reflection.collection? || entries.empty?
54
+ entries << record
55
+ assignments = true
56
+ end
57
+ end
58
+
59
+ assignments
60
+ end
61
+ end
62
+
47
63
  # The name of the key on the associated records
48
64
  def association_key_name
49
65
  reflection.join_primary_key(klass)
@@ -113,7 +129,9 @@ module ActiveRecord
113
129
  end
114
130
 
115
131
  def reflection_scope
116
- @reflection_scope ||= reflection.scope ? reflection.scope_for(klass.unscoped) : klass.unscoped
132
+ @reflection_scope ||= begin
133
+ reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
134
+ end
117
135
  end
118
136
 
119
137
  def build_scope
@@ -123,9 +141,17 @@ module ActiveRecord
123
141
  scope.where!(reflection.type => model.polymorphic_name)
124
142
  end
125
143
 
126
- scope.merge!(reflection_scope) if reflection.scope
127
- scope.merge!(preload_scope) if preload_scope
128
- scope
144
+ scope.merge!(reflection_scope) unless reflection_scope.empty_scope?
145
+
146
+ if preload_scope && !preload_scope.empty_scope?
147
+ scope.merge!(preload_scope)
148
+ end
149
+
150
+ if preload_scope && preload_scope.strict_loading_value
151
+ scope.strict_loading
152
+ else
153
+ scope
154
+ end
129
155
  end
130
156
  end
131
157
  end
@@ -4,7 +4,7 @@ module ActiveRecord
4
4
  module Associations
5
5
  class Preloader
6
6
  class ThroughAssociation < Association # :nodoc:
7
- PRELOADER = ActiveRecord::Associations::Preloader.new
7
+ PRELOADER = ActiveRecord::Associations::Preloader.new(associate_by_default: false)
8
8
 
9
9
  def initialize(*)
10
10
  super
@@ -90,7 +90,7 @@ module ActiveRecord
90
90
  end
91
91
 
92
92
  if values[:references] && !values[:references].empty?
93
- scope.references!(values[:references])
93
+ scope.references_values |= values[:references]
94
94
  else
95
95
  scope.references!(source_reflection.table_name)
96
96
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
4
+
3
5
  module ActiveRecord
4
6
  module Associations
5
7
  # Implements the details of eager loading of Active Record associations.
@@ -58,7 +60,7 @@ module ActiveRecord
58
60
  # == Parameters
59
61
  # +records+ is an array of ActiveRecord::Base. This array needs not be flat,
60
62
  # i.e. +records+ itself may also contain arrays of records. In any case,
61
- # +preload_associations+ will preload the all associations records by
63
+ # +preload_associations+ will preload all associations records by
62
64
  # flattening +records+.
63
65
  #
64
66
  # +associations+ specifies one or more associations that you want to
@@ -94,8 +96,11 @@ module ActiveRecord
94
96
  end
95
97
  end
96
98
 
97
- private
99
+ def initialize(associate_by_default: true)
100
+ @associate_by_default = associate_by_default
101
+ end
98
102
 
103
+ private
99
104
  # Loads all the given data into +records+ for the +association+.
100
105
  def preloaders_on(association, records, scope, polymorphic_parent = false)
101
106
  case association
@@ -143,7 +148,7 @@ module ActiveRecord
143
148
 
144
149
  def preloaders_for_reflection(reflection, records, scope)
145
150
  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
151
+ preloader_for(reflection, rs).new(rhs_klass, rs, reflection, scope, @associate_by_default).run
147
152
  end
148
153
  end
149
154
 
@@ -158,7 +163,7 @@ module ActiveRecord
158
163
  end
159
164
 
160
165
  class AlreadyLoaded # :nodoc:
161
- def initialize(klass, owners, reflection, preload_scope)
166
+ def initialize(klass, owners, reflection, preload_scope, associate_by_default = true)
162
167
  @owners = owners
163
168
  @reflection = reflection
164
169
  end
@@ -172,8 +177,8 @@ module ActiveRecord
172
177
  end
173
178
 
174
179
  def records_by_owner
175
- @records_by_owner ||= owners.each_with_object({}) do |owner, result|
176
- result[owner] = Array(owner.association(reflection.name).target)
180
+ @records_by_owner ||= owners.index_with do |owner|
181
+ Array(owner.association(reflection.name).target)
177
182
  end
178
183
  end
179
184
 
@@ -185,7 +190,7 @@ module ActiveRecord
185
190
  # and attach it to a relation. The class returned implements a `run` method
186
191
  # that accepts a preloader.
187
192
  def preloader_for(reflection, owners)
188
- if owners.first.association(reflection.name).loaded?
193
+ if owners.all? { |o| o.association(reflection.name).loaded? }
189
194
  return AlreadyLoaded
190
195
  end
191
196
  reflection.check_preloadable!
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  replace(record)
18
18
  end
19
19
 
20
- def build(attributes = {}, &block)
20
+ def build(attributes = nil, &block)
21
21
  record = build_record(attributes, &block)
22
22
  set_new_record(record)
23
23
  record
@@ -32,7 +32,7 @@ module ActiveRecord
32
32
  reflection.chain.drop(1).each do |reflection|
33
33
  relation = reflection.klass.scope_for_association
34
34
  scope.merge!(
35
- relation.except(:select, :create_with, :includes, :preload, :joins, :eager_load)
35
+ relation.except(:select, :create_with, :includes, :preload, :eager_load, :joins, :left_outer_joins)
36
36
  )
37
37
  end
38
38
  scope
@@ -2,38 +2,116 @@
2
2
 
3
3
  require "active_support/core_ext/enumerable"
4
4
  require "active_support/core_ext/string/conversions"
5
- require "active_support/core_ext/module/remove_method"
6
- require "active_record/errors"
7
5
 
8
6
  module ActiveRecord
9
7
  class AssociationNotFoundError < ConfigurationError #:nodoc:
8
+ attr_reader :record, :association_name
10
9
  def initialize(record = nil, association_name = nil)
10
+ @record = record
11
+ @association_name = association_name
11
12
  if record && association_name
12
13
  super("Association named '#{association_name}' was not found on #{record.class.name}; perhaps you misspelled it?")
13
14
  else
14
15
  super("Association was not found.")
15
16
  end
16
17
  end
18
+
19
+ class Correction
20
+ def initialize(error)
21
+ @error = error
22
+ end
23
+
24
+ def corrections
25
+ if @error.association_name
26
+ maybe_these = @error.record.class.reflections.keys
27
+
28
+ maybe_these.sort_by { |n|
29
+ DidYouMean::Jaro.distance(@error.association_name.to_s, n)
30
+ }.reverse.first(4)
31
+ else
32
+ []
33
+ end
34
+ end
35
+ end
36
+
37
+ # We may not have DYM, and DYM might not let us register error handlers
38
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
39
+ DidYouMean.correct_error(self, Correction)
40
+ end
17
41
  end
18
42
 
19
43
  class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
44
+ attr_reader :reflection, :associated_class
20
45
  def initialize(reflection = nil, associated_class = nil)
21
46
  if reflection
47
+ @reflection = reflection
48
+ @associated_class = associated_class.nil? ? reflection.klass : associated_class
22
49
  super("Could not find the inverse association for #{reflection.name} (#{reflection.options[:inverse_of].inspect} in #{associated_class.nil? ? reflection.class_name : associated_class.name})")
23
50
  else
24
51
  super("Could not find the inverse association.")
25
52
  end
26
53
  end
54
+
55
+ class Correction
56
+ def initialize(error)
57
+ @error = error
58
+ end
59
+
60
+ def corrections
61
+ if @error.reflection && @error.associated_class
62
+ maybe_these = @error.associated_class.reflections.keys
63
+
64
+ maybe_these.sort_by { |n|
65
+ DidYouMean::Jaro.distance(@error.reflection.options[:inverse_of].to_s, n)
66
+ }.reverse.first(4)
67
+ else
68
+ []
69
+ end
70
+ end
71
+ end
72
+
73
+ # We may not have DYM, and DYM might not let us register error handlers
74
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
75
+ DidYouMean.correct_error(self, Correction)
76
+ end
27
77
  end
28
78
 
29
79
  class HasManyThroughAssociationNotFoundError < ActiveRecordError #:nodoc:
30
- def initialize(owner_class_name = nil, reflection = nil)
31
- if owner_class_name && reflection
32
- super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class_name}")
80
+ attr_reader :owner_class, :reflection
81
+
82
+ def initialize(owner_class = nil, reflection = nil)
83
+ if owner_class && reflection
84
+ @owner_class = owner_class
85
+ @reflection = reflection
86
+ super("Could not find the association #{reflection.options[:through].inspect} in model #{owner_class.name}")
33
87
  else
34
88
  super("Could not find the association.")
35
89
  end
36
90
  end
91
+
92
+ class Correction
93
+ def initialize(error)
94
+ @error = error
95
+ end
96
+
97
+ def corrections
98
+ if @error.reflection && @error.owner_class
99
+ maybe_these = @error.owner_class.reflections.keys
100
+ maybe_these -= [@error.reflection.name.to_s] # remove failing reflection
101
+
102
+ maybe_these.sort_by { |n|
103
+ DidYouMean::Jaro.distance(@error.reflection.options[:through].to_s, n)
104
+ }.reverse.first(4)
105
+ else
106
+ []
107
+ end
108
+ end
109
+ end
110
+
111
+ # We may not have DYM, and DYM might not let us register error handlers
112
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
113
+ DidYouMean.correct_error(self, Correction)
114
+ end
37
115
  end
38
116
 
39
117
  class HasManyThroughAssociationPolymorphicSourceError < ActiveRecordError #:nodoc:
@@ -702,9 +780,8 @@ module ActiveRecord
702
780
  # inverse detection only works on #has_many, #has_one, and
703
781
  # #belongs_to associations.
704
782
  #
705
- # Extra options on the associations, as defined in the
706
- # <tt>AssociationReflection::INVALID_AUTOMATIC_INVERSE_OPTIONS</tt>
707
- # constant, or a custom scope, will also prevent the association's inverse
783
+ # <tt>:foreign_key</tt> and <tt>:through</tt> options on the associations,
784
+ # or a custom scope, will also prevent the association's inverse
708
785
  # from being found automatically.
709
786
  #
710
787
  # The automatic guessing of the inverse association uses a heuristic based
@@ -1292,7 +1369,9 @@ module ActiveRecord
1292
1369
  # similar callbacks may affect the <tt>:dependent</tt> behavior, and the
1293
1370
  # <tt>:dependent</tt> behavior may affect other callbacks.
1294
1371
  #
1372
+ # * <tt>nil</tt> do nothing (default).
1295
1373
  # * <tt>:destroy</tt> causes all the associated objects to also be destroyed.
1374
+ # * <tt>:destroy_async</tt> destroys all the associated objects in a background job.
1296
1375
  # * <tt>:delete_all</tt> causes all the associated objects to be deleted directly from the database (so callbacks will not be executed).
1297
1376
  # * <tt>:nullify</tt> causes the foreign keys to be set to +NULL+. Polymorphic type will also be nullified
1298
1377
  # on polymorphic associations. Callbacks are not executed.
@@ -1357,6 +1436,11 @@ module ActiveRecord
1357
1436
  # Specifies a module or array of modules that will be extended into the association object returned.
1358
1437
  # Useful for defining methods on associations, especially when they should be shared between multiple
1359
1438
  # association objects.
1439
+ # [:strict_loading]
1440
+ # Enforces strict loading every time the associated record is loaded through this association.
1441
+ # [:ensuring_owner_was]
1442
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1443
+ # associated records to be deleted in a background job.
1360
1444
  #
1361
1445
  # Option examples:
1362
1446
  # has_many :comments, -> { order("posted_on") }
@@ -1367,6 +1451,7 @@ module ActiveRecord
1367
1451
  # has_many :tags, as: :taggable
1368
1452
  # has_many :reports, -> { readonly }
1369
1453
  # has_many :subscribers, through: :subscriptions, source: :user
1454
+ # has_many :comments, strict_loading: true
1370
1455
  def has_many(name, scope = nil, **options, &extension)
1371
1456
  reflection = Builder::HasMany.build(self, name, scope, options, &extension)
1372
1457
  Reflection.add_reflection self, name, reflection
@@ -1436,7 +1521,9 @@ module ActiveRecord
1436
1521
  # Controls what happens to the associated object when
1437
1522
  # its owner is destroyed:
1438
1523
  #
1524
+ # * <tt>nil</tt> do nothing (default).
1439
1525
  # * <tt>:destroy</tt> causes the associated object to also be destroyed
1526
+ # * <tt>:destroy_async</tt> causes all the associated object to be destroyed in a background job.
1440
1527
  # * <tt>:delete</tt> causes the associated object to be deleted directly from the database (so callbacks will not execute)
1441
1528
  # * <tt>:nullify</tt> causes the foreign key to be set to +NULL+. Polymorphic type column is also nullified
1442
1529
  # on polymorphic associations. Callbacks are not executed.
@@ -1495,6 +1582,11 @@ module ActiveRecord
1495
1582
  # When set to +true+, the association will also have its presence validated.
1496
1583
  # This will validate the association itself, not the id. You can use
1497
1584
  # +:inverse_of+ to avoid an extra query during validation.
1585
+ # [:strict_loading]
1586
+ # Enforces strict loading every time the associated record is loaded through this association.
1587
+ # [:ensuring_owner_was]
1588
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1589
+ # associated records to be deleted in a background job.
1498
1590
  #
1499
1591
  # Option examples:
1500
1592
  # has_one :credit_card, dependent: :destroy # destroys the associated credit card
@@ -1507,6 +1599,7 @@ module ActiveRecord
1507
1599
  # has_one :club, through: :membership
1508
1600
  # has_one :primary_address, -> { where(primary: true) }, through: :addressables, source: :addressable
1509
1601
  # has_one :credit_card, required: true
1602
+ # has_one :credit_card, strict_loading: true
1510
1603
  def has_one(name, scope = nil, **options)
1511
1604
  reflection = Builder::HasOne.build(self, name, scope, options)
1512
1605
  Reflection.add_reflection self, name, reflection
@@ -1588,7 +1681,8 @@ module ActiveRecord
1588
1681
  # By default this is +id+.
1589
1682
  # [:dependent]
1590
1683
  # If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
1591
- # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
1684
+ # <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method. If set to
1685
+ # <tt>:destroy_async</tt>, the associated object is scheduled to be destroyed in a background job.
1592
1686
  # This option should not be specified when #belongs_to is used in conjunction with
1593
1687
  # a #has_many relationship on another class because of the potential to leave
1594
1688
  # orphaned records behind.
@@ -1640,6 +1734,11 @@ module ActiveRecord
1640
1734
  # [:default]
1641
1735
  # Provide a callable (i.e. proc or lambda) to specify that the association should
1642
1736
  # be initialized with a particular record before validation.
1737
+ # [:strict_loading]
1738
+ # Enforces strict loading every time the associated record is loaded through this association.
1739
+ # [:ensuring_owner_was]
1740
+ # Specifies an instance method to be called on the owner. The method must return true in order for the
1741
+ # associated records to be deleted in a background job.
1643
1742
  #
1644
1743
  # Option examples:
1645
1744
  # belongs_to :firm, foreign_key: "client_of"
@@ -1654,6 +1753,7 @@ module ActiveRecord
1654
1753
  # belongs_to :company, touch: :employees_last_updated_at
1655
1754
  # belongs_to :user, optional: true
1656
1755
  # belongs_to :account, default: -> { company.account }
1756
+ # belongs_to :account, strict_loading: true
1657
1757
  def belongs_to(name, scope = nil, **options)
1658
1758
  reflection = Builder::BelongsTo.build(self, name, scope, options)
1659
1759
  Reflection.add_reflection self, name, reflection
@@ -1676,7 +1776,7 @@ module ActiveRecord
1676
1776
  # The join table should not have a primary key or a model associated with it. You must manually generate the
1677
1777
  # join table with a migration such as this:
1678
1778
  #
1679
- # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[5.0]
1779
+ # class CreateDevelopersProjectsJoinTable < ActiveRecord::Migration[6.0]
1680
1780
  # def change
1681
1781
  # create_join_table :developers, :projects
1682
1782
  # end
@@ -1816,6 +1916,8 @@ module ActiveRecord
1816
1916
  #
1817
1917
  # Note that NestedAttributes::ClassMethods#accepts_nested_attributes_for sets
1818
1918
  # <tt>:autosave</tt> to <tt>true</tt>.
1919
+ # [:strict_loading]
1920
+ # Enforces strict loading every time an associated record is loaded through this association.
1819
1921
  #
1820
1922
  # Option examples:
1821
1923
  # has_and_belongs_to_many :projects
@@ -1823,6 +1925,7 @@ module ActiveRecord
1823
1925
  # has_and_belongs_to_many :nations, class_name: "Country"
1824
1926
  # has_and_belongs_to_many :categories, join_table: "prods_cats"
1825
1927
  # has_and_belongs_to_many :categories, -> { readonly }
1928
+ # has_and_belongs_to_many :categories, strict_loading: true
1826
1929
  def has_and_belongs_to_many(name, scope = nil, **options, &extension)
1827
1930
  habtm_reflection = ActiveRecord::Reflection::HasAndBelongsToManyReflection.new(name, scope, options, self)
1828
1931
 
@@ -1853,11 +1956,11 @@ module ActiveRecord
1853
1956
  hm_options[:through] = middle_reflection.name
1854
1957
  hm_options[:source] = join_model.right_reflection.name
1855
1958
 
1856
- [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend].each do |k|
1959
+ [:before_add, :after_add, :before_remove, :after_remove, :autosave, :validate, :join_table, :class_name, :extend, :strict_loading].each do |k|
1857
1960
  hm_options[k] = options[k] if options.key? k
1858
1961
  end
1859
1962
 
1860
- has_many name, scope, hm_options, &extension
1963
+ has_many name, scope, **hm_options, &extension
1861
1964
  _reflections[name.to_s].parent_reflection = habtm_reflection
1862
1965
  end
1863
1966
  end
@@ -7,22 +7,23 @@ module ActiveRecord
7
7
  include ActiveModel::AttributeAssignment
8
8
 
9
9
  private
10
-
11
10
  def _assign_attributes(attributes)
12
- multi_parameter_attributes = {}
13
- nested_parameter_attributes = {}
11
+ multi_parameter_attributes = nested_parameter_attributes = nil
14
12
 
15
13
  attributes.each do |k, v|
16
- if k.include?("(")
17
- multi_parameter_attributes[k] = attributes.delete(k)
14
+ key = k.to_s
15
+
16
+ if key.include?("(")
17
+ (multi_parameter_attributes ||= {})[key] = v
18
18
  elsif v.is_a?(Hash)
19
- nested_parameter_attributes[k] = attributes.delete(k)
19
+ (nested_parameter_attributes ||= {})[key] = v
20
+ else
21
+ _assign_attribute(key, v)
20
22
  end
21
23
  end
22
- super(attributes)
23
24
 
24
- assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
25
- assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
25
+ assign_nested_parameter_attributes(nested_parameter_attributes) if nested_parameter_attributes
26
+ assign_multiparameter_attributes(multi_parameter_attributes) if multi_parameter_attributes
26
27
  end
27
28
 
28
29
  # Assign any deferred nested attributes after the base attributes have been set.
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  extend ActiveSupport::Concern
30
30
 
31
31
  included do
32
- attribute_method_suffix "_before_type_cast"
32
+ attribute_method_suffix "_before_type_cast", "_for_database"
33
33
  attribute_method_suffix "_came_from_user?"
34
34
  end
35
35
 
@@ -46,8 +46,10 @@ 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?
50
- @attributes[attr_name.to_s].value_before_type_cast
49
+ name = attr_name.to_s
50
+ name = self.class.attribute_aliases[name] || name
51
+
52
+ attribute_before_type_cast(name)
51
53
  end
52
54
 
53
55
  # Returns a hash of attributes before typecasting and deserialization.
@@ -61,20 +63,21 @@ module ActiveRecord
61
63
  # task.attributes_before_type_cast
62
64
  # # => {"id"=>nil, "title"=>nil, "is_done"=>true, "completed_on"=>"2012-10-21", "created_at"=>nil, "updated_at"=>nil}
63
65
  def attributes_before_type_cast
64
- sync_with_transaction_state if @transaction_state&.finalized?
65
66
  @attributes.values_before_type_cast
66
67
  end
67
68
 
68
69
  private
69
-
70
70
  # Dispatch target for <tt>*_before_type_cast</tt> attribute methods.
71
- def attribute_before_type_cast(attribute_name)
72
- read_attribute_before_type_cast(attribute_name)
71
+ def attribute_before_type_cast(attr_name)
72
+ @attributes[attr_name].value_before_type_cast
73
+ end
74
+
75
+ def attribute_for_database(attr_name)
76
+ @attributes[attr_name].value_for_database
73
77
  end
74
78
 
75
- def attribute_came_from_user?(attribute_name)
76
- sync_with_transaction_state if @transaction_state&.finalized?
77
- @attributes[attribute_name].came_from_user?
79
+ def attribute_came_from_user?(attr_name)
80
+ @attributes[attr_name].came_from_user?
78
81
  end
79
82
  end
80
83
  end
@@ -49,7 +49,7 @@ module ActiveRecord
49
49
  # +to+ When passed, this method will return false unless the value was
50
50
  # changed to the given value
51
51
  def saved_change_to_attribute?(attr_name, **options)
52
- mutations_before_last_save.changed?(attr_name.to_s, options)
52
+ mutations_before_last_save.changed?(attr_name.to_s, **options)
53
53
  end
54
54
 
55
55
  # Returns the change to an attribute during the last save. If the
@@ -89,7 +89,7 @@ module ActiveRecord
89
89
  # This method is useful in validations and before callbacks to determine
90
90
  # if the next call to +save+ will change a particular attribute. It can be
91
91
  # invoked as +will_save_change_to_name?+ instead of
92
- # <tt>will_save_change_to_attribute("name")</tt>.
92
+ # <tt>will_save_change_to_attribute?("name")</tt>.
93
93
  #
94
94
  # ==== Options
95
95
  #
@@ -99,7 +99,7 @@ module ActiveRecord
99
99
  # +to+ When passed, this method will return false unless the value will be
100
100
  # changed to the given value
101
101
  def will_save_change_to_attribute?(attr_name, **options)
102
- mutations_from_database.changed?(attr_name.to_s, options)
102
+ mutations_from_database.changed?(attr_name.to_s, **options)
103
103
  end
104
104
 
105
105
  # Returns the change to an attribute that will be persisted during the
@@ -156,16 +156,6 @@ module ActiveRecord
156
156
  end
157
157
 
158
158
  private
159
- def mutations_from_database
160
- sync_with_transaction_state if @transaction_state&.finalized?
161
- super
162
- end
163
-
164
- def mutations_before_last_save
165
- sync_with_transaction_state if @transaction_state&.finalized?
166
- super
167
- end
168
-
169
159
  def write_attribute_without_type_cast(attr_name, value)
170
160
  result = super
171
161
  clear_attribute_change(attr_name)
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
 
32
32
  # Returns the primary key column's value before type cast.
33
33
  def id_before_type_cast
34
- read_attribute_before_type_cast(@primary_key)
34
+ attribute_before_type_cast(@primary_key)
35
35
  end
36
36
 
37
37
  # Returns the primary key column's previous value.
@@ -44,14 +44,17 @@ module ActiveRecord
44
44
  attribute_in_database(@primary_key)
45
45
  end
46
46
 
47
- private
47
+ def id_for_database # :nodoc:
48
+ @attributes[@primary_key].value_for_database
49
+ end
48
50
 
51
+ private
49
52
  def attribute_method?(attr_name)
50
53
  attr_name == "id" || super
51
54
  end
52
55
 
53
56
  module ClassMethods
54
- ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database).to_set
57
+ ID_ATTRIBUTE_METHODS = %w(id id= id? id_before_type_cast id_was id_in_database id_for_database).to_set
55
58
 
56
59
  def instance_method_already_implemented?(method_name)
57
60
  super || primary_key && ID_ATTRIBUTE_METHODS.include?(method_name)
@@ -120,7 +123,6 @@ module ActiveRecord
120
123
  end
121
124
 
122
125
  private
123
-
124
126
  def suppress_composite_primary_key(pk)
125
127
  return pk unless pk.is_a?(Array)
126
128
 
@@ -17,7 +17,7 @@ module ActiveRecord
17
17
  when false, nil then false
18
18
  else
19
19
  if !type_for_attribute(attr_name) { false }
20
- if Numeric === value || value !~ /[^0-9]/
20
+ if Numeric === value || !value.match?(/[^0-9]/)
21
21
  !value.to_i.zero?
22
22
  else
23
23
  return false if ActiveModel::Type::Boolean::FALSE_VALUES.include?(value)
@@ -31,11 +31,8 @@ module ActiveRecord
31
31
  end
32
32
  end
33
33
 
34
- private
35
- # Dispatch target for <tt>*?</tt> attribute methods.
36
- def attribute?(attribute_name)
37
- query_attribute(attribute_name)
38
- end
34
+ alias :attribute? :query_attribute
35
+ private :attribute?
39
36
  end
40
37
  end
41
38
  end