activerecord 6.0.0 → 6.1.4

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 (270) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1178 -600
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -4
  5. data/lib/active_record/aggregations.rb +5 -6
  6. data/lib/active_record/association_relation.rb +30 -10
  7. data/lib/active_record/associations/alias_tracker.rb +19 -16
  8. data/lib/active_record/associations/association.rb +55 -29
  9. data/lib/active_record/associations/association_scope.rb +19 -15
  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 -3
  16. data/lib/active_record/associations/builder/has_many.rb +6 -2
  17. data/lib/active_record/associations/builder/has_one.rb +11 -14
  18. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  19. data/lib/active_record/associations/collection_association.rb +25 -8
  20. data/lib/active_record/associations/collection_proxy.rb +14 -7
  21. data/lib/active_record/associations/foreign_association.rb +13 -0
  22. data/lib/active_record/associations/has_many_association.rb +24 -3
  23. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  24. data/lib/active_record/associations/has_one_association.rb +15 -1
  25. data/lib/active_record/associations/join_dependency/join_association.rb +39 -16
  26. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/preloader/association.rb +51 -25
  29. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/singular_association.rb +1 -1
  32. data/lib/active_record/associations/through_association.rb +1 -1
  33. data/lib/active_record/associations.rb +120 -13
  34. data/lib/active_record/attribute_assignment.rb +10 -9
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  36. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  37. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  38. data/lib/active_record/attribute_methods/query.rb +3 -6
  39. data/lib/active_record/attribute_methods/read.rb +8 -12
  40. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  42. data/lib/active_record/attribute_methods/write.rb +12 -21
  43. data/lib/active_record/attribute_methods.rb +64 -54
  44. data/lib/active_record/attributes.rb +33 -9
  45. data/lib/active_record/autosave_association.rb +63 -44
  46. data/lib/active_record/base.rb +2 -14
  47. data/lib/active_record/callbacks.rb +153 -24
  48. data/lib/active_record/coders/yaml_column.rb +12 -3
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +87 -38
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +5 -10
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +141 -52
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +267 -105
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +94 -36
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +76 -79
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  62. data/lib/active_record/connection_adapters/column.rb +15 -1
  63. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  64. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +35 -0
  65. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  66. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  67. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  68. data/lib/active_record/connection_adapters/mysql/quoting.rb +18 -3
  69. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  70. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  71. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +5 -2
  72. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  73. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  74. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  75. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  76. data/lib/active_record/connection_adapters/pool_manager.rb +47 -0
  77. data/lib/active_record/connection_adapters/postgresql/column.rb +24 -1
  78. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  79. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  81. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +0 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +2 -3
  87. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  91. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  92. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  97. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  98. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  99. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  101. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  102. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  103. data/lib/active_record/connection_adapters/postgresql_adapter.rb +83 -65
  104. data/lib/active_record/connection_adapters/schema_cache.rb +106 -15
  105. data/lib/active_record/connection_adapters/sql_type_metadata.rb +8 -0
  106. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  107. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  108. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  109. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  110. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  111. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  112. data/lib/active_record/connection_adapters.rb +52 -0
  113. data/lib/active_record/connection_handling.rb +219 -81
  114. data/lib/active_record/core.rb +268 -71
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  117. data/lib/active_record/database_configurations/database_config.rb +52 -9
  118. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  119. data/lib/active_record/database_configurations/url_config.rb +15 -41
  120. data/lib/active_record/database_configurations.rb +124 -85
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +80 -38
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +58 -12
  134. data/lib/active_record/gem_version.rb +2 -2
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +43 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +33 -18
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +28 -9
  143. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector.rb +4 -2
  146. data/lib/active_record/migration/command_recorder.rb +53 -45
  147. data/lib/active_record/migration/compatibility.rb +71 -20
  148. data/lib/active_record/migration/join_table.rb +0 -1
  149. data/lib/active_record/migration.rb +115 -85
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/console_sandbox.rb +2 -4
  159. data/lib/active_record/railties/databases.rake +280 -99
  160. data/lib/active_record/readonly_attributes.rb +4 -0
  161. data/lib/active_record/reflection.rb +77 -63
  162. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  163. data/lib/active_record/relation/batches.rb +38 -32
  164. data/lib/active_record/relation/calculations.rb +106 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  170. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  171. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -6
  172. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  173. data/lib/active_record/relation/predicate_builder.rb +59 -40
  174. data/lib/active_record/relation/query_methods.rb +344 -181
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +111 -62
  178. data/lib/active_record/relation.rb +116 -82
  179. data/lib/active_record/result.rb +41 -34
  180. data/lib/active_record/runtime_registry.rb +2 -2
  181. data/lib/active_record/sanitization.rb +6 -17
  182. data/lib/active_record/schema_dumper.rb +34 -4
  183. data/lib/active_record/schema_migration.rb +2 -8
  184. data/lib/active_record/scoping/default.rb +1 -4
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/scoping.rb +0 -1
  187. data/lib/active_record/secure_token.rb +16 -8
  188. data/lib/active_record/serialization.rb +5 -3
  189. data/lib/active_record/signed_id.rb +116 -0
  190. data/lib/active_record/statement_cache.rb +20 -4
  191. data/lib/active_record/store.rb +3 -3
  192. data/lib/active_record/suppressor.rb +2 -2
  193. data/lib/active_record/table_metadata.rb +42 -36
  194. data/lib/active_record/tasks/database_tasks.rb +140 -113
  195. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  196. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  197. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  198. data/lib/active_record/test_databases.rb +5 -4
  199. data/lib/active_record/test_fixtures.rb +79 -16
  200. data/lib/active_record/timestamp.rb +4 -7
  201. data/lib/active_record/touch_later.rb +20 -21
  202. data/lib/active_record/transactions.rb +26 -73
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type.rb +8 -2
  210. data/lib/active_record/type_caster/connection.rb +0 -1
  211. data/lib/active_record/type_caster/map.rb +8 -5
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/active_record/validations.rb +3 -3
  216. data/lib/active_record.rb +7 -13
  217. data/lib/arel/attributes/attribute.rb +4 -0
  218. data/lib/arel/collectors/bind.rb +5 -0
  219. data/lib/arel/collectors/composite.rb +8 -0
  220. data/lib/arel/collectors/sql_string.rb +7 -0
  221. data/lib/arel/collectors/substitute_binds.rb +7 -0
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +76 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/nodes.rb +3 -1
  237. data/lib/arel/predications.rb +17 -24
  238. data/lib/arel/select_manager.rb +1 -2
  239. data/lib/arel/table.rb +13 -5
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/arel/visitors.rb +0 -7
  247. data/lib/arel.rb +15 -12
  248. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  249. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  250. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  251. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  252. data/lib/rails/generators/active_record/migration.rb +6 -2
  253. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  254. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  255. metadata +27 -24
  256. data/lib/active_record/attribute_decorators.rb +0 -90
  257. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  258. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  259. data/lib/active_record/define_callbacks.rb +0 -22
  260. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  261. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  262. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  263. data/lib/arel/attributes.rb +0 -22
  264. data/lib/arel/visitors/depth_first.rb +0 -204
  265. data/lib/arel/visitors/ibm_db.rb +0 -34
  266. data/lib/arel/visitors/informix.rb +0 -62
  267. data/lib/arel/visitors/mssql.rb +0 -157
  268. data/lib/arel/visitors/oracle.rb +0 -159
  269. data/lib/arel/visitors/oracle12.rb +0 -66
  270. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -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,103 +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.
98
130
  #
99
- # For cases where you may want to connect to a database outside of the model,
100
- # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
- # expects a symbol that corresponds to the database key in your config.
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.
102
133
  #
103
- # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
104
- # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
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
105
136
  # end
106
137
  #
107
- # This will connect to a new database for the queries inside the block. By
108
- # default the `:writing` role will be used since all connections must be assigned
109
- # a role. If you would like to use a different role you can pass a hash to database:
110
- #
111
- # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
- # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
- # Dog.run_a_long_query
114
- # end
115
- #
116
- # When using the database key a new connection will be established every time.
117
- def connected_to(database: nil, role: nil, &blk)
118
- if database && role
119
- 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."
120
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
+
121
159
  if database.is_a?(Hash)
122
160
  role, database = database.first
123
161
  role = role.to_sym
124
162
  end
125
163
 
126
- config_hash = resolve_config_for_connection(database)
164
+ db_config, owner_name = resolve_config_for_connection(database)
127
165
  handler = lookup_connection_handler(role)
128
166
 
129
- handler.establish_connection(config_hash)
167
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
130
168
 
131
169
  with_handler(role, &blk)
132
- elsif role
133
- with_handler(role.to_sym, &blk)
170
+ elsif role || shard
171
+ unless role
172
+ raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
173
+ end
174
+
175
+ with_role_and_shard(role, shard, prevent_writes, &blk)
134
176
  else
135
- raise ArgumentError, "must provide a `database` or a `role`."
177
+ raise ArgumentError, "must provide a `shard` and/or `role`."
136
178
  end
137
179
  end
138
180
 
139
- # 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.
140
184
  #
141
- # ActiveRecord::Base.connected_to(role: :writing) do
142
- # ActiveRecord::Base.connected_to?(role: :writing) #=> true
143
- # 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
144
193
  # end
145
- def connected_to?(role:)
146
- 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
147
211
  end
148
212
 
149
- # Returns the symbol representing the current connected role.
213
+ # Use a specified connection.
150
214
  #
151
- # ActiveRecord::Base.connected_to(role: :writing) do
152
- # ActiveRecord::Base.current_role #=> :writing
153
- # 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.
154
217
  #
155
- # ActiveRecord::Base.connected_to(role: :reading) do
156
- # ActiveRecord::Base.current_role #=> :reading
157
- # end
158
- def current_role
159
- connection_handlers.key(connection_handler)
160
- 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
161
224
 
162
- def lookup_connection_handler(handler_key) # :nodoc:
163
- handler_key ||= ActiveRecord::Base.writing_role
164
- connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
165
- end
225
+ prevent_writes = true if role == reading_role
166
226
 
167
- def with_handler(handler_key, &blk) # :nodoc:
168
- handler = lookup_connection_handler(handler_key)
169
- swap_connection_handler(handler, &blk)
227
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
170
228
  end
171
229
 
172
- def resolve_config_for_connection(config_or_env) # :nodoc:
173
- raise "Anonymous class is not allowed." unless name
174
-
175
- config_or_env ||= DEFAULT_ENV.call.to_sym
176
- pool_name = primary_class? ? "primary" : name
177
- 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
178
248
 
179
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
180
- config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
181
- 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
182
258
 
183
- 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
184
266
  end
185
267
 
186
268
  # Clears the query cache for all connections associated with the current thread.
187
269
  def clear_query_caches_for_current_thread
188
- ActiveRecord::Base.connection_handlers.each_value do |handler|
189
- handler.connection_pool_list.each do |pool|
190
- 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)
191
273
  end
274
+ else
275
+ clear_on_handler(ActiveRecord::Base.connection_handler)
192
276
  end
193
277
  end
194
278
 
@@ -201,10 +285,10 @@ module ActiveRecord
201
285
 
202
286
  attr_writer :connection_specification_name
203
287
 
204
- # Return the specification name from the current class or its parent.
288
+ # Return the connection specification name from the current class or its parent.
205
289
  def connection_specification_name
206
290
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
207
- return primary_class? ? "primary" : superclass.connection_specification_name
291
+ return self == Base ? Base.name : superclass.connection_specification_name
208
292
  end
209
293
  @connection_specification_name
210
294
  end
@@ -220,20 +304,32 @@ module ActiveRecord
220
304
  #
221
305
  # Please use only for reading.
222
306
  def connection_config
223
- 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
224
320
  end
225
321
 
226
322
  def connection_pool
227
- 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)
228
324
  end
229
325
 
230
326
  def retrieve_connection
231
- connection_handler.retrieve_connection(connection_specification_name)
327
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
232
328
  end
233
329
 
234
330
  # Returns +true+ if Active Record is connected.
235
331
  def connected?
236
- connection_handler.connected?(connection_specification_name)
332
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
237
333
  end
238
334
 
239
335
  def remove_connection(name = nil)
@@ -241,11 +337,11 @@ module ActiveRecord
241
337
  # if removing a connection that has a pool, we reset the
242
338
  # connection_specification_name so it will use the parent
243
339
  # pool.
244
- if connection_handler.retrieve_connection_pool(name)
340
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
245
341
  self.connection_specification_name = nil
246
342
  end
247
343
 
248
- connection_handler.remove_connection(name)
344
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
249
345
  end
250
346
 
251
347
  def clear_cache! # :nodoc:
@@ -256,10 +352,52 @@ module ActiveRecord
256
352
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
257
353
 
258
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
259
395
 
260
396
  def swap_connection_handler(handler, &blk) # :nodoc:
261
397
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
262
- yield
398
+ return_value = yield
399
+ return_value.load if return_value.is_a? ActiveRecord::Relation
400
+ return_value
263
401
  ensure
264
402
  ActiveRecord::Base.connection_handler = old_handler
265
403
  end