activerecord 6.0.3 → 6.1.3

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 (246) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +968 -682
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -14
  6. data/lib/active_record/aggregations.rb +5 -5
  7. data/lib/active_record/association_relation.rb +30 -12
  8. data/lib/active_record/associations.rb +118 -11
  9. data/lib/active_record/associations/alias_tracker.rb +19 -15
  10. data/lib/active_record/associations/association.rb +44 -28
  11. data/lib/active_record/associations/association_scope.rb +19 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +22 -8
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  14. data/lib/active_record/associations/builder/association.rb +32 -5
  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 +39 -16
  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 +64 -54
  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 +11 -5
  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 +33 -8
  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 +152 -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 +191 -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 +66 -23
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  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 +116 -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 +228 -83
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +80 -32
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +54 -72
  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 +18 -3
  70. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -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 +5 -2
  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 +73 -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 +10 -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/money.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -5
  90. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  91. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  92. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  95. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  96. data/lib/active_record/connection_adapters/postgresql_adapter.rb +74 -63
  97. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  98. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  99. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +31 -6
  100. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  101. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  102. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +37 -4
  103. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +49 -50
  104. data/lib/active_record/connection_handling.rb +218 -71
  105. data/lib/active_record/core.rb +245 -61
  106. data/lib/active_record/database_configurations.rb +124 -85
  107. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  108. data/lib/active_record/database_configurations/database_config.rb +52 -9
  109. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  110. data/lib/active_record/database_configurations/url_config.rb +15 -40
  111. data/lib/active_record/delegated_type.rb +209 -0
  112. data/lib/active_record/destroy_association_async_job.rb +36 -0
  113. data/lib/active_record/enum.rb +82 -38
  114. data/lib/active_record/errors.rb +47 -12
  115. data/lib/active_record/explain.rb +9 -4
  116. data/lib/active_record/explain_subscriber.rb +1 -1
  117. data/lib/active_record/fixture_set/file.rb +10 -17
  118. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  119. data/lib/active_record/fixture_set/render_context.rb +1 -1
  120. data/lib/active_record/fixture_set/table_row.rb +2 -2
  121. data/lib/active_record/fixtures.rb +58 -9
  122. data/lib/active_record/gem_version.rb +1 -1
  123. data/lib/active_record/inheritance.rb +40 -18
  124. data/lib/active_record/insert_all.rb +35 -6
  125. data/lib/active_record/integration.rb +3 -5
  126. data/lib/active_record/internal_metadata.rb +16 -7
  127. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  128. data/lib/active_record/locking/optimistic.rb +33 -17
  129. data/lib/active_record/locking/pessimistic.rb +6 -2
  130. data/lib/active_record/log_subscriber.rb +27 -8
  131. data/lib/active_record/middleware/database_selector.rb +4 -1
  132. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  133. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  134. data/lib/active_record/migration.rb +114 -84
  135. data/lib/active_record/migration/command_recorder.rb +47 -27
  136. data/lib/active_record/migration/compatibility.rb +68 -17
  137. data/lib/active_record/model_schema.rb +117 -13
  138. data/lib/active_record/nested_attributes.rb +2 -3
  139. data/lib/active_record/no_touching.rb +1 -1
  140. data/lib/active_record/persistence.rb +50 -45
  141. data/lib/active_record/query_cache.rb +15 -5
  142. data/lib/active_record/querying.rb +11 -6
  143. data/lib/active_record/railtie.rb +64 -44
  144. data/lib/active_record/railties/console_sandbox.rb +2 -4
  145. data/lib/active_record/railties/databases.rake +276 -99
  146. data/lib/active_record/readonly_attributes.rb +4 -0
  147. data/lib/active_record/reflection.rb +71 -57
  148. data/lib/active_record/relation.rb +95 -67
  149. data/lib/active_record/relation/batches.rb +38 -31
  150. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  151. data/lib/active_record/relation/calculations.rb +101 -44
  152. data/lib/active_record/relation/delegation.rb +2 -1
  153. data/lib/active_record/relation/finder_methods.rb +45 -15
  154. data/lib/active_record/relation/from_clause.rb +1 -1
  155. data/lib/active_record/relation/merger.rb +27 -25
  156. data/lib/active_record/relation/predicate_builder.rb +61 -38
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  158. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  159. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  160. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  161. data/lib/active_record/relation/query_methods.rb +333 -195
  162. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  163. data/lib/active_record/relation/spawn_methods.rb +8 -7
  164. data/lib/active_record/relation/where_clause.rb +107 -60
  165. data/lib/active_record/result.rb +41 -33
  166. data/lib/active_record/runtime_registry.rb +2 -2
  167. data/lib/active_record/sanitization.rb +6 -17
  168. data/lib/active_record/schema_dumper.rb +34 -4
  169. data/lib/active_record/schema_migration.rb +2 -8
  170. data/lib/active_record/scoping/named.rb +6 -17
  171. data/lib/active_record/secure_token.rb +16 -8
  172. data/lib/active_record/serialization.rb +5 -3
  173. data/lib/active_record/signed_id.rb +116 -0
  174. data/lib/active_record/statement_cache.rb +20 -4
  175. data/lib/active_record/store.rb +2 -2
  176. data/lib/active_record/suppressor.rb +2 -2
  177. data/lib/active_record/table_metadata.rb +42 -51
  178. data/lib/active_record/tasks/database_tasks.rb +140 -113
  179. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  180. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  181. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  182. data/lib/active_record/test_databases.rb +5 -4
  183. data/lib/active_record/test_fixtures.rb +37 -16
  184. data/lib/active_record/timestamp.rb +4 -6
  185. data/lib/active_record/touch_later.rb +21 -21
  186. data/lib/active_record/transactions.rb +19 -66
  187. data/lib/active_record/type.rb +8 -1
  188. data/lib/active_record/type/serialized.rb +6 -2
  189. data/lib/active_record/type/time.rb +10 -0
  190. data/lib/active_record/type_caster/connection.rb +0 -1
  191. data/lib/active_record/type_caster/map.rb +8 -5
  192. data/lib/active_record/validations.rb +1 -0
  193. data/lib/active_record/validations/numericality.rb +35 -0
  194. data/lib/active_record/validations/uniqueness.rb +24 -4
  195. data/lib/arel.rb +5 -13
  196. data/lib/arel/attributes/attribute.rb +4 -0
  197. data/lib/arel/collectors/bind.rb +5 -0
  198. data/lib/arel/collectors/composite.rb +8 -0
  199. data/lib/arel/collectors/sql_string.rb +7 -0
  200. data/lib/arel/collectors/substitute_binds.rb +7 -0
  201. data/lib/arel/nodes.rb +3 -1
  202. data/lib/arel/nodes/binary.rb +82 -8
  203. data/lib/arel/nodes/bind_param.rb +8 -0
  204. data/lib/arel/nodes/casted.rb +21 -9
  205. data/lib/arel/nodes/equality.rb +6 -9
  206. data/lib/arel/nodes/grouping.rb +3 -0
  207. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  208. data/lib/arel/nodes/in.rb +8 -1
  209. data/lib/arel/nodes/infix_operation.rb +13 -1
  210. data/lib/arel/nodes/join_source.rb +1 -1
  211. data/lib/arel/nodes/node.rb +7 -6
  212. data/lib/arel/nodes/ordering.rb +27 -0
  213. data/lib/arel/nodes/sql_literal.rb +3 -0
  214. data/lib/arel/nodes/table_alias.rb +7 -3
  215. data/lib/arel/nodes/unary.rb +0 -1
  216. data/lib/arel/predications.rb +12 -18
  217. data/lib/arel/select_manager.rb +1 -2
  218. data/lib/arel/table.rb +13 -5
  219. data/lib/arel/visitors.rb +0 -7
  220. data/lib/arel/visitors/dot.rb +14 -2
  221. data/lib/arel/visitors/mysql.rb +11 -1
  222. data/lib/arel/visitors/postgresql.rb +15 -4
  223. data/lib/arel/visitors/to_sql.rb +89 -78
  224. data/lib/rails/generators/active_record/migration.rb +6 -1
  225. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  226. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  227. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  228. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  229. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  230. metadata +28 -29
  231. data/lib/active_record/advisory_lock_base.rb +0 -18
  232. data/lib/active_record/attribute_decorators.rb +0 -88
  233. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  234. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  235. data/lib/active_record/define_callbacks.rb +0 -22
  236. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  237. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  238. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  239. data/lib/arel/attributes.rb +0 -22
  240. data/lib/arel/visitors/depth_first.rb +0 -203
  241. data/lib/arel/visitors/ibm_db.rb +0 -34
  242. data/lib/arel/visitors/informix.rb +0 -62
  243. data/lib/arel/visitors/mssql.rb +0 -156
  244. data/lib/arel/visitors/oracle.rb +0 -158
  245. data/lib/arel/visitors/oracle12.rb +0 -65
  246. 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,56 @@ 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
+ self.connection_class = true
95
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
96
+ end
97
+
98
+ shards.each do |shard, database_keys|
99
+ database_keys.each do |role, database_key|
100
+ db_config, owner_name = resolve_config_for_connection(database_key)
101
+ handler = lookup_connection_handler(role.to_sym)
102
+
103
+ self.connection_class = true
104
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
105
+ end
76
106
  end
77
107
 
78
108
  connections
79
109
  end
80
110
 
81
- # Connects to a database or role (ex writing, reading, or another
82
- # custom role) for the duration of the block.
111
+ # Connects to a role (ex writing, reading or a custom role) and/or
112
+ # shard for the duration of the block. At the end of the block the
113
+ # connection will be returned to the original role / shard.
83
114
  #
84
- # If a role is passed, Active Record will look up the connection
85
- # based on the requested role:
115
+ # If only a role is passed, Active Record will look up the connection
116
+ # based on the requested role. If a non-established role is requested
117
+ # an +ActiveRecord::ConnectionNotEstablished+ error will be raised:
86
118
  #
87
119
  # ActiveRecord::Base.connected_to(role: :writing) do
88
120
  # Dog.create! # creates dog using dog writing connection
@@ -92,93 +124,155 @@ module ActiveRecord
92
124
  # Dog.create! # throws exception because we're on a replica
93
125
  # end
94
126
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
- # # raises exception due to non-existent role
97
- # end
127
+ # When swapping to a shard, the role must be passed as well. If a non-existent
128
+ # shard is passed, an +ActiveRecord::ConnectionNotEstablished+ error will be
129
+ # raised.
130
+ #
131
+ # When a shard and role is passed, Active Record will first lookup the role,
132
+ # and then look up the connection by shard key.
98
133
  #
99
- # The `database` kwarg is deprecated in 6.1 and will be removed in 6.2
134
+ # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
135
+ # Dog.first # finds first Dog record stored on the shard one replica
136
+ # end
100
137
  #
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."
138
+ # The database kwarg is deprecated and will be removed in 6.2.0 without replacement.
139
+ def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
140
+ if legacy_connection_handling
141
+ if self != Base
142
+ raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling."
143
+ end
144
+ else
145
+ if self != Base && !abstract_class
146
+ raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
147
+ end
148
+
149
+ if name != connection_specification_name && !primary_class?
150
+ raise NotImplementedError, "calling `connected_to` is only allowed on the abstract class that established the connection."
151
+ end
152
+ end
153
+
154
+ if database && (role || shard)
155
+ raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
106
156
  elsif database
157
+ ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
158
+
107
159
  if database.is_a?(Hash)
108
160
  role, database = database.first
109
161
  role = role.to_sym
110
162
  end
111
163
 
112
- config_hash = resolve_config_for_connection(database)
164
+ db_config, owner_name = resolve_config_for_connection(database)
113
165
  handler = lookup_connection_handler(role)
114
166
 
115
- handler.establish_connection(config_hash)
167
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
116
168
 
117
169
  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)
170
+ elsif role || shard
171
+ unless role
172
+ raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
123
173
  end
174
+
175
+ with_role_and_shard(role, shard, prevent_writes, &blk)
124
176
  else
125
- raise ArgumentError, "must provide a `database` or a `role`."
177
+ raise ArgumentError, "must provide a `shard` and/or `role`."
126
178
  end
127
179
  end
128
180
 
129
- # Returns true if role is the current connected role.
181
+ # Connects a role and/or shard to the provided connection names. Optionally +prevent_writes+
182
+ # can be passed to block writes on a connection. +reading+ will automatically set
183
+ # +prevent_writes+ to true.
130
184
  #
131
- # ActiveRecord::Base.connected_to(role: :writing) do
132
- # ActiveRecord::Base.connected_to?(role: :writing) #=> true
133
- # ActiveRecord::Base.connected_to?(role: :reading) #=> false
185
+ # +connected_to_many+ is an alternative to deeply nested +connected_to+ blocks.
186
+ #
187
+ # Usage:
188
+ #
189
+ # ActiveRecord::Base.connected_to_many(AnimalsRecord, MealsRecord, role: :reading) do
190
+ # Dog.first # Read from animals replica
191
+ # Dinner.first # Read from meals replica
192
+ # Person.first # Read from primary writer
134
193
  # end
135
- def connected_to?(role:)
136
- current_role == role.to_sym
194
+ def connected_to_many(*classes, role:, shard: nil, prevent_writes: false)
195
+ classes = classes.flatten
196
+
197
+ if legacy_connection_handling
198
+ raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
199
+ end
200
+
201
+ if self != Base || classes.include?(Base)
202
+ raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
203
+ end
204
+
205
+ prevent_writes = true if role == reading_role
206
+
207
+ connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes }
208
+ yield
209
+ ensure
210
+ connected_to_stack.pop
137
211
  end
138
212
 
139
- # Returns the symbol representing the current connected role.
213
+ # Use a specified connection.
140
214
  #
141
- # ActiveRecord::Base.connected_to(role: :writing) do
142
- # ActiveRecord::Base.current_role #=> :writing
143
- # end
215
+ # This method is useful for ensuring that a specific connection is
216
+ # being used. For example, when booting a console in readonly mode.
144
217
  #
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
218
+ # It is not recommended to use this method in a request since it
219
+ # does not yield to a block like +connected_to+.
220
+ def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
221
+ if legacy_connection_handling
222
+ raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
223
+ end
151
224
 
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
225
+ prevent_writes = true if role == reading_role
156
226
 
157
- def with_handler(handler_key, &blk) # :nodoc:
158
- handler = lookup_connection_handler(handler_key)
159
- swap_connection_handler(handler, &blk)
227
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
160
228
  end
161
229
 
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
230
+ # Prevent writing to the database regardless of role.
231
+ #
232
+ # In some cases you may want to prevent writes to the database
233
+ # even if you are on a database that can write. +while_preventing_writes+
234
+ # will prevent writes to the database for the duration of the block.
235
+ #
236
+ # This method does not provide the same protection as a readonly
237
+ # user and is meant to be a safeguard against accidental writes.
238
+ #
239
+ # See +READ_QUERY+ for the queries that are blocked by this
240
+ # method.
241
+ def while_preventing_writes(enabled = true, &block)
242
+ if legacy_connection_handling
243
+ connection_handler.while_preventing_writes(enabled, &block)
244
+ else
245
+ connected_to(role: current_role, prevent_writes: enabled, &block)
246
+ end
247
+ end
168
248
 
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
249
+ # Returns true if role is the current connected role.
250
+ #
251
+ # ActiveRecord::Base.connected_to(role: :writing) do
252
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
253
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
254
+ # end
255
+ def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
256
+ current_role == role.to_sym && current_shard == shard.to_sym
257
+ end
172
258
 
173
- config_hash
259
+ def lookup_connection_handler(handler_key) # :nodoc:
260
+ if ActiveRecord::Base.legacy_connection_handling
261
+ handler_key ||= ActiveRecord::Base.writing_role
262
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
263
+ else
264
+ ActiveRecord::Base.connection_handler
265
+ end
174
266
  end
175
267
 
176
268
  # Clears the query cache for all connections associated with the current thread.
177
269
  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?
270
+ if ActiveRecord::Base.legacy_connection_handling
271
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
272
+ clear_on_handler(handler)
181
273
  end
274
+ else
275
+ clear_on_handler(ActiveRecord::Base.connection_handler)
182
276
  end
183
277
  end
184
278
 
@@ -191,10 +285,10 @@ module ActiveRecord
191
285
 
192
286
  attr_writer :connection_specification_name
193
287
 
194
- # Return the specification name from the current class or its parent.
288
+ # Return the connection specification name from the current class or its parent.
195
289
  def connection_specification_name
196
290
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
197
- return self == Base ? "primary" : superclass.connection_specification_name
291
+ return self == Base ? Base.name : superclass.connection_specification_name
198
292
  end
199
293
  @connection_specification_name
200
294
  end
@@ -210,20 +304,32 @@ module ActiveRecord
210
304
  #
211
305
  # Please use only for reading.
212
306
  def connection_config
213
- connection_pool.spec.config
307
+ connection_pool.db_config.configuration_hash
308
+ end
309
+ deprecate connection_config: "Use connection_db_config instead"
310
+
311
+ # Returns the db_config object from the associated connection:
312
+ #
313
+ # ActiveRecord::Base.connection_db_config
314
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
315
+ # @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>
316
+ #
317
+ # Use only for reading.
318
+ def connection_db_config
319
+ connection_pool.db_config
214
320
  end
215
321
 
216
322
  def connection_pool
217
- connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
323
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
218
324
  end
219
325
 
220
326
  def retrieve_connection
221
- connection_handler.retrieve_connection(connection_specification_name)
327
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
222
328
  end
223
329
 
224
330
  # Returns +true+ if Active Record is connected.
225
331
  def connected?
226
- connection_handler.connected?(connection_specification_name)
332
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
227
333
  end
228
334
 
229
335
  def remove_connection(name = nil)
@@ -231,11 +337,11 @@ module ActiveRecord
231
337
  # if removing a connection that has a pool, we reset the
232
338
  # connection_specification_name so it will use the parent
233
339
  # pool.
234
- if connection_handler.retrieve_connection_pool(name)
340
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
235
341
  self.connection_specification_name = nil
236
342
  end
237
343
 
238
- connection_handler.remove_connection(name)
344
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
239
345
  end
240
346
 
241
347
  def clear_cache! # :nodoc:
@@ -246,6 +352,47 @@ module ActiveRecord
246
352
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
247
353
 
248
354
  private
355
+ def clear_on_handler(handler)
356
+ handler.all_connection_pools.each do |pool|
357
+ pool.connection.clear_query_cache if pool.active_connection?
358
+ end
359
+ end
360
+
361
+ def resolve_config_for_connection(config_or_env)
362
+ raise "Anonymous class is not allowed." unless name
363
+
364
+ owner_name = primary_class? ? Base.name : name
365
+ self.connection_specification_name = owner_name
366
+
367
+ db_config = Base.configurations.resolve(config_or_env)
368
+ [db_config, self]
369
+ end
370
+
371
+ def with_handler(handler_key, &blk)
372
+ handler = lookup_connection_handler(handler_key)
373
+ swap_connection_handler(handler, &blk)
374
+ end
375
+
376
+ def with_role_and_shard(role, shard, prevent_writes)
377
+ prevent_writes = true if role == reading_role
378
+
379
+ if ActiveRecord::Base.legacy_connection_handling
380
+ with_handler(role.to_sym) do
381
+ connection_handler.while_preventing_writes(prevent_writes) do
382
+ self.connected_to_stack << { shard: shard, klasses: [self] }
383
+ yield
384
+ end
385
+ end
386
+ else
387
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
388
+ return_value = yield
389
+ return_value.load if return_value.is_a? ActiveRecord::Relation
390
+ return_value
391
+ end
392
+ ensure
393
+ self.connected_to_stack.pop
394
+ end
395
+
249
396
  def swap_connection_handler(handler, &blk) # :nodoc:
250
397
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
251
398
  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,182 @@ 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
124
135
 
125
- mattr_accessor :connection_handlers, instance_accessor: false, default: {}
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
141
+
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
+ unless legacy_connection_handling
170
+ raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
171
+ end
172
+
173
+ @@connection_handlers ||= {}
174
+ end
175
+
176
+ def self.connection_handlers=(handlers)
177
+ unless legacy_connection_handling
178
+ raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers."
179
+ end
180
+
181
+ @@connection_handlers = handlers
182
+ end
183
+
184
+ # Returns the symbol representing the current connected role.
185
+ #
186
+ # ActiveRecord::Base.connected_to(role: :writing) do
187
+ # ActiveRecord::Base.current_role #=> :writing
188
+ # end
189
+ #
190
+ # ActiveRecord::Base.connected_to(role: :reading) do
191
+ # ActiveRecord::Base.current_role #=> :reading
192
+ # end
193
+ def self.current_role
194
+ if ActiveRecord::Base.legacy_connection_handling
195
+ connection_handlers.key(connection_handler) || default_role
196
+ else
197
+ connected_to_stack.reverse_each do |hash|
198
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
200
+ end
201
+
202
+ default_role
203
+ end
204
+ end
205
+
206
+ # Returns the symbol representing the current connected shard.
207
+ #
208
+ # ActiveRecord::Base.connected_to(role: :reading) do
209
+ # ActiveRecord::Base.current_shard #=> :default
210
+ # end
211
+ #
212
+ # ActiveRecord::Base.connected_to(role: :writing, shard: :one) do
213
+ # ActiveRecord::Base.current_shard #=> :one
214
+ # end
215
+ def self.current_shard
216
+ connected_to_stack.reverse_each do |hash|
217
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
218
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
219
+ end
220
+
221
+ default_shard
222
+ end
223
+
224
+ # Returns the symbol representing the current setting for
225
+ # preventing writes.
226
+ #
227
+ # ActiveRecord::Base.connected_to(role: :reading) do
228
+ # ActiveRecord::Base.current_preventing_writes #=> true
229
+ # end
230
+ #
231
+ # ActiveRecord::Base.connected_to(role: :writing) do
232
+ # ActiveRecord::Base.current_preventing_writes #=> false
233
+ # end
234
+ def self.current_preventing_writes
235
+ if legacy_connection_handling
236
+ connection_handler.prevent_writes
237
+ else
238
+ connected_to_stack.reverse_each do |hash|
239
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
240
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
241
+ end
242
+
243
+ false
244
+ end
245
+ end
246
+
247
+ def self.connected_to_stack # :nodoc:
248
+ if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
249
+ connected_to_stack
250
+ else
251
+ connected_to_stack = Concurrent::Array.new
252
+ Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
253
+ connected_to_stack
254
+ end
255
+ end
256
+
257
+ def self.connection_class=(b) # :nodoc:
258
+ @connection_class = b
259
+ end
260
+
261
+ def self.connection_class # :nodoc
262
+ @connection_class ||= false
263
+ end
264
+
265
+ def self.connection_class? # :nodoc:
266
+ self.connection_class
267
+ end
268
+
269
+ def self.connection_classes # :nodoc:
270
+ klass = self
271
+
272
+ until klass == Base
273
+ break if klass.connection_class?
274
+ klass = klass.superclass
275
+ end
276
+
277
+ klass
278
+ end
279
+
280
+ def self.allow_unsafe_raw_sql # :nodoc:
281
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 6.2")
282
+ end
283
+
284
+ def self.allow_unsafe_raw_sql=(value) # :nodoc:
285
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 6.2")
141
286
  end
142
287
 
143
288
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
289
+ self.default_role = writing_role
290
+ self.default_shard = :default
291
+
292
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
293
+ case action_on_strict_loading_violation
294
+ when :raise
295
+ message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
296
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
297
+ when :log
298
+ name = "strict_loading_violation.active_record"
299
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
300
+ end
301
+ end
144
302
  end
145
303
 
146
304
  module ClassMethods
@@ -151,16 +309,20 @@ module ActiveRecord
151
309
  def inherited(child_class) # :nodoc:
152
310
  # initialize cache at class definition for thread safety
153
311
  child_class.initialize_find_by_cache
312
+ unless child_class.base_class?
313
+ klass = self
314
+ until klass.base_class?
315
+ klass.initialize_find_by_cache
316
+ klass = klass.superclass
317
+ end
318
+ end
154
319
  super
155
320
  end
156
321
 
157
322
  def find(*ids) # :nodoc:
158
323
  # We don't have cache keys for this stuff yet
159
324
  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?
325
+ return super if block_given? || primary_key.nil? || scope_attributes?
164
326
 
165
327
  id = ids.first
166
328
 
@@ -172,36 +334,47 @@ module ActiveRecord
172
334
  where(key => params.bind).limit(1)
173
335
  }
174
336
 
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
337
+ statement.execute([id], connection).first ||
338
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
180
339
  end
181
340
 
182
341
  def find_by(*args) # :nodoc:
183
- return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
- columns_hash.key?(inheritance_column) && !base_class?
342
+ return super if scope_attributes?
185
343
 
186
344
  hash = args.first
345
+ return super unless Hash === hash
187
346
 
188
- return super if !(Hash === hash) || hash.values.any? { |v|
189
- StatementCache.unsupported_value?(v)
190
- }
347
+ hash = hash.each_with_object({}) do |(key, value), h|
348
+ key = key.to_s
349
+ key = attribute_aliases[key] || key
191
350
 
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) }
351
+ return super if reflect_on_aggregation(key)
194
352
 
195
- keys = hash.keys
353
+ reflection = _reflect_on_association(key)
196
354
 
355
+ if !reflection
356
+ value = value.id if value.respond_to?(:id)
357
+ elsif reflection.belongs_to? && !reflection.polymorphic?
358
+ key = reflection.join_foreign_key
359
+ pkey = reflection.join_primary_key
360
+ value = value.public_send(pkey) if value.respond_to?(pkey)
361
+ end
362
+
363
+ if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
364
+ return super
365
+ end
366
+
367
+ h[key] = value
368
+ end
369
+
370
+ keys = hash.keys
197
371
  statement = cached_find_by_statement(keys) { |params|
198
- wheres = keys.each_with_object({}) { |param, o|
199
- o[param] = params.bind
200
- }
372
+ wheres = keys.index_with { params.bind }
201
373
  where(wheres).limit(1)
202
374
  }
375
+
203
376
  begin
204
- statement.execute(hash.values, connection)&.first
377
+ statement.execute(hash.values, connection).first
205
378
  rescue TypeError
206
379
  raise ActiveRecord::StatementInvalid
207
380
  end
@@ -264,14 +437,13 @@ module ActiveRecord
264
437
  # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
265
438
  # end
266
439
  def arel_table # :nodoc:
267
- @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
440
+ @arel_table ||= Arel::Table.new(table_name, klass: self)
268
441
  end
269
442
 
270
443
  def arel_attribute(name, table = arel_table) # :nodoc:
271
- name = name.to_s
272
- name = attribute_aliases[name] || name
273
444
  table[name]
274
445
  end
446
+ deprecate :arel_attribute
275
447
 
276
448
  def predicate_builder # :nodoc:
277
449
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
@@ -285,18 +457,17 @@ module ActiveRecord
285
457
  false
286
458
  end
287
459
 
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
460
+ def cached_find_by_statement(key, &block) # :nodoc:
461
+ cache = @find_by_statement_cache[connection.prepared_statements]
462
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
463
+ end
293
464
 
465
+ private
294
466
  def relation
295
467
  relation = Relation.create(self)
296
468
 
297
469
  if finder_needs_type_condition? && !ignore_default_scope?
298
470
  relation.where!(type_condition)
299
- relation.create_with!(inheritance_column.to_s => sti_name)
300
471
  else
301
472
  relation
302
473
  end
@@ -400,9 +571,9 @@ module ActiveRecord
400
571
  _run_initialize_callbacks
401
572
 
402
573
  @new_record = true
574
+ @previously_new_record = false
403
575
  @destroyed = false
404
576
  @_start_transaction_state = nil
405
- @transaction_state = nil
406
577
 
407
578
  super
408
579
  end
@@ -462,7 +633,6 @@ module ActiveRecord
462
633
 
463
634
  # Returns +true+ if the attributes hash has been frozen.
464
635
  def frozen?
465
- sync_with_transaction_state if @transaction_state&.finalized?
466
636
  @attributes.frozen?
467
637
  end
468
638
 
@@ -483,12 +653,27 @@ module ActiveRecord
483
653
  false
484
654
  end
485
655
 
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.
656
+ # Returns +true+ if the record is read only.
488
657
  def readonly?
489
658
  @readonly
490
659
  end
491
660
 
661
+ # Returns +true+ if the record is in strict_loading mode.
662
+ def strict_loading?
663
+ @strict_loading
664
+ end
665
+
666
+ # Sets the record to strict_loading mode. This will raise an error
667
+ # if the record tries to lazily load an association.
668
+ #
669
+ # user = User.first
670
+ # user.strict_loading!
671
+ # user.comments.to_a
672
+ # => ActiveRecord::StrictLoadingViolationError
673
+ def strict_loading!
674
+ @strict_loading = true
675
+ end
676
+
492
677
  # Marks this record as read only.
493
678
  def readonly!
494
679
  @readonly = true
@@ -504,15 +689,8 @@ module ActiveRecord
504
689
  # allocated but not initialized.
505
690
  inspection = if defined?(@attributes) && @attributes
506
691
  self.class.attribute_names.collect do |name|
507
- if has_attribute?(name)
508
- attr = _read_attribute(name)
509
- value = if attr.nil?
510
- attr.inspect
511
- else
512
- attr = format_for_inspect(attr)
513
- inspection_filter.filter_param(name, attr)
514
- end
515
- "#{name}: #{value}"
692
+ if _has_attribute?(name)
693
+ "#{name}: #{attribute_for_inspect(name)}"
516
694
  end
517
695
  end.compact.join(", ")
518
696
  else
@@ -528,7 +706,7 @@ module ActiveRecord
528
706
  return super if custom_inspect_method_defined?
529
707
  pp.object_address_group(self) do
530
708
  if defined?(@attributes) && @attributes
531
- attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
709
+ attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
532
710
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
533
711
  pp.breakable " "
534
712
  pp.group(1) do
@@ -549,7 +727,12 @@ module ActiveRecord
549
727
 
550
728
  # Returns a hash of the given methods with their names as keys and returned values as values.
551
729
  def slice(*methods)
552
- Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
730
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
731
+ end
732
+
733
+ # Returns an array of the values returned by the given methods.
734
+ def values_at(*methods)
735
+ methods.flatten.map! { |method| public_send(method) }
553
736
  end
554
737
 
555
738
  private
@@ -568,11 +751,12 @@ module ActiveRecord
568
751
  def init_internals
569
752
  @primary_key = self.class.primary_key
570
753
  @readonly = false
754
+ @previously_new_record = false
571
755
  @destroyed = false
572
756
  @marked_for_destruction = false
573
757
  @destroyed_by_association = nil
574
758
  @_start_transaction_state = nil
575
- @transaction_state = nil
759
+ @strict_loading = self.class.strict_loading_by_default
576
760
 
577
761
  self.class.define_attribute_methods
578
762
  end