activerecord 6.0.3 → 6.1.0.rc1

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 (244) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +779 -705
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +1 -1
  7. data/lib/active_record/association_relation.rb +22 -14
  8. data/lib/active_record/associations.rb +114 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +40 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +19 -6
  22. data/lib/active_record/associations/collection_proxy.rb +13 -5
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -2
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +72 -50
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +11 -5
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -8
  36. data/lib/active_record/attribute_methods.rb +52 -48
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  38. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -11
  42. data/lib/active_record/attribute_methods/serialization.rb +4 -4
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  44. data/lib/active_record/attribute_methods/write.rb +12 -20
  45. data/lib/active_record/attributes.rb +27 -7
  46. data/lib/active_record/autosave_association.rb +57 -40
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +32 -22
  49. data/lib/active_record/coders/yaml_column.rb +1 -1
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +186 -134
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +65 -22
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +2 -7
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +112 -27
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +224 -85
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +66 -24
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +36 -69
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +129 -88
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -25
  68. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +33 -6
  71. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  72. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  73. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +11 -7
  74. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  75. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  76. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  77. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +13 -54
  80. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  82. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  90. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  91. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  94. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +72 -55
  96. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  97. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  103. data/lib/active_record/connection_handling.rb +210 -71
  104. data/lib/active_record/core.rb +220 -55
  105. data/lib/active_record/database_configurations.rb +124 -85
  106. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  107. data/lib/active_record/database_configurations/database_config.rb +52 -9
  108. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  109. data/lib/active_record/database_configurations/url_config.rb +15 -40
  110. data/lib/active_record/delegated_type.rb +209 -0
  111. data/lib/active_record/destroy_association_async_job.rb +36 -0
  112. data/lib/active_record/enum.rb +27 -10
  113. data/lib/active_record/errors.rb +47 -12
  114. data/lib/active_record/explain.rb +9 -4
  115. data/lib/active_record/explain_subscriber.rb +1 -1
  116. data/lib/active_record/fixture_set/file.rb +10 -17
  117. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  118. data/lib/active_record/fixture_set/render_context.rb +1 -1
  119. data/lib/active_record/fixture_set/table_row.rb +2 -2
  120. data/lib/active_record/fixtures.rb +54 -8
  121. data/lib/active_record/gem_version.rb +3 -3
  122. data/lib/active_record/inheritance.rb +40 -18
  123. data/lib/active_record/insert_all.rb +33 -6
  124. data/lib/active_record/integration.rb +3 -5
  125. data/lib/active_record/internal_metadata.rb +15 -4
  126. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  127. data/lib/active_record/locking/optimistic.rb +22 -16
  128. data/lib/active_record/locking/pessimistic.rb +6 -2
  129. data/lib/active_record/log_subscriber.rb +26 -8
  130. data/lib/active_record/middleware/database_selector.rb +4 -1
  131. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  133. data/lib/active_record/migration.rb +113 -83
  134. data/lib/active_record/migration/command_recorder.rb +47 -27
  135. data/lib/active_record/migration/compatibility.rb +67 -17
  136. data/lib/active_record/model_schema.rb +88 -13
  137. data/lib/active_record/nested_attributes.rb +2 -3
  138. data/lib/active_record/no_touching.rb +1 -1
  139. data/lib/active_record/persistence.rb +50 -45
  140. data/lib/active_record/query_cache.rb +15 -5
  141. data/lib/active_record/querying.rb +11 -6
  142. data/lib/active_record/railtie.rb +64 -44
  143. data/lib/active_record/railties/databases.rake +253 -98
  144. data/lib/active_record/readonly_attributes.rb +4 -0
  145. data/lib/active_record/reflection.rb +70 -57
  146. data/lib/active_record/relation.rb +96 -67
  147. data/lib/active_record/relation/batches.rb +38 -31
  148. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  149. data/lib/active_record/relation/calculations.rb +101 -44
  150. data/lib/active_record/relation/delegation.rb +2 -1
  151. data/lib/active_record/relation/finder_methods.rb +45 -15
  152. data/lib/active_record/relation/from_clause.rb +1 -1
  153. data/lib/active_record/relation/merger.rb +27 -25
  154. data/lib/active_record/relation/predicate_builder.rb +57 -33
  155. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  156. data/lib/active_record/relation/predicate_builder/association_query_value.rb +2 -2
  157. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  159. data/lib/active_record/relation/query_methods.rb +330 -195
  160. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  161. data/lib/active_record/relation/spawn_methods.rb +6 -5
  162. data/lib/active_record/relation/where_clause.rb +104 -57
  163. data/lib/active_record/result.rb +41 -33
  164. data/lib/active_record/runtime_registry.rb +2 -2
  165. data/lib/active_record/sanitization.rb +6 -17
  166. data/lib/active_record/schema_dumper.rb +34 -4
  167. data/lib/active_record/schema_migration.rb +0 -4
  168. data/lib/active_record/scoping/named.rb +6 -17
  169. data/lib/active_record/secure_token.rb +16 -8
  170. data/lib/active_record/serialization.rb +5 -3
  171. data/lib/active_record/signed_id.rb +116 -0
  172. data/lib/active_record/statement_cache.rb +20 -4
  173. data/lib/active_record/store.rb +2 -2
  174. data/lib/active_record/suppressor.rb +2 -2
  175. data/lib/active_record/table_metadata.rb +36 -52
  176. data/lib/active_record/tasks/database_tasks.rb +139 -113
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  180. data/lib/active_record/test_databases.rb +5 -4
  181. data/lib/active_record/test_fixtures.rb +37 -16
  182. data/lib/active_record/timestamp.rb +4 -6
  183. data/lib/active_record/touch_later.rb +21 -21
  184. data/lib/active_record/transactions.rb +15 -64
  185. data/lib/active_record/type.rb +8 -1
  186. data/lib/active_record/type/serialized.rb +6 -2
  187. data/lib/active_record/type/time.rb +10 -0
  188. data/lib/active_record/type_caster/connection.rb +0 -1
  189. data/lib/active_record/type_caster/map.rb +8 -5
  190. data/lib/active_record/validations.rb +1 -0
  191. data/lib/active_record/validations/numericality.rb +35 -0
  192. data/lib/active_record/validations/uniqueness.rb +24 -4
  193. data/lib/arel.rb +5 -13
  194. data/lib/arel/attributes/attribute.rb +4 -0
  195. data/lib/arel/collectors/bind.rb +5 -0
  196. data/lib/arel/collectors/composite.rb +8 -0
  197. data/lib/arel/collectors/sql_string.rb +7 -0
  198. data/lib/arel/collectors/substitute_binds.rb +7 -0
  199. data/lib/arel/nodes.rb +3 -1
  200. data/lib/arel/nodes/binary.rb +82 -8
  201. data/lib/arel/nodes/bind_param.rb +8 -0
  202. data/lib/arel/nodes/casted.rb +21 -9
  203. data/lib/arel/nodes/equality.rb +6 -9
  204. data/lib/arel/nodes/grouping.rb +3 -0
  205. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  206. data/lib/arel/nodes/in.rb +8 -1
  207. data/lib/arel/nodes/infix_operation.rb +13 -1
  208. data/lib/arel/nodes/join_source.rb +1 -1
  209. data/lib/arel/nodes/node.rb +7 -6
  210. data/lib/arel/nodes/ordering.rb +27 -0
  211. data/lib/arel/nodes/sql_literal.rb +3 -0
  212. data/lib/arel/nodes/table_alias.rb +7 -3
  213. data/lib/arel/nodes/unary.rb +0 -1
  214. data/lib/arel/predications.rb +12 -18
  215. data/lib/arel/select_manager.rb +1 -2
  216. data/lib/arel/table.rb +13 -5
  217. data/lib/arel/visitors.rb +0 -7
  218. data/lib/arel/visitors/dot.rb +14 -2
  219. data/lib/arel/visitors/mysql.rb +11 -1
  220. data/lib/arel/visitors/postgresql.rb +15 -4
  221. data/lib/arel/visitors/to_sql.rb +89 -78
  222. data/lib/rails/generators/active_record/migration.rb +6 -1
  223. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  224. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  225. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  226. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  227. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  228. metadata +27 -28
  229. data/lib/active_record/advisory_lock_base.rb +0 -18
  230. data/lib/active_record/attribute_decorators.rb +0 -88
  231. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  232. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  233. data/lib/active_record/define_callbacks.rb +0 -22
  234. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  235. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  236. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  237. data/lib/arel/attributes.rb +0 -22
  238. data/lib/arel/visitors/depth_first.rb +0 -203
  239. data/lib/arel/visitors/ibm_db.rb +0 -34
  240. data/lib/arel/visitors/informix.rb +0 -62
  241. data/lib/arel/visitors/mssql.rb +0 -156
  242. data/lib/arel/visitors/oracle.rb +0 -158
  243. data/lib/arel/visitors/oracle12.rb +0 -65
  244. data/lib/arel/visitors/where_sql.rb +0 -22
@@ -47,8 +47,9 @@ module ActiveRecord
47
47
  # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
48
48
  # may be returned on an error.
49
49
  def establish_connection(config_or_env = nil)
50
- config_hash = resolve_config_for_connection(config_or_env)
51
- connection_handler.establish_connection(config_hash)
50
+ config_or_env ||= DEFAULT_ENV.call.to_sym
51
+ db_config, owner_name = resolve_config_for_connection(config_or_env)
52
+ connection_handler.establish_connection(db_config, owner_name: owner_name, role: current_role, shard: current_shard)
52
53
  end
53
54
 
54
55
  # Connects a model to the databases specified. The +database+ keyword
@@ -64,25 +65,54 @@ module ActiveRecord
64
65
  # connects_to database: { writing: :primary, reading: :primary_replica }
65
66
  # end
66
67
  #
67
- # Returns an array of established connections.
68
- def connects_to(database: {})
68
+ # +connects_to+ also supports horizontal sharding. The horizontal sharding API
69
+ # also supports read replicas. Connect a model to a list of shards like this:
70
+ #
71
+ # class AnimalsModel < ApplicationRecord
72
+ # self.abstract_class = true
73
+ #
74
+ # connects_to shards: {
75
+ # default: { writing: :primary, reading: :primary_replica },
76
+ # shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
77
+ # }
78
+ # end
79
+ #
80
+ # Returns an array of database connections.
81
+ def connects_to(database: {}, shards: {})
82
+ raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class?
83
+
84
+ if database.present? && shards.present?
85
+ raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments."
86
+ end
87
+
69
88
  connections = []
70
89
 
71
90
  database.each do |role, database_key|
72
- config_hash = resolve_config_for_connection(database_key)
91
+ db_config, owner_name = resolve_config_for_connection(database_key)
73
92
  handler = lookup_connection_handler(role.to_sym)
74
93
 
75
- connections << handler.establish_connection(config_hash)
94
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
95
+ end
96
+
97
+ shards.each do |shard, database_keys|
98
+ database_keys.each do |role, database_key|
99
+ db_config, owner_name = resolve_config_for_connection(database_key)
100
+ handler = lookup_connection_handler(role.to_sym)
101
+
102
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
103
+ end
76
104
  end
77
105
 
78
106
  connections
79
107
  end
80
108
 
81
- # Connects to a database or role (ex writing, reading, or another
82
- # custom role) for the duration of the block.
109
+ # Connects to a role (ex writing, reading or a custom role) and/or
110
+ # shard for the duration of the block. At the end of the block the
111
+ # connection will be returned to the original role / shard.
83
112
  #
84
- # If a role is passed, Active Record will look up the connection
85
- # based on the requested role:
113
+ # If only a role is passed, Active Record will look up the connection
114
+ # based on the requested role. If a non-established role is requested
115
+ # an `ActiveRecord::ConnectionNotEstablished` error will be raised:
86
116
  #
87
117
  # ActiveRecord::Base.connected_to(role: :writing) do
88
118
  # Dog.create! # creates dog using dog writing connection
@@ -92,93 +122,149 @@ module ActiveRecord
92
122
  # Dog.create! # throws exception because we're on a replica
93
123
  # end
94
124
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
- # # raises exception due to non-existent role
97
- # end
125
+ # When swapping to a shard, the role must be passed as well. If a non-existent
126
+ # shard is passed, an `ActiveRecord::ConnectionNotEstablished` error will be
127
+ # raised.
128
+ #
129
+ # When a shard and role is passed, Active Record will first lookup the role,
130
+ # and then look up the connection by shard key.
98
131
  #
99
- # The `database` kwarg is deprecated in 6.1 and will be removed in 6.2
132
+ # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
133
+ # Dog.first # finds first Dog record stored on the shard one replica
134
+ # end
100
135
  #
101
- # It is not recommended for use as it re-establishes a connection every
102
- # time it is called.
103
- def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
104
- if database && role
105
- raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
136
+ # The database kwarg is deprecated and will be removed in 6.2.0 without replacement.
137
+ def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
138
+ if legacy_connection_handling
139
+ if self != Base
140
+ raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling."
141
+ end
142
+ else
143
+ if self != Base && !abstract_class
144
+ raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
145
+ end
146
+ end
147
+
148
+ if database && (role || shard)
149
+ raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
106
150
  elsif database
151
+ ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
152
+
107
153
  if database.is_a?(Hash)
108
154
  role, database = database.first
109
155
  role = role.to_sym
110
156
  end
111
157
 
112
- config_hash = resolve_config_for_connection(database)
158
+ db_config, owner_name = resolve_config_for_connection(database)
113
159
  handler = lookup_connection_handler(role)
114
160
 
115
- handler.establish_connection(config_hash)
161
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
116
162
 
117
163
  with_handler(role, &blk)
118
- elsif role
119
- prevent_writes = true if role == reading_role
120
-
121
- with_handler(role.to_sym) do
122
- connection_handler.while_preventing_writes(prevent_writes, &blk)
164
+ elsif role || shard
165
+ unless role
166
+ raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
123
167
  end
168
+
169
+ with_role_and_shard(role, shard, prevent_writes, &blk)
124
170
  else
125
- raise ArgumentError, "must provide a `database` or a `role`."
171
+ raise ArgumentError, "must provide a `shard` and/or `role`."
126
172
  end
127
173
  end
128
174
 
129
- # Returns true if role is the current connected role.
175
+ # Connects a role and/or shard to the provided connection names. Optionally `prevent_writes`
176
+ # can be passed to block writes on a connection. `reading` will automatically set
177
+ # `prevent_writes` to true.
130
178
  #
131
- # ActiveRecord::Base.connected_to(role: :writing) do
132
- # ActiveRecord::Base.connected_to?(role: :writing) #=> true
133
- # ActiveRecord::Base.connected_to?(role: :reading) #=> false
179
+ # `connected_to_many` is an alternative to deeply nested `connected_to` blocks.
180
+ #
181
+ # Usage:
182
+ #
183
+ # ActiveRecord::Base.connected_to(AnimalsRecord, MealsRecord], role: :reading) do
184
+ # Dog.first # Read from animals replica
185
+ # Dinner.first # Read from meals replica
186
+ # Person.first # Read from primary writer
134
187
  # end
135
- def connected_to?(role:)
136
- current_role == role.to_sym
188
+ def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
189
+ if legacy_connection_handling
190
+ raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
191
+ end
192
+
193
+ if self != Base || classes.include?(Base)
194
+ raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
195
+ end
196
+
197
+ prevent_writes = true if role == reading_role
198
+
199
+ connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes }
200
+ yield
201
+ ensure
202
+ connected_to_stack.pop
137
203
  end
138
204
 
139
- # Returns the symbol representing the current connected role.
205
+ # Use a specified connection.
140
206
  #
141
- # ActiveRecord::Base.connected_to(role: :writing) do
142
- # ActiveRecord::Base.current_role #=> :writing
143
- # end
207
+ # This method is useful for ensuring that a specific connection is
208
+ # being used. For example, when booting a console in readonly mode.
144
209
  #
145
- # ActiveRecord::Base.connected_to(role: :reading) do
146
- # ActiveRecord::Base.current_role #=> :reading
147
- # end
148
- def current_role
149
- connection_handlers.key(connection_handler)
150
- end
210
+ # It is not recommended to use this method in a request since it
211
+ # does not yield to a block like `connected_to`.
212
+ def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
213
+ if legacy_connection_handling
214
+ raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
215
+ end
151
216
 
152
- def lookup_connection_handler(handler_key) # :nodoc:
153
- handler_key ||= ActiveRecord::Base.writing_role
154
- connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
155
- end
217
+ prevent_writes = true if role == reading_role
156
218
 
157
- def with_handler(handler_key, &blk) # :nodoc:
158
- handler = lookup_connection_handler(handler_key)
159
- swap_connection_handler(handler, &blk)
219
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
160
220
  end
161
221
 
162
- def resolve_config_for_connection(config_or_env) # :nodoc:
163
- raise "Anonymous class is not allowed." unless name
164
-
165
- config_or_env ||= DEFAULT_ENV.call.to_sym
166
- pool_name = primary_class? ? "primary" : name
167
- self.connection_specification_name = pool_name
222
+ # Prevent writing to the database regardless of role.
223
+ #
224
+ # In some cases you may want to prevent writes to the database
225
+ # even if you are on a database that can write. `while_preventing_writes`
226
+ # will prevent writes to the database for the duration of the block.
227
+ #
228
+ # This method does not provide the same protection as a readonly
229
+ # user and is meant to be a safeguard against accidental writes.
230
+ #
231
+ # See `READ_QUERY` for the queries that are blocked by this
232
+ # method.
233
+ def while_preventing_writes(enabled = true, &block)
234
+ if legacy_connection_handling
235
+ connection_handler.while_preventing_writes(enabled, &block)
236
+ else
237
+ connected_to(role: current_role, prevent_writes: enabled, &block)
238
+ end
239
+ end
168
240
 
169
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
170
- config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
171
- config_hash[:name] = pool_name
241
+ # Returns true if role is the current connected role.
242
+ #
243
+ # ActiveRecord::Base.connected_to(role: :writing) do
244
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
245
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
246
+ # end
247
+ def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
248
+ current_role == role.to_sym && current_shard == shard.to_sym
249
+ end
172
250
 
173
- config_hash
251
+ def lookup_connection_handler(handler_key) # :nodoc:
252
+ if ActiveRecord::Base.legacy_connection_handling
253
+ handler_key ||= ActiveRecord::Base.writing_role
254
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
255
+ else
256
+ ActiveRecord::Base.connection_handler
257
+ end
174
258
  end
175
259
 
176
260
  # Clears the query cache for all connections associated with the current thread.
177
261
  def clear_query_caches_for_current_thread
178
- ActiveRecord::Base.connection_handlers.each_value do |handler|
179
- handler.connection_pool_list.each do |pool|
180
- pool.connection.clear_query_cache if pool.active_connection?
262
+ if ActiveRecord::Base.legacy_connection_handling
263
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
264
+ clear_on_handler(handler)
181
265
  end
266
+ else
267
+ clear_on_handler(ActiveRecord::Base.connection_handler)
182
268
  end
183
269
  end
184
270
 
@@ -191,10 +277,10 @@ module ActiveRecord
191
277
 
192
278
  attr_writer :connection_specification_name
193
279
 
194
- # Return the specification name from the current class or its parent.
280
+ # Return the connection specification name from the current class or its parent.
195
281
  def connection_specification_name
196
282
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
197
- return self == Base ? "primary" : superclass.connection_specification_name
283
+ return self == Base ? Base.name : superclass.connection_specification_name
198
284
  end
199
285
  @connection_specification_name
200
286
  end
@@ -210,20 +296,32 @@ module ActiveRecord
210
296
  #
211
297
  # Please use only for reading.
212
298
  def connection_config
213
- connection_pool.spec.config
299
+ connection_pool.db_config.configuration_hash
300
+ end
301
+ deprecate connection_config: "Use connection_db_config instead"
302
+
303
+ # Returns the db_config object from the associated connection:
304
+ #
305
+ # ActiveRecord::Base.connection_db_config
306
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
307
+ # @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>
308
+ #
309
+ # Use only for reading.
310
+ def connection_db_config
311
+ connection_pool.db_config
214
312
  end
215
313
 
216
314
  def connection_pool
217
- connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
315
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
218
316
  end
219
317
 
220
318
  def retrieve_connection
221
- connection_handler.retrieve_connection(connection_specification_name)
319
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
222
320
  end
223
321
 
224
322
  # Returns +true+ if Active Record is connected.
225
323
  def connected?
226
- connection_handler.connected?(connection_specification_name)
324
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
227
325
  end
228
326
 
229
327
  def remove_connection(name = nil)
@@ -231,11 +329,11 @@ module ActiveRecord
231
329
  # if removing a connection that has a pool, we reset the
232
330
  # connection_specification_name so it will use the parent
233
331
  # pool.
234
- if connection_handler.retrieve_connection_pool(name)
332
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
235
333
  self.connection_specification_name = nil
236
334
  end
237
335
 
238
- connection_handler.remove_connection(name)
336
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
239
337
  end
240
338
 
241
339
  def clear_cache! # :nodoc:
@@ -246,6 +344,47 @@ module ActiveRecord
246
344
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
247
345
 
248
346
  private
347
+ def clear_on_handler(handler)
348
+ handler.all_connection_pools.each do |pool|
349
+ pool.connection.clear_query_cache if pool.active_connection?
350
+ end
351
+ end
352
+
353
+ def resolve_config_for_connection(config_or_env)
354
+ raise "Anonymous class is not allowed." unless name
355
+
356
+ owner_name = primary_class? ? Base.name : name
357
+ self.connection_specification_name = owner_name
358
+
359
+ db_config = Base.configurations.resolve(config_or_env)
360
+ [db_config, owner_name]
361
+ end
362
+
363
+ def with_handler(handler_key, &blk)
364
+ handler = lookup_connection_handler(handler_key)
365
+ swap_connection_handler(handler, &blk)
366
+ end
367
+
368
+ def with_role_and_shard(role, shard, prevent_writes)
369
+ prevent_writes = true if role == reading_role
370
+
371
+ if ActiveRecord::Base.legacy_connection_handling
372
+ with_handler(role.to_sym) do
373
+ connection_handler.while_preventing_writes(prevent_writes) do
374
+ self.connected_to_stack << { shard: shard, klasses: [self] }
375
+ yield
376
+ end
377
+ end
378
+ else
379
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
380
+ return_value = yield
381
+ return_value.load if return_value.is_a? ActiveRecord::Relation
382
+ return_value
383
+ end
384
+ ensure
385
+ self.connected_to_stack.pop
386
+ end
387
+
249
388
  def swap_connection_handler(handler, &blk) # :nodoc:
250
389
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
251
390
  return_value = yield
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
3
4
  require "active_support/core_ext/hash/indifferent_access"
4
5
  require "active_support/core_ext/string/filters"
5
6
  require "active_support/parameter_filter"
@@ -25,6 +26,18 @@ module ActiveRecord
25
26
  # their relevant queries. Defaults to false.
26
27
  mattr_accessor :verbose_query_logs, instance_writer: false, default: false
27
28
 
29
+ ##
30
+ # :singleton-method:
31
+ #
32
+ # Specifies the names of the queues used by background jobs.
33
+ mattr_accessor :queues, instance_accessor: false, default: {}
34
+
35
+ ##
36
+ # :singleton-method:
37
+ #
38
+ # Specifies the job used to destroy associations in the background
39
+ class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false
40
+
28
41
  ##
29
42
  # Contains the database configuration - as is typically stored in config/database.yml -
30
43
  # as an ActiveRecord::DatabaseConfigurations object.
@@ -43,9 +56,9 @@ module ActiveRecord
43
56
  #
44
57
  # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
45
58
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
46
- # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
59
+ # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
47
60
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
48
- # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
61
+ # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
49
62
  # ]>
50
63
  def self.configurations=(config)
51
64
  @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
@@ -80,14 +93,6 @@ module ActiveRecord
80
93
  # scope being ignored is error-worthy, rather than a warning.
81
94
  mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
82
95
 
83
- # :singleton-method:
84
- # Specify the behavior for unsafe raw query methods. Values are as follows
85
- # deprecated - Warnings are logged when unsafe raw SQL is passed to
86
- # query methods.
87
- # disabled - Unsafe raw SQL passed to query methods results in
88
- # UnknownAttributeReference exception.
89
- mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
90
-
91
96
  ##
92
97
  # :singleton-method:
93
98
  # Specify whether or not to use timestamps for migration versions
@@ -103,7 +108,7 @@ module ActiveRecord
103
108
 
104
109
  ##
105
110
  # :singleton-method:
106
- # Specifies which database schemas to dump when calling db:structure:dump.
111
+ # Specifies which database schemas to dump when calling db:schema:dump.
107
112
  # If the value is :schema_search_path (the default), any schemas listed in
108
113
  # schema_search_path are dumped. Use :all to dump all schemas regardless
109
114
  # of schema_search_path, or a string of comma separated schemas for a
@@ -118,29 +123,162 @@ module ActiveRecord
118
123
  # potentially cause memory bloat.
119
124
  mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
120
125
 
126
+ ##
127
+ # :singleton-method:
128
+ # Show a warning when Rails couldn't parse your database.yml
129
+ # for multiple databases.
130
+ mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false
131
+
121
132
  mattr_accessor :maintain_test_schema, instance_accessor: false
122
133
 
123
- mattr_accessor :belongs_to_required_by_default, instance_accessor: false
134
+ class_attribute :belongs_to_required_by_default, instance_accessor: false
135
+
136
+ ##
137
+ # :singleton-method:
138
+ # Set the application to log or raise when an association violates strict loading.
139
+ # Defaults to :raise.
140
+ mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
124
141
 
125
- mattr_accessor :connection_handlers, instance_accessor: false, default: {}
142
+ class_attribute :strict_loading_by_default, instance_accessor: false, default: false
126
143
 
127
144
  mattr_accessor :writing_role, instance_accessor: false, default: :writing
128
145
 
129
146
  mattr_accessor :reading_role, instance_accessor: false, default: :reading
130
147
 
148
+ mattr_accessor :has_many_inversing, instance_accessor: false, default: false
149
+
131
150
  class_attribute :default_connection_handler, instance_writer: false
132
151
 
152
+ class_attribute :default_role, instance_writer: false
153
+
154
+ class_attribute :default_shard, instance_writer: false
155
+
156
+ mattr_accessor :legacy_connection_handling, instance_writer: false, default: true
157
+
133
158
  self.filter_attributes = []
134
159
 
135
160
  def self.connection_handler
136
- Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
161
+ Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
137
162
  end
138
163
 
139
164
  def self.connection_handler=(handler)
140
- Thread.current.thread_variable_set("ar_connection_handler", handler)
165
+ Thread.current.thread_variable_set(:ar_connection_handler, handler)
166
+ end
167
+
168
+ def self.connection_handlers
169
+ @@connection_handlers ||= {}
170
+ end
171
+
172
+ def self.connection_handlers=(handlers)
173
+ @@connection_handlers = handlers
174
+ end
175
+
176
+ # Returns the symbol representing the current connected role.
177
+ #
178
+ # ActiveRecord::Base.connected_to(role: :writing) do
179
+ # ActiveRecord::Base.current_role #=> :writing
180
+ # end
181
+ #
182
+ # ActiveRecord::Base.connected_to(role: :reading) do
183
+ # ActiveRecord::Base.current_role #=> :reading
184
+ # end
185
+ def self.current_role
186
+ if ActiveRecord::Base.legacy_connection_handling
187
+ connection_handlers.key(connection_handler) || default_role
188
+ else
189
+ connected_to_stack.reverse_each do |hash|
190
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
191
+ return hash[:role] if hash[:role] && hash[:klasses].include?(abstract_base_class)
192
+ end
193
+
194
+ default_role
195
+ end
196
+ end
197
+
198
+ # Returns the symbol representing the current connected shard.
199
+ #
200
+ # ActiveRecord::Base.connected_to(role: :reading) do
201
+ # ActiveRecord::Base.current_shard #=> :default
202
+ # end
203
+ #
204
+ # ActiveRecord::Base.connected_to(role: :writing, shard: :one) do
205
+ # ActiveRecord::Base.current_shard #=> :one
206
+ # end
207
+ def self.current_shard
208
+ connected_to_stack.reverse_each do |hash|
209
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
210
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(abstract_base_class)
211
+ end
212
+
213
+ default_shard
214
+ end
215
+
216
+ # Returns the symbol representing the current setting for
217
+ # preventing writes.
218
+ #
219
+ # ActiveRecord::Base.connected_to(role: :reading) do
220
+ # ActiveRecord::Base.current_preventing_writes #=> true
221
+ # end
222
+ #
223
+ # ActiveRecord::Base.connected_to(role: :writing) do
224
+ # ActiveRecord::Base.current_preventing_writes #=> false
225
+ # end
226
+ def self.current_preventing_writes
227
+ if legacy_connection_handling
228
+ connection_handler.prevent_writes
229
+ else
230
+ connected_to_stack.reverse_each do |hash|
231
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
232
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(abstract_base_class)
233
+ end
234
+
235
+ false
236
+ end
237
+ end
238
+
239
+ def self.connected_to_stack # :nodoc:
240
+ if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
241
+ connected_to_stack
242
+ else
243
+ connected_to_stack = Concurrent::Array.new
244
+ Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
245
+ connected_to_stack
246
+ end
247
+ end
248
+
249
+ def self.abstract_base_class # :nodoc:
250
+ klass = self
251
+
252
+ until klass == Base
253
+ break if klass.abstract_class?
254
+ klass = klass.superclass
255
+ end
256
+
257
+ klass
258
+ end
259
+
260
+ def self.allow_unsafe_raw_sql # :nodoc:
261
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 6.2")
262
+ end
263
+
264
+ def self.allow_unsafe_raw_sql=(value) # :nodoc:
265
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 6.2")
141
266
  end
142
267
 
143
268
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
269
+ self.default_role = writing_role
270
+ self.default_shard = :default
271
+
272
+ def self.strict_loading_violation!(owner:, association:) # :nodoc:
273
+ case action_on_strict_loading_violation
274
+ when :raise
275
+ message = "`#{association}` called on `#{owner}` is marked for strict_loading and cannot be lazily loaded."
276
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
277
+ when :log
278
+ name = "strict_loading_violation.active_record"
279
+ ActiveSupport::Notifications.instrument(name, owner: owner, association: association)
280
+ end
281
+ end
144
282
  end
145
283
 
146
284
  module ClassMethods
@@ -151,16 +289,20 @@ module ActiveRecord
151
289
  def inherited(child_class) # :nodoc:
152
290
  # initialize cache at class definition for thread safety
153
291
  child_class.initialize_find_by_cache
292
+ unless child_class.base_class?
293
+ klass = self
294
+ until klass.base_class?
295
+ klass.initialize_find_by_cache
296
+ klass = klass.superclass
297
+ end
298
+ end
154
299
  super
155
300
  end
156
301
 
157
302
  def find(*ids) # :nodoc:
158
303
  # We don't have cache keys for this stuff yet
159
304
  return super unless ids.length == 1
160
- return super if block_given? ||
161
- primary_key.nil? ||
162
- scope_attributes? ||
163
- columns_hash.key?(inheritance_column) && !base_class?
305
+ return super if block_given? || primary_key.nil? || scope_attributes?
164
306
 
165
307
  id = ids.first
166
308
 
@@ -172,36 +314,41 @@ module ActiveRecord
172
314
  where(key => params.bind).limit(1)
173
315
  }
174
316
 
175
- record = statement.execute([id], connection)&.first
176
- unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
178
- end
179
- record
317
+ statement.execute([id], connection).first ||
318
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
180
319
  end
181
320
 
182
321
  def find_by(*args) # :nodoc:
183
- return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
- columns_hash.key?(inheritance_column) && !base_class?
322
+ return super if scope_attributes?
185
323
 
186
324
  hash = args.first
325
+ return super unless Hash === hash
326
+
327
+ values = hash.values.map! { |value| value.is_a?(Base) ? value.id : value }
328
+ return super if values.any? { |v| StatementCache.unsupported_value?(v) }
329
+
330
+ keys = hash.keys.map! do |key|
331
+ attribute_aliases[name = key.to_s] || begin
332
+ reflection = _reflect_on_association(name)
333
+ if reflection&.belongs_to? && !reflection.polymorphic?
334
+ reflection.join_foreign_key
335
+ elsif reflect_on_aggregation(name)
336
+ return super
337
+ else
338
+ name
339
+ end
340
+ end
341
+ end
187
342
 
188
- return super if !(Hash === hash) || hash.values.any? { |v|
189
- StatementCache.unsupported_value?(v)
190
- }
191
-
192
- # We can't cache Post.find_by(author: david) ...yet
193
- return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
194
-
195
- keys = hash.keys
343
+ return super unless keys.all? { |k| columns_hash.key?(k) }
196
344
 
197
345
  statement = cached_find_by_statement(keys) { |params|
198
- wheres = keys.each_with_object({}) { |param, o|
199
- o[param] = params.bind
200
- }
346
+ wheres = keys.index_with { params.bind }
201
347
  where(wheres).limit(1)
202
348
  }
349
+
203
350
  begin
204
- statement.execute(hash.values, connection)&.first
351
+ statement.execute(values, connection).first
205
352
  rescue TypeError
206
353
  raise ActiveRecord::StatementInvalid
207
354
  end
@@ -264,14 +411,13 @@ module ActiveRecord
264
411
  # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
265
412
  # end
266
413
  def arel_table # :nodoc:
267
- @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
414
+ @arel_table ||= Arel::Table.new(table_name, klass: self)
268
415
  end
269
416
 
270
417
  def arel_attribute(name, table = arel_table) # :nodoc:
271
- name = name.to_s
272
- name = attribute_aliases[name] || name
273
418
  table[name]
274
419
  end
420
+ deprecate :arel_attribute
275
421
 
276
422
  def predicate_builder # :nodoc:
277
423
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
@@ -285,18 +431,17 @@ module ActiveRecord
285
431
  false
286
432
  end
287
433
 
288
- private
289
- def cached_find_by_statement(key, &block)
290
- cache = @find_by_statement_cache[connection.prepared_statements]
291
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
292
- end
434
+ def cached_find_by_statement(key, &block) # :nodoc:
435
+ cache = @find_by_statement_cache[connection.prepared_statements]
436
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
437
+ end
293
438
 
439
+ private
294
440
  def relation
295
441
  relation = Relation.create(self)
296
442
 
297
443
  if finder_needs_type_condition? && !ignore_default_scope?
298
444
  relation.where!(type_condition)
299
- relation.create_with!(inheritance_column.to_s => sti_name)
300
445
  else
301
446
  relation
302
447
  end
@@ -400,9 +545,9 @@ module ActiveRecord
400
545
  _run_initialize_callbacks
401
546
 
402
547
  @new_record = true
548
+ @previously_new_record = false
403
549
  @destroyed = false
404
550
  @_start_transaction_state = nil
405
- @transaction_state = nil
406
551
 
407
552
  super
408
553
  end
@@ -462,7 +607,6 @@ module ActiveRecord
462
607
 
463
608
  # Returns +true+ if the attributes hash has been frozen.
464
609
  def frozen?
465
- sync_with_transaction_state if @transaction_state&.finalized?
466
610
  @attributes.frozen?
467
611
  end
468
612
 
@@ -483,12 +627,27 @@ module ActiveRecord
483
627
  false
484
628
  end
485
629
 
486
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
487
- # attributes will be marked as read only since they cannot be saved.
630
+ # Returns +true+ if the record is read only.
488
631
  def readonly?
489
632
  @readonly
490
633
  end
491
634
 
635
+ # Returns +true+ if the record is in strict_loading mode.
636
+ def strict_loading?
637
+ @strict_loading
638
+ end
639
+
640
+ # Sets the record to strict_loading mode. This will raise an error
641
+ # if the record tries to lazily load an association.
642
+ #
643
+ # user = User.first
644
+ # user.strict_loading!
645
+ # user.comments.to_a
646
+ # => ActiveRecord::StrictLoadingViolationError
647
+ def strict_loading!
648
+ @strict_loading = true
649
+ end
650
+
492
651
  # Marks this record as read only.
493
652
  def readonly!
494
653
  @readonly = true
@@ -504,7 +663,7 @@ module ActiveRecord
504
663
  # allocated but not initialized.
505
664
  inspection = if defined?(@attributes) && @attributes
506
665
  self.class.attribute_names.collect do |name|
507
- if has_attribute?(name)
666
+ if _has_attribute?(name)
508
667
  attr = _read_attribute(name)
509
668
  value = if attr.nil?
510
669
  attr.inspect
@@ -528,7 +687,7 @@ module ActiveRecord
528
687
  return super if custom_inspect_method_defined?
529
688
  pp.object_address_group(self) do
530
689
  if defined?(@attributes) && @attributes
531
- attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
690
+ attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
532
691
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
533
692
  pp.breakable " "
534
693
  pp.group(1) do
@@ -549,7 +708,12 @@ module ActiveRecord
549
708
 
550
709
  # Returns a hash of the given methods with their names as keys and returned values as values.
551
710
  def slice(*methods)
552
- Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
711
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
712
+ end
713
+
714
+ # Returns an array of the values returned by the given methods.
715
+ def values_at(*methods)
716
+ methods.flatten.map! { |method| public_send(method) }
553
717
  end
554
718
 
555
719
  private
@@ -568,11 +732,12 @@ module ActiveRecord
568
732
  def init_internals
569
733
  @primary_key = self.class.primary_key
570
734
  @readonly = false
735
+ @previously_new_record = false
571
736
  @destroyed = false
572
737
  @marked_for_destruction = false
573
738
  @destroyed_by_association = nil
574
739
  @_start_transaction_state = nil
575
- @transaction_state = nil
740
+ @strict_loading = self.class.strict_loading_by_default
576
741
 
577
742
  self.class.define_attribute_methods
578
743
  end