activerecord 5.2.3 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +898 -532
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/aggregations.rb +5 -4
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations/alias_tracker.rb +19 -16
  9. data/lib/active_record/associations/association.rb +95 -42
  10. data/lib/active_record/associations/association_scope.rb +21 -21
  11. data/lib/active_record/associations/belongs_to_association.rb +50 -46
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -5
  13. data/lib/active_record/associations/builder/association.rb +23 -21
  14. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  15. data/lib/active_record/associations/builder/collection_association.rb +10 -19
  16. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  17. data/lib/active_record/associations/builder/has_many.rb +8 -2
  18. data/lib/active_record/associations/builder/has_one.rb +33 -2
  19. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  20. data/lib/active_record/associations/collection_association.rb +31 -29
  21. data/lib/active_record/associations/collection_proxy.rb +25 -21
  22. data/lib/active_record/associations/foreign_association.rb +20 -0
  23. data/lib/active_record/associations/has_many_association.rb +26 -13
  24. data/lib/active_record/associations/has_many_through_association.rb +27 -28
  25. data/lib/active_record/associations/has_one_association.rb +43 -31
  26. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  27. data/lib/active_record/associations/join_dependency/join_association.rb +54 -12
  28. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/preloader/association.rb +71 -43
  31. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  32. data/lib/active_record/associations/preloader.rb +48 -35
  33. data/lib/active_record/associations/singular_association.rb +3 -17
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/associations.rb +133 -25
  36. data/lib/active_record/attribute_assignment.rb +17 -19
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  38. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  40. data/lib/active_record/attribute_methods/query.rb +4 -8
  41. data/lib/active_record/attribute_methods/read.rb +14 -56
  42. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +18 -34
  45. data/lib/active_record/attribute_methods.rb +81 -143
  46. data/lib/active_record/attributes.rb +45 -8
  47. data/lib/active_record/autosave_association.rb +76 -47
  48. data/lib/active_record/base.rb +4 -17
  49. data/lib/active_record/callbacks.rb +158 -43
  50. data/lib/active_record/coders/yaml_column.rb +1 -2
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +293 -132
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +21 -17
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +203 -90
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +381 -146
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +229 -98
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  64. data/lib/active_record/connection_adapters/column.rb +30 -12
  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/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  77. data/lib/active_record/connection_adapters/pool_config.rb +63 -0
  78. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  81. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  82. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  84. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  92. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  93. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  94. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  95. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  96. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  98. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  99. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  100. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  101. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  102. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  103. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  105. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  106. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  107. data/lib/active_record/connection_adapters/postgresql_adapter.rb +222 -112
  108. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  109. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  110. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  111. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  112. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  113. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  114. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +175 -187
  115. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  116. data/lib/active_record/connection_adapters.rb +50 -0
  117. data/lib/active_record/connection_handling.rb +285 -33
  118. data/lib/active_record/core.rb +308 -100
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  121. data/lib/active_record/database_configurations/database_config.rb +80 -0
  122. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  123. data/lib/active_record/database_configurations/url_config.rb +53 -0
  124. data/lib/active_record/database_configurations.rb +272 -0
  125. data/lib/active_record/delegated_type.rb +209 -0
  126. data/lib/active_record/destroy_association_async_job.rb +36 -0
  127. data/lib/active_record/dynamic_matchers.rb +3 -4
  128. data/lib/active_record/enum.rb +71 -17
  129. data/lib/active_record/errors.rb +62 -19
  130. data/lib/active_record/explain.rb +10 -6
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/file.rb +10 -17
  133. data/lib/active_record/fixture_set/model_metadata.rb +32 -0
  134. data/lib/active_record/fixture_set/render_context.rb +17 -0
  135. data/lib/active_record/fixture_set/table_row.rb +152 -0
  136. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  137. data/lib/active_record/fixtures.rb +197 -481
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +53 -24
  140. data/lib/active_record/insert_all.rb +208 -0
  141. data/lib/active_record/integration.rb +67 -17
  142. data/lib/active_record/internal_metadata.rb +26 -9
  143. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  144. data/lib/active_record/locking/optimistic.rb +26 -22
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +34 -35
  147. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector.rb +77 -0
  150. data/lib/active_record/migration/command_recorder.rb +96 -44
  151. data/lib/active_record/migration/compatibility.rb +141 -64
  152. data/lib/active_record/migration/join_table.rb +0 -1
  153. data/lib/active_record/migration.rb +205 -156
  154. data/lib/active_record/model_schema.rb +148 -22
  155. data/lib/active_record/nested_attributes.rb +4 -7
  156. data/lib/active_record/no_touching.rb +8 -1
  157. data/lib/active_record/null_relation.rb +0 -1
  158. data/lib/active_record/persistence.rb +267 -59
  159. data/lib/active_record/query_cache.rb +21 -4
  160. data/lib/active_record/querying.rb +40 -23
  161. data/lib/active_record/railtie.rb +115 -58
  162. data/lib/active_record/railties/controller_runtime.rb +30 -35
  163. data/lib/active_record/railties/databases.rake +402 -78
  164. data/lib/active_record/readonly_attributes.rb +4 -0
  165. data/lib/active_record/reflection.rb +113 -101
  166. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  167. data/lib/active_record/relation/batches.rb +44 -35
  168. data/lib/active_record/relation/calculations.rb +157 -93
  169. data/lib/active_record/relation/delegation.rb +35 -50
  170. data/lib/active_record/relation/finder_methods.rb +65 -40
  171. data/lib/active_record/relation/from_clause.rb +5 -1
  172. data/lib/active_record/relation/merger.rb +32 -40
  173. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  174. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  175. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  176. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -7
  177. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  179. data/lib/active_record/relation/predicate_builder.rb +58 -40
  180. data/lib/active_record/relation/query_attribute.rb +13 -8
  181. data/lib/active_record/relation/query_methods.rb +487 -199
  182. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  183. data/lib/active_record/relation/spawn_methods.rb +9 -9
  184. data/lib/active_record/relation/where_clause.rb +108 -58
  185. data/lib/active_record/relation.rb +375 -104
  186. data/lib/active_record/result.rb +64 -38
  187. data/lib/active_record/runtime_registry.rb +2 -2
  188. data/lib/active_record/sanitization.rb +22 -41
  189. data/lib/active_record/schema.rb +2 -11
  190. data/lib/active_record/schema_dumper.rb +54 -9
  191. data/lib/active_record/schema_migration.rb +7 -9
  192. data/lib/active_record/scoping/default.rb +6 -8
  193. data/lib/active_record/scoping/named.rb +17 -24
  194. data/lib/active_record/scoping.rb +8 -9
  195. data/lib/active_record/secure_token.rb +16 -8
  196. data/lib/active_record/serialization.rb +5 -3
  197. data/lib/active_record/signed_id.rb +116 -0
  198. data/lib/active_record/statement_cache.rb +51 -8
  199. data/lib/active_record/store.rb +88 -9
  200. data/lib/active_record/suppressor.rb +2 -2
  201. data/lib/active_record/table_metadata.rb +39 -43
  202. data/lib/active_record/tasks/database_tasks.rb +276 -81
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  206. data/lib/active_record/test_databases.rb +24 -0
  207. data/lib/active_record/test_fixtures.rb +246 -0
  208. data/lib/active_record/timestamp.rb +43 -32
  209. data/lib/active_record/touch_later.rb +23 -22
  210. data/lib/active_record/transactions.rb +59 -117
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  213. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  214. data/lib/active_record/type/serialized.rb +6 -3
  215. data/lib/active_record/type/time.rb +10 -0
  216. data/lib/active_record/type/type_map.rb +0 -1
  217. data/lib/active_record/type/unsigned_integer.rb +0 -1
  218. data/lib/active_record/type.rb +10 -5
  219. data/lib/active_record/type_caster/connection.rb +15 -15
  220. data/lib/active_record/type_caster/map.rb +8 -8
  221. data/lib/active_record/validations/associated.rb +1 -2
  222. data/lib/active_record/validations/numericality.rb +35 -0
  223. data/lib/active_record/validations/uniqueness.rb +38 -30
  224. data/lib/active_record/validations.rb +4 -3
  225. data/lib/active_record.rb +13 -12
  226. data/lib/arel/alias_predication.rb +9 -0
  227. data/lib/arel/attributes/attribute.rb +41 -0
  228. data/lib/arel/collectors/bind.rb +29 -0
  229. data/lib/arel/collectors/composite.rb +39 -0
  230. data/lib/arel/collectors/plain_string.rb +20 -0
  231. data/lib/arel/collectors/sql_string.rb +27 -0
  232. data/lib/arel/collectors/substitute_binds.rb +35 -0
  233. data/lib/arel/crud.rb +42 -0
  234. data/lib/arel/delete_manager.rb +18 -0
  235. data/lib/arel/errors.rb +9 -0
  236. data/lib/arel/expressions.rb +29 -0
  237. data/lib/arel/factory_methods.rb +49 -0
  238. data/lib/arel/insert_manager.rb +49 -0
  239. data/lib/arel/math.rb +45 -0
  240. data/lib/arel/nodes/and.rb +32 -0
  241. data/lib/arel/nodes/ascending.rb +23 -0
  242. data/lib/arel/nodes/binary.rb +126 -0
  243. data/lib/arel/nodes/bind_param.rb +44 -0
  244. data/lib/arel/nodes/case.rb +55 -0
  245. data/lib/arel/nodes/casted.rb +62 -0
  246. data/lib/arel/nodes/comment.rb +29 -0
  247. data/lib/arel/nodes/count.rb +12 -0
  248. data/lib/arel/nodes/delete_statement.rb +45 -0
  249. data/lib/arel/nodes/descending.rb +23 -0
  250. data/lib/arel/nodes/equality.rb +15 -0
  251. data/lib/arel/nodes/extract.rb +24 -0
  252. data/lib/arel/nodes/false.rb +16 -0
  253. data/lib/arel/nodes/full_outer_join.rb +8 -0
  254. data/lib/arel/nodes/function.rb +44 -0
  255. data/lib/arel/nodes/grouping.rb +11 -0
  256. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  257. data/lib/arel/nodes/in.rb +15 -0
  258. data/lib/arel/nodes/infix_operation.rb +92 -0
  259. data/lib/arel/nodes/inner_join.rb +8 -0
  260. data/lib/arel/nodes/insert_statement.rb +37 -0
  261. data/lib/arel/nodes/join_source.rb +20 -0
  262. data/lib/arel/nodes/matches.rb +18 -0
  263. data/lib/arel/nodes/named_function.rb +23 -0
  264. data/lib/arel/nodes/node.rb +51 -0
  265. data/lib/arel/nodes/node_expression.rb +13 -0
  266. data/lib/arel/nodes/ordering.rb +27 -0
  267. data/lib/arel/nodes/outer_join.rb +8 -0
  268. data/lib/arel/nodes/over.rb +15 -0
  269. data/lib/arel/nodes/regexp.rb +16 -0
  270. data/lib/arel/nodes/right_outer_join.rb +8 -0
  271. data/lib/arel/nodes/select_core.rb +67 -0
  272. data/lib/arel/nodes/select_statement.rb +41 -0
  273. data/lib/arel/nodes/sql_literal.rb +19 -0
  274. data/lib/arel/nodes/string_join.rb +11 -0
  275. data/lib/arel/nodes/table_alias.rb +31 -0
  276. data/lib/arel/nodes/terminal.rb +16 -0
  277. data/lib/arel/nodes/true.rb +16 -0
  278. data/lib/arel/nodes/unary.rb +44 -0
  279. data/lib/arel/nodes/unary_operation.rb +20 -0
  280. data/lib/arel/nodes/unqualified_column.rb +22 -0
  281. data/lib/arel/nodes/update_statement.rb +41 -0
  282. data/lib/arel/nodes/values_list.rb +9 -0
  283. data/lib/arel/nodes/window.rb +126 -0
  284. data/lib/arel/nodes/with.rb +11 -0
  285. data/lib/arel/nodes.rb +70 -0
  286. data/lib/arel/order_predications.rb +13 -0
  287. data/lib/arel/predications.rb +250 -0
  288. data/lib/arel/select_manager.rb +270 -0
  289. data/lib/arel/table.rb +118 -0
  290. data/lib/arel/tree_manager.rb +72 -0
  291. data/lib/arel/update_manager.rb +34 -0
  292. data/lib/arel/visitors/dot.rb +308 -0
  293. data/lib/arel/visitors/mysql.rb +93 -0
  294. data/lib/arel/visitors/postgresql.rb +120 -0
  295. data/lib/arel/visitors/sqlite.rb +38 -0
  296. data/lib/arel/visitors/to_sql.rb +899 -0
  297. data/lib/arel/visitors/visitor.rb +45 -0
  298. data/lib/arel/visitors.rb +13 -0
  299. data/lib/arel/window_predications.rb +9 -0
  300. data/lib/arel.rb +54 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  303. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  304. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  305. data/lib/rails/generators/active_record/migration.rb +19 -2
  306. data/lib/rails/generators/active_record/model/model_generator.rb +39 -2
  307. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  308. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  309. metadata +117 -32
  310. data/lib/active_record/attribute_decorators.rb +0 -90
  311. data/lib/active_record/collection_cache_key.rb +0 -53
  312. data/lib/active_record/connection_adapters/connection_specification.rb +0 -287
  313. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -33
  314. data/lib/active_record/define_callbacks.rb +0 -22
  315. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -19
  316. data/lib/active_record/relation/where_clause_factory.rb +0 -34
@@ -46,41 +46,226 @@ module ActiveRecord
46
46
  #
47
47
  # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
48
48
  # may be returned on an error.
49
- def establish_connection(config = nil)
50
- raise "Anonymous class is not allowed." unless name
49
+ def establish_connection(config_or_env = nil)
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)
53
+ end
54
+
55
+ # Connects a model to the databases specified. The +database+ keyword
56
+ # takes a hash consisting of a +role+ and a +database_key+.
57
+ #
58
+ # This will create a connection handler for switching between connections,
59
+ # look up the config hash using the +database_key+ and finally
60
+ # establishes a connection to that config.
61
+ #
62
+ # class AnimalsModel < ApplicationRecord
63
+ # self.abstract_class = true
64
+ #
65
+ # connects_to database: { writing: :primary, reading: :primary_replica }
66
+ # end
67
+ #
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?
51
83
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_name = spec_name
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
+
88
+ connections = []
55
89
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[:name] = spec_name
90
+ database.each do |role, database_key|
91
+ db_config, owner_name = resolve_config_for_connection(database_key)
92
+ handler = lookup_connection_handler(role.to_sym)
59
93
 
60
- connection_handler.establish_connection(spec)
94
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role)
95
+ end
96
+
97
+ shards.each do |shard, database_keys|
98
+ database_keys.each do |role, database_key|
99
+ db_config, owner_name = resolve_config_for_connection(database_key)
100
+ handler = lookup_connection_handler(role.to_sym)
101
+
102
+ connections << handler.establish_connection(db_config, owner_name: owner_name, role: role, shard: shard.to_sym)
103
+ end
104
+ end
105
+
106
+ connections
61
107
  end
62
108
 
63
- class MergeAndResolveDefaultUrlConfig # :nodoc:
64
- def initialize(raw_configurations)
65
- @raw_config = raw_configurations.dup
66
- @env = DEFAULT_ENV.call.to_s
109
+ # Connects to a role (ex writing, reading or a custom role) and/or
110
+ # shard for the duration of the block. At the end of the block the
111
+ # connection will be returned to the original role / shard.
112
+ #
113
+ # If only a role is passed, Active Record will look up the connection
114
+ # based on the requested role. If a non-established role is requested
115
+ # an `ActiveRecord::ConnectionNotEstablished` error will be raised:
116
+ #
117
+ # ActiveRecord::Base.connected_to(role: :writing) do
118
+ # Dog.create! # creates dog using dog writing connection
119
+ # end
120
+ #
121
+ # ActiveRecord::Base.connected_to(role: :reading) do
122
+ # Dog.create! # throws exception because we're on a replica
123
+ # end
124
+ #
125
+ # When swapping to a shard, the role must be passed as well. If a non-existent
126
+ # shard is passed, an `ActiveRecord::ConnectionNotEstablished` error will be
127
+ # raised.
128
+ #
129
+ # When a shard and role is passed, Active Record will first lookup the role,
130
+ # and then look up the connection by shard key.
131
+ #
132
+ # ActiveRecord::Base.connected_to(role: :reading, shard: :shard_one_replica) do
133
+ # Dog.first # finds first Dog record stored on the shard one replica
134
+ # end
135
+ #
136
+ # The database kwarg is deprecated and will be removed in 6.2.0 without replacement.
137
+ def connected_to(database: nil, role: nil, shard: nil, prevent_writes: false, &blk)
138
+ if legacy_connection_handling
139
+ if self != Base
140
+ raise NotImplementedError, "`connected_to` can only be called on ActiveRecord::Base with legacy connection handling."
141
+ end
142
+ else
143
+ if self != Base && !abstract_class
144
+ raise NotImplementedError, "calling `connected_to` is only allowed on ActiveRecord::Base or abstract classes."
145
+ end
146
+ end
147
+
148
+ if database && (role || shard)
149
+ raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
150
+ elsif database
151
+ ActiveSupport::Deprecation.warn("The database key in `connected_to` is deprecated. It will be removed in Rails 6.2.0 without replacement.")
152
+
153
+ if database.is_a?(Hash)
154
+ role, database = database.first
155
+ role = role.to_sym
156
+ end
157
+
158
+ db_config, owner_name = resolve_config_for_connection(database)
159
+ handler = lookup_connection_handler(role)
160
+
161
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
162
+
163
+ with_handler(role, &blk)
164
+ elsif role || shard
165
+ unless role
166
+ raise ArgumentError, "`connected_to` cannot accept a `shard` argument without a `role`."
167
+ end
168
+
169
+ with_role_and_shard(role, shard, prevent_writes, &blk)
170
+ else
171
+ raise ArgumentError, "must provide a `shard` and/or `role`."
67
172
  end
173
+ end
68
174
 
69
- # Returns fully resolved connection hashes.
70
- # Merges connection information from `ENV['DATABASE_URL']` if available.
71
- def resolve
72
- ConnectionAdapters::ConnectionSpecification::Resolver.new(config).resolve_all
175
+ # Connects a role and/or shard to the provided connection names. Optionally `prevent_writes`
176
+ # can be passed to block writes on a connection. `reading` will automatically set
177
+ # `prevent_writes` to true.
178
+ #
179
+ # `connected_to_many` is an alternative to deeply nested `connected_to` blocks.
180
+ #
181
+ # Usage:
182
+ #
183
+ # ActiveRecord::Base.connected_to(AnimalsRecord, MealsRecord], role: :reading) do
184
+ # Dog.first # Read from animals replica
185
+ # Dinner.first # Read from meals replica
186
+ # Person.first # Read from primary writer
187
+ # end
188
+ def connected_to_many(classes, role:, shard: nil, prevent_writes: false)
189
+ if legacy_connection_handling
190
+ raise NotImplementedError, "connected_to_many is not available with legacy connection handling"
73
191
  end
74
192
 
75
- private
76
- def config
77
- @raw_config.dup.tap do |cfg|
78
- if url = ENV["DATABASE_URL"]
79
- cfg[@env] ||= {}
80
- cfg[@env]["url"] ||= url
81
- end
82
- end
193
+ if self != Base || classes.include?(Base)
194
+ raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
195
+ end
196
+
197
+ prevent_writes = true if role == reading_role
198
+
199
+ connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: classes }
200
+ yield
201
+ ensure
202
+ connected_to_stack.pop
203
+ end
204
+
205
+ # Use a specified connection.
206
+ #
207
+ # This method is useful for ensuring that a specific connection is
208
+ # being used. For example, when booting a console in readonly mode.
209
+ #
210
+ # It is not recommended to use this method in a request since it
211
+ # does not yield to a block like `connected_to`.
212
+ def connecting_to(role: default_role, shard: default_shard, prevent_writes: false)
213
+ if legacy_connection_handling
214
+ raise NotImplementedError, "`connecting_to` is not available with `legacy_connection_handling`."
215
+ end
216
+
217
+ prevent_writes = true if role == reading_role
218
+
219
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
220
+ end
221
+
222
+ # Prevent writing to the database regardless of role.
223
+ #
224
+ # In some cases you may want to prevent writes to the database
225
+ # even if you are on a database that can write. `while_preventing_writes`
226
+ # will prevent writes to the database for the duration of the block.
227
+ #
228
+ # This method does not provide the same protection as a readonly
229
+ # user and is meant to be a safeguard against accidental writes.
230
+ #
231
+ # See `READ_QUERY` for the queries that are blocked by this
232
+ # method.
233
+ def while_preventing_writes(enabled = true, &block)
234
+ if legacy_connection_handling
235
+ connection_handler.while_preventing_writes(enabled, &block)
236
+ else
237
+ connected_to(role: current_role, prevent_writes: enabled, &block)
238
+ end
239
+ end
240
+
241
+ # Returns true if role is the current connected role.
242
+ #
243
+ # ActiveRecord::Base.connected_to(role: :writing) do
244
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
245
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
246
+ # end
247
+ def connected_to?(role:, shard: ActiveRecord::Base.default_shard)
248
+ current_role == role.to_sym && current_shard == shard.to_sym
249
+ end
250
+
251
+ def lookup_connection_handler(handler_key) # :nodoc:
252
+ if ActiveRecord::Base.legacy_connection_handling
253
+ handler_key ||= ActiveRecord::Base.writing_role
254
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
255
+ else
256
+ ActiveRecord::Base.connection_handler
257
+ end
258
+ end
259
+
260
+ # Clears the query cache for all connections associated with the current thread.
261
+ def clear_query_caches_for_current_thread
262
+ if ActiveRecord::Base.legacy_connection_handling
263
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
264
+ clear_on_handler(handler)
83
265
  end
266
+ else
267
+ clear_on_handler(ActiveRecord::Base.connection_handler)
268
+ end
84
269
  end
85
270
 
86
271
  # Returns the connection currently associated with the class. This can
@@ -92,14 +277,18 @@ module ActiveRecord
92
277
 
93
278
  attr_writer :connection_specification_name
94
279
 
95
- # Return the specification name from the current class or its parent.
280
+ # Return the connection specification name from the current class or its parent.
96
281
  def connection_specification_name
97
282
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
98
- return self == Base ? "primary" : superclass.connection_specification_name
283
+ return self == Base ? Base.name : superclass.connection_specification_name
99
284
  end
100
285
  @connection_specification_name
101
286
  end
102
287
 
288
+ def primary_class? # :nodoc:
289
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
290
+ end
291
+
103
292
  # Returns the configuration of the associated connection as a hash:
104
293
  #
105
294
  # ActiveRecord::Base.connection_config
@@ -107,20 +296,32 @@ module ActiveRecord
107
296
  #
108
297
  # Please use only for reading.
109
298
  def connection_config
110
- connection_pool.spec.config
299
+ connection_pool.db_config.configuration_hash
300
+ end
301
+ deprecate connection_config: "Use connection_db_config instead"
302
+
303
+ # Returns the db_config object from the associated connection:
304
+ #
305
+ # ActiveRecord::Base.connection_db_config
306
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
307
+ # @name="primary", @config={pool: 5, timeout: 5000, database: "db/development.sqlite3", adapter: "sqlite3"}>
308
+ #
309
+ # Use only for reading.
310
+ def connection_db_config
311
+ connection_pool.db_config
111
312
  end
112
313
 
113
314
  def connection_pool
114
- connection_handler.retrieve_connection_pool(connection_specification_name) || raise(ConnectionNotEstablished)
315
+ connection_handler.retrieve_connection_pool(connection_specification_name, role: current_role, shard: current_shard) || raise(ConnectionNotEstablished)
115
316
  end
116
317
 
117
318
  def retrieve_connection
118
- connection_handler.retrieve_connection(connection_specification_name)
319
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
119
320
  end
120
321
 
121
322
  # Returns +true+ if Active Record is connected.
122
323
  def connected?
123
- connection_handler.connected?(connection_specification_name)
324
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
124
325
  end
125
326
 
126
327
  def remove_connection(name = nil)
@@ -128,11 +329,11 @@ module ActiveRecord
128
329
  # if removing a connection that has a pool, we reset the
129
330
  # connection_specification_name so it will use the parent
130
331
  # pool.
131
- if connection_handler.retrieve_connection_pool(name)
332
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
132
333
  self.connection_specification_name = nil
133
334
  end
134
335
 
135
- connection_handler.remove_connection(name)
336
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
136
337
  end
137
338
 
138
339
  def clear_cache! # :nodoc:
@@ -141,5 +342,56 @@ module ActiveRecord
141
342
 
142
343
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
344
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
345
+
346
+ private
347
+ def clear_on_handler(handler)
348
+ handler.all_connection_pools.each do |pool|
349
+ pool.connection.clear_query_cache if pool.active_connection?
350
+ end
351
+ end
352
+
353
+ def resolve_config_for_connection(config_or_env)
354
+ raise "Anonymous class is not allowed." unless name
355
+
356
+ owner_name = primary_class? ? Base.name : name
357
+ self.connection_specification_name = owner_name
358
+
359
+ db_config = Base.configurations.resolve(config_or_env)
360
+ [db_config, owner_name]
361
+ end
362
+
363
+ def with_handler(handler_key, &blk)
364
+ handler = lookup_connection_handler(handler_key)
365
+ swap_connection_handler(handler, &blk)
366
+ end
367
+
368
+ def with_role_and_shard(role, shard, prevent_writes)
369
+ prevent_writes = true if role == reading_role
370
+
371
+ if ActiveRecord::Base.legacy_connection_handling
372
+ with_handler(role.to_sym) do
373
+ connection_handler.while_preventing_writes(prevent_writes) do
374
+ self.connected_to_stack << { shard: shard, klasses: [self] }
375
+ yield
376
+ end
377
+ end
378
+ else
379
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
380
+ return_value = yield
381
+ return_value.load if return_value.is_a? ActiveRecord::Relation
382
+ return_value
383
+ end
384
+ ensure
385
+ self.connected_to_stack.pop
386
+ end
387
+
388
+ def swap_connection_handler(handler, &blk) # :nodoc:
389
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
390
+ return_value = yield
391
+ return_value.load if return_value.is_a? ActiveRecord::Relation
392
+ return_value
393
+ ensure
394
+ ActiveRecord::Base.connection_handler = old_handler
395
+ end
144
396
  end
145
397
  end