activerecord 6.0.6.1 → 6.1.7.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1152 -779
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/active_record/aggregations.rb +5 -5
  6. data/lib/active_record/association_relation.rb +30 -12
  7. data/lib/active_record/associations/alias_tracker.rb +19 -15
  8. data/lib/active_record/associations/association.rb +49 -26
  9. data/lib/active_record/associations/association_scope.rb +18 -20
  10. data/lib/active_record/associations/belongs_to_association.rb +23 -10
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +8 -3
  12. data/lib/active_record/associations/builder/association.rb +32 -5
  13. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  14. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -1
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +32 -18
  20. data/lib/active_record/associations/collection_proxy.rb +12 -5
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -2
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +37 -21
  26. data/lib/active_record/associations/join_dependency/join_part.rb +1 -1
  27. data/lib/active_record/associations/join_dependency.rb +63 -49
  28. data/lib/active_record/associations/preloader/association.rb +14 -8
  29. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  30. data/lib/active_record/associations/preloader.rb +5 -3
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations.rb +118 -11
  33. data/lib/active_record/attribute_assignment.rb +10 -8
  34. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -9
  35. data/lib/active_record/attribute_methods/dirty.rb +1 -11
  36. data/lib/active_record/attribute_methods/primary_key.rb +6 -2
  37. data/lib/active_record/attribute_methods/query.rb +3 -6
  38. data/lib/active_record/attribute_methods/read.rb +8 -11
  39. data/lib/active_record/attribute_methods/serialization.rb +11 -5
  40. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -13
  41. data/lib/active_record/attribute_methods/write.rb +12 -20
  42. data/lib/active_record/attribute_methods.rb +64 -54
  43. data/lib/active_record/attributes.rb +33 -8
  44. data/lib/active_record/autosave_association.rb +47 -30
  45. data/lib/active_record/base.rb +2 -14
  46. data/lib/active_record/callbacks.rb +152 -22
  47. data/lib/active_record/coders/yaml_column.rb +1 -1
  48. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +185 -134
  49. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +66 -23
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +3 -8
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  53. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -116
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +114 -26
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +228 -83
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +92 -33
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +52 -76
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +123 -87
  61. data/lib/active_record/connection_adapters/column.rb +15 -1
  62. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  63. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +24 -24
  65. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  67. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -6
  68. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  69. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  70. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +7 -4
  71. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -12
  73. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  74. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  75. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +14 -53
  77. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  78. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +2 -2
  80. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -2
  84. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +30 -4
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +5 -1
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -29
  90. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +75 -64
  92. data/lib/active_record/connection_adapters/schema_cache.rb +130 -15
  93. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  94. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +32 -5
  95. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -1
  96. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  97. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +36 -3
  98. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +48 -50
  99. data/lib/active_record/connection_adapters.rb +52 -0
  100. data/lib/active_record/connection_handling.rb +218 -71
  101. data/lib/active_record/core.rb +264 -63
  102. data/lib/active_record/database_configurations/connection_url_resolver.rb +99 -0
  103. data/lib/active_record/database_configurations/database_config.rb +52 -9
  104. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  105. data/lib/active_record/database_configurations/url_config.rb +15 -40
  106. data/lib/active_record/database_configurations.rb +125 -85
  107. data/lib/active_record/delegated_type.rb +209 -0
  108. data/lib/active_record/destroy_association_async_job.rb +36 -0
  109. data/lib/active_record/enum.rb +69 -34
  110. data/lib/active_record/errors.rb +47 -12
  111. data/lib/active_record/explain.rb +9 -4
  112. data/lib/active_record/explain_subscriber.rb +1 -1
  113. data/lib/active_record/fixture_set/file.rb +10 -17
  114. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  115. data/lib/active_record/fixture_set/render_context.rb +1 -1
  116. data/lib/active_record/fixture_set/table_row.rb +2 -2
  117. data/lib/active_record/fixtures.rb +58 -9
  118. data/lib/active_record/gem_version.rb +3 -3
  119. data/lib/active_record/inheritance.rb +40 -18
  120. data/lib/active_record/insert_all.rb +38 -5
  121. data/lib/active_record/integration.rb +3 -5
  122. data/lib/active_record/internal_metadata.rb +18 -7
  123. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  124. data/lib/active_record/locking/optimistic.rb +24 -17
  125. data/lib/active_record/locking/pessimistic.rb +6 -2
  126. data/lib/active_record/log_subscriber.rb +27 -8
  127. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  128. data/lib/active_record/middleware/database_selector/resolver.rb +5 -0
  129. data/lib/active_record/middleware/database_selector.rb +4 -1
  130. data/lib/active_record/migration/command_recorder.rb +47 -27
  131. data/lib/active_record/migration/compatibility.rb +72 -18
  132. data/lib/active_record/migration.rb +114 -84
  133. data/lib/active_record/model_schema.rb +89 -14
  134. data/lib/active_record/nested_attributes.rb +2 -3
  135. data/lib/active_record/no_touching.rb +1 -1
  136. data/lib/active_record/persistence.rb +50 -45
  137. data/lib/active_record/query_cache.rb +15 -5
  138. data/lib/active_record/querying.rb +11 -6
  139. data/lib/active_record/railtie.rb +64 -44
  140. data/lib/active_record/railties/console_sandbox.rb +2 -4
  141. data/lib/active_record/railties/databases.rake +279 -101
  142. data/lib/active_record/readonly_attributes.rb +4 -0
  143. data/lib/active_record/reflection.rb +60 -44
  144. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  145. data/lib/active_record/relation/batches.rb +38 -31
  146. data/lib/active_record/relation/calculations.rb +104 -43
  147. data/lib/active_record/relation/finder_methods.rb +44 -14
  148. data/lib/active_record/relation/from_clause.rb +1 -1
  149. data/lib/active_record/relation/merger.rb +20 -23
  150. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  151. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  152. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  153. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  154. data/lib/active_record/relation/predicate_builder.rb +61 -38
  155. data/lib/active_record/relation/query_methods.rb +322 -196
  156. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  157. data/lib/active_record/relation/spawn_methods.rb +8 -7
  158. data/lib/active_record/relation/where_clause.rb +111 -61
  159. data/lib/active_record/relation.rb +100 -81
  160. data/lib/active_record/result.rb +41 -33
  161. data/lib/active_record/runtime_registry.rb +2 -2
  162. data/lib/active_record/sanitization.rb +6 -17
  163. data/lib/active_record/schema_dumper.rb +34 -4
  164. data/lib/active_record/schema_migration.rb +2 -8
  165. data/lib/active_record/scoping/default.rb +1 -3
  166. data/lib/active_record/scoping/named.rb +1 -17
  167. data/lib/active_record/secure_token.rb +16 -8
  168. data/lib/active_record/serialization.rb +5 -3
  169. data/lib/active_record/signed_id.rb +116 -0
  170. data/lib/active_record/statement_cache.rb +20 -4
  171. data/lib/active_record/store.rb +8 -3
  172. data/lib/active_record/suppressor.rb +2 -2
  173. data/lib/active_record/table_metadata.rb +42 -51
  174. data/lib/active_record/tasks/database_tasks.rb +140 -113
  175. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -35
  176. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -26
  177. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -9
  178. data/lib/active_record/test_databases.rb +5 -4
  179. data/lib/active_record/test_fixtures.rb +79 -31
  180. data/lib/active_record/timestamp.rb +4 -6
  181. data/lib/active_record/touch_later.rb +21 -21
  182. data/lib/active_record/transactions.rb +19 -66
  183. data/lib/active_record/type/serialized.rb +6 -2
  184. data/lib/active_record/type.rb +8 -1
  185. data/lib/active_record/type_caster/connection.rb +0 -1
  186. data/lib/active_record/type_caster/map.rb +8 -5
  187. data/lib/active_record/validations/associated.rb +1 -1
  188. data/lib/active_record/validations/numericality.rb +35 -0
  189. data/lib/active_record/validations/uniqueness.rb +24 -4
  190. data/lib/active_record/validations.rb +1 -0
  191. data/lib/active_record.rb +7 -14
  192. data/lib/arel/attributes/attribute.rb +4 -0
  193. data/lib/arel/collectors/bind.rb +5 -0
  194. data/lib/arel/collectors/composite.rb +8 -0
  195. data/lib/arel/collectors/sql_string.rb +7 -0
  196. data/lib/arel/collectors/substitute_binds.rb +7 -0
  197. data/lib/arel/nodes/binary.rb +82 -8
  198. data/lib/arel/nodes/bind_param.rb +8 -0
  199. data/lib/arel/nodes/casted.rb +21 -9
  200. data/lib/arel/nodes/equality.rb +6 -9
  201. data/lib/arel/nodes/grouping.rb +3 -0
  202. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  203. data/lib/arel/nodes/in.rb +8 -1
  204. data/lib/arel/nodes/infix_operation.rb +13 -1
  205. data/lib/arel/nodes/join_source.rb +1 -1
  206. data/lib/arel/nodes/node.rb +7 -6
  207. data/lib/arel/nodes/ordering.rb +27 -0
  208. data/lib/arel/nodes/sql_literal.rb +3 -0
  209. data/lib/arel/nodes/table_alias.rb +7 -3
  210. data/lib/arel/nodes/unary.rb +0 -1
  211. data/lib/arel/nodes.rb +3 -1
  212. data/lib/arel/predications.rb +12 -18
  213. data/lib/arel/select_manager.rb +1 -2
  214. data/lib/arel/table.rb +13 -5
  215. data/lib/arel/visitors/dot.rb +14 -2
  216. data/lib/arel/visitors/mysql.rb +11 -1
  217. data/lib/arel/visitors/postgresql.rb +15 -4
  218. data/lib/arel/visitors/to_sql.rb +89 -78
  219. data/lib/arel/visitors.rb +0 -7
  220. data/lib/arel.rb +5 -13
  221. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  222. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  223. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +3 -3
  224. data/lib/rails/generators/active_record/migration.rb +6 -1
  225. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  226. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  227. metadata +25 -26
  228. data/lib/active_record/advisory_lock_base.rb +0 -18
  229. data/lib/active_record/attribute_decorators.rb +0 -88
  230. data/lib/active_record/connection_adapters/connection_specification.rb +0 -296
  231. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  232. data/lib/active_record/define_callbacks.rb +0 -22
  233. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  234. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  235. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  236. data/lib/arel/attributes.rb +0 -22
  237. data/lib/arel/visitors/depth_first.rb +0 -203
  238. data/lib/arel/visitors/ibm_db.rb +0 -34
  239. data/lib/arel/visitors/informix.rb +0 -62
  240. data/lib/arel/visitors/mssql.rb +0 -156
  241. data/lib/arel/visitors/oracle.rb +0 -158
  242. data/lib/arel/visitors/oracle12.rb +0 -65
  243. 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 Rails 7.0.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 7.0.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