activerecord 5.2.6 → 6.1.3.2

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 +1038 -571
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +7 -5
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +13 -12
  7. data/lib/active_record/aggregations.rb +9 -8
  8. data/lib/active_record/association_relation.rb +30 -10
  9. data/lib/active_record/associations.rb +137 -25
  10. data/lib/active_record/associations/alias_tracker.rb +19 -16
  11. data/lib/active_record/associations/association.rb +95 -42
  12. data/lib/active_record/associations/association_scope.rb +23 -21
  13. data/lib/active_record/associations/belongs_to_association.rb +54 -46
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +7 -6
  15. data/lib/active_record/associations/builder/association.rb +45 -22
  16. data/lib/active_record/associations/builder/belongs_to.rb +29 -59
  17. data/lib/active_record/associations/builder/collection_association.rb +8 -17
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -41
  19. data/lib/active_record/associations/builder/has_many.rb +8 -2
  20. data/lib/active_record/associations/builder/has_one.rb +33 -2
  21. data/lib/active_record/associations/builder/singular_association.rb +3 -1
  22. data/lib/active_record/associations/collection_association.rb +31 -29
  23. data/lib/active_record/associations/collection_proxy.rb +25 -21
  24. data/lib/active_record/associations/foreign_association.rb +20 -0
  25. data/lib/active_record/associations/has_many_association.rb +26 -13
  26. data/lib/active_record/associations/has_many_through_association.rb +24 -18
  27. data/lib/active_record/associations/has_one_association.rb +43 -31
  28. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  29. data/lib/active_record/associations/join_dependency.rb +91 -60
  30. data/lib/active_record/associations/join_dependency/join_association.rb +44 -22
  31. data/lib/active_record/associations/join_dependency/join_part.rb +5 -5
  32. data/lib/active_record/associations/preloader.rb +47 -34
  33. data/lib/active_record/associations/preloader/association.rb +71 -43
  34. data/lib/active_record/associations/preloader/through_association.rb +49 -40
  35. data/lib/active_record/associations/singular_association.rb +3 -17
  36. data/lib/active_record/associations/through_association.rb +1 -1
  37. data/lib/active_record/attribute_assignment.rb +17 -19
  38. data/lib/active_record/attribute_methods.rb +81 -143
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -7
  40. data/lib/active_record/attribute_methods/dirty.rb +101 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +20 -25
  42. data/lib/active_record/attribute_methods/query.rb +4 -8
  43. data/lib/active_record/attribute_methods/read.rb +14 -56
  44. data/lib/active_record/attribute_methods/serialization.rb +12 -7
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  46. data/lib/active_record/attribute_methods/write.rb +18 -34
  47. data/lib/active_record/attributes.rb +46 -9
  48. data/lib/active_record/autosave_association.rb +57 -42
  49. data/lib/active_record/base.rb +4 -17
  50. data/lib/active_record/callbacks.rb +158 -43
  51. data/lib/active_record/coders/yaml_column.rb +1 -2
  52. data/lib/active_record/connection_adapters.rb +50 -0
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +272 -130
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +7 -36
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +167 -146
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -14
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +98 -47
  58. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  59. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +153 -110
  60. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +207 -90
  61. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -4
  62. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +385 -144
  63. data/lib/active_record/connection_adapters/abstract/transaction.rb +155 -68
  64. data/lib/active_record/connection_adapters/abstract_adapter.rb +228 -98
  65. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +243 -275
  66. data/lib/active_record/connection_adapters/column.rb +30 -12
  67. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  68. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  69. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  70. data/lib/active_record/connection_adapters/mysql/database_statements.rb +86 -32
  71. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +59 -7
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +34 -10
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +48 -32
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +18 -7
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  77. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +14 -9
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +53 -18
  79. data/lib/active_record/connection_adapters/pool_config.rb +73 -0
  80. data/lib/active_record/connection_adapters/pool_manager.rb +43 -0
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +37 -28
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +38 -54
  83. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +3 -5
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +2 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +10 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +49 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +3 -4
  93. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +3 -4
  96. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  97. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  98. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  99. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +15 -3
  100. data/lib/active_record/connection_adapters/postgresql/quoting.rb +47 -10
  101. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  102. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +19 -4
  103. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  104. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  105. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +120 -100
  106. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +31 -26
  107. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  108. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -120
  109. data/lib/active_record/connection_adapters/schema_cache.rb +127 -21
  110. data/lib/active_record/connection_adapters/sql_type_metadata.rb +19 -6
  111. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +144 -0
  112. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  113. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  114. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +77 -13
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +174 -186
  116. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  117. data/lib/active_record/connection_handling.rb +293 -33
  118. data/lib/active_record/core.rb +323 -97
  119. data/lib/active_record/counter_cache.rb +8 -30
  120. data/lib/active_record/database_configurations.rb +272 -0
  121. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  122. data/lib/active_record/database_configurations/database_config.rb +80 -0
  123. data/lib/active_record/database_configurations/hash_config.rb +96 -0
  124. data/lib/active_record/database_configurations/url_config.rb +53 -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 +111 -37
  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 +200 -481
  138. data/lib/active_record/gem_version.rb +4 -4
  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 +37 -23
  145. data/lib/active_record/locking/pessimistic.rb +9 -5
  146. data/lib/active_record/log_subscriber.rb +35 -35
  147. data/lib/active_record/middleware/database_selector.rb +77 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +92 -0
  149. data/lib/active_record/middleware/database_selector/resolver/session.rb +48 -0
  150. data/lib/active_record/migration.rb +206 -157
  151. data/lib/active_record/migration/command_recorder.rb +96 -44
  152. data/lib/active_record/migration/compatibility.rb +142 -64
  153. data/lib/active_record/migration/join_table.rb +0 -1
  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/console_sandbox.rb +2 -4
  163. data/lib/active_record/railties/controller_runtime.rb +30 -35
  164. data/lib/active_record/railties/databases.rake +408 -78
  165. data/lib/active_record/readonly_attributes.rb +4 -0
  166. data/lib/active_record/reflection.rb +109 -93
  167. data/lib/active_record/relation.rb +374 -104
  168. data/lib/active_record/relation/batches.rb +44 -35
  169. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  170. data/lib/active_record/relation/calculations.rb +153 -90
  171. data/lib/active_record/relation/delegation.rb +35 -50
  172. data/lib/active_record/relation/finder_methods.rb +64 -39
  173. data/lib/active_record/relation/from_clause.rb +5 -1
  174. data/lib/active_record/relation/merger.rb +32 -40
  175. data/lib/active_record/relation/predicate_builder.rb +62 -45
  176. data/lib/active_record/relation/predicate_builder/array_handler.rb +13 -13
  177. data/lib/active_record/relation/predicate_builder/association_query_value.rb +5 -9
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +11 -10
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  182. data/lib/active_record/relation/query_attribute.rb +13 -8
  183. data/lib/active_record/relation/query_methods.rb +475 -186
  184. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  185. data/lib/active_record/relation/spawn_methods.rb +9 -9
  186. data/lib/active_record/relation/where_clause.rb +111 -61
  187. data/lib/active_record/result.rb +64 -38
  188. data/lib/active_record/runtime_registry.rb +2 -2
  189. data/lib/active_record/sanitization.rb +22 -41
  190. data/lib/active_record/schema.rb +2 -11
  191. data/lib/active_record/schema_dumper.rb +54 -9
  192. data/lib/active_record/schema_migration.rb +7 -9
  193. data/lib/active_record/scoping.rb +8 -9
  194. data/lib/active_record/scoping/default.rb +4 -6
  195. data/lib/active_record/scoping/named.rb +17 -24
  196. data/lib/active_record/secure_token.rb +16 -8
  197. data/lib/active_record/serialization.rb +5 -3
  198. data/lib/active_record/signed_id.rb +116 -0
  199. data/lib/active_record/statement_cache.rb +49 -6
  200. data/lib/active_record/store.rb +88 -9
  201. data/lib/active_record/suppressor.rb +2 -2
  202. data/lib/active_record/table_metadata.rb +42 -43
  203. data/lib/active_record/tasks/database_tasks.rb +277 -81
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +37 -39
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +27 -32
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +14 -17
  207. data/lib/active_record/test_databases.rb +24 -0
  208. data/lib/active_record/test_fixtures.rb +246 -0
  209. data/lib/active_record/timestamp.rb +43 -32
  210. data/lib/active_record/touch_later.rb +23 -22
  211. data/lib/active_record/transactions.rb +62 -118
  212. data/lib/active_record/translation.rb +1 -1
  213. data/lib/active_record/type.rb +10 -5
  214. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  215. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  216. data/lib/active_record/type/serialized.rb +6 -3
  217. data/lib/active_record/type/time.rb +10 -0
  218. data/lib/active_record/type/type_map.rb +0 -1
  219. data/lib/active_record/type/unsigned_integer.rb +0 -1
  220. data/lib/active_record/type_caster/connection.rb +15 -15
  221. data/lib/active_record/type_caster/map.rb +8 -8
  222. data/lib/active_record/validations.rb +4 -3
  223. data/lib/active_record/validations/associated.rb +1 -2
  224. data/lib/active_record/validations/numericality.rb +35 -0
  225. data/lib/active_record/validations/uniqueness.rb +38 -30
  226. data/lib/arel.rb +54 -0
  227. data/lib/arel/alias_predication.rb +9 -0
  228. data/lib/arel/attributes/attribute.rb +41 -0
  229. data/lib/arel/collectors/bind.rb +29 -0
  230. data/lib/arel/collectors/composite.rb +39 -0
  231. data/lib/arel/collectors/plain_string.rb +20 -0
  232. data/lib/arel/collectors/sql_string.rb +27 -0
  233. data/lib/arel/collectors/substitute_binds.rb +35 -0
  234. data/lib/arel/crud.rb +42 -0
  235. data/lib/arel/delete_manager.rb +18 -0
  236. data/lib/arel/errors.rb +9 -0
  237. data/lib/arel/expressions.rb +29 -0
  238. data/lib/arel/factory_methods.rb +49 -0
  239. data/lib/arel/insert_manager.rb +49 -0
  240. data/lib/arel/math.rb +45 -0
  241. data/lib/arel/nodes.rb +70 -0
  242. data/lib/arel/nodes/and.rb +32 -0
  243. data/lib/arel/nodes/ascending.rb +23 -0
  244. data/lib/arel/nodes/binary.rb +126 -0
  245. data/lib/arel/nodes/bind_param.rb +44 -0
  246. data/lib/arel/nodes/case.rb +55 -0
  247. data/lib/arel/nodes/casted.rb +62 -0
  248. data/lib/arel/nodes/comment.rb +29 -0
  249. data/lib/arel/nodes/count.rb +12 -0
  250. data/lib/arel/nodes/delete_statement.rb +45 -0
  251. data/lib/arel/nodes/descending.rb +23 -0
  252. data/lib/arel/nodes/equality.rb +15 -0
  253. data/lib/arel/nodes/extract.rb +24 -0
  254. data/lib/arel/nodes/false.rb +16 -0
  255. data/lib/arel/nodes/full_outer_join.rb +8 -0
  256. data/lib/arel/nodes/function.rb +44 -0
  257. data/lib/arel/nodes/grouping.rb +11 -0
  258. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  259. data/lib/arel/nodes/in.rb +15 -0
  260. data/lib/arel/nodes/infix_operation.rb +92 -0
  261. data/lib/arel/nodes/inner_join.rb +8 -0
  262. data/lib/arel/nodes/insert_statement.rb +37 -0
  263. data/lib/arel/nodes/join_source.rb +20 -0
  264. data/lib/arel/nodes/matches.rb +18 -0
  265. data/lib/arel/nodes/named_function.rb +23 -0
  266. data/lib/arel/nodes/node.rb +51 -0
  267. data/lib/arel/nodes/node_expression.rb +13 -0
  268. data/lib/arel/nodes/ordering.rb +27 -0
  269. data/lib/arel/nodes/outer_join.rb +8 -0
  270. data/lib/arel/nodes/over.rb +15 -0
  271. data/lib/arel/nodes/regexp.rb +16 -0
  272. data/lib/arel/nodes/right_outer_join.rb +8 -0
  273. data/lib/arel/nodes/select_core.rb +67 -0
  274. data/lib/arel/nodes/select_statement.rb +41 -0
  275. data/lib/arel/nodes/sql_literal.rb +19 -0
  276. data/lib/arel/nodes/string_join.rb +11 -0
  277. data/lib/arel/nodes/table_alias.rb +31 -0
  278. data/lib/arel/nodes/terminal.rb +16 -0
  279. data/lib/arel/nodes/true.rb +16 -0
  280. data/lib/arel/nodes/unary.rb +44 -0
  281. data/lib/arel/nodes/unary_operation.rb +20 -0
  282. data/lib/arel/nodes/unqualified_column.rb +22 -0
  283. data/lib/arel/nodes/update_statement.rb +41 -0
  284. data/lib/arel/nodes/values_list.rb +9 -0
  285. data/lib/arel/nodes/window.rb +126 -0
  286. data/lib/arel/nodes/with.rb +11 -0
  287. data/lib/arel/order_predications.rb +13 -0
  288. data/lib/arel/predications.rb +250 -0
  289. data/lib/arel/select_manager.rb +270 -0
  290. data/lib/arel/table.rb +118 -0
  291. data/lib/arel/tree_manager.rb +72 -0
  292. data/lib/arel/update_manager.rb +34 -0
  293. data/lib/arel/visitors.rb +13 -0
  294. data/lib/arel/visitors/dot.rb +308 -0
  295. data/lib/arel/visitors/mysql.rb +93 -0
  296. data/lib/arel/visitors/postgresql.rb +120 -0
  297. data/lib/arel/visitors/sqlite.rb +38 -0
  298. data/lib/arel/visitors/to_sql.rb +899 -0
  299. data/lib/arel/visitors/visitor.rb +45 -0
  300. data/lib/arel/window_predications.rb +9 -0
  301. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  302. data/lib/rails/generators/active_record/migration.rb +19 -2
  303. data/lib/rails/generators/active_record/migration/migration_generator.rb +3 -5
  304. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +3 -1
  305. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +7 -5
  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 +119 -34
  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
@@ -48,7 +48,6 @@ module ActiveRecord
48
48
  end
49
49
 
50
50
  private
51
-
52
51
  def cache
53
52
  @cache[Process.pid]
54
53
  end
@@ -46,41 +46,234 @@ 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?
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
+
88
+ connections = []
89
+
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)
93
+
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
106
+ end
107
+
108
+ connections
109
+ end
110
+
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.
114
+ #
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:
118
+ #
119
+ # ActiveRecord::Base.connected_to(role: :writing) do
120
+ # Dog.create! # creates dog using dog writing connection
121
+ # end
122
+ #
123
+ # ActiveRecord::Base.connected_to(role: :reading) do
124
+ # Dog.create! # throws exception because we're on a replica
125
+ # end
126
+ #
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.
133
+ #
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
137
+ #
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
51
153
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_name = spec_name
154
+ if database && (role || shard)
155
+ raise ArgumentError, "`connected_to` cannot accept a `database` argument with any other arguments."
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.")
55
158
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[:name] = spec_name
159
+ if database.is_a?(Hash)
160
+ role, database = database.first
161
+ role = role.to_sym
162
+ end
59
163
 
60
- connection_handler.establish_connection(spec)
164
+ db_config, owner_name = resolve_config_for_connection(database)
165
+ handler = lookup_connection_handler(role)
166
+
167
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
168
+
169
+ with_handler(role, &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)
176
+ else
177
+ raise ArgumentError, "must provide a `shard` and/or `role`."
178
+ end
61
179
  end
62
180
 
63
- class MergeAndResolveDefaultUrlConfig # :nodoc:
64
- def initialize(raw_configurations)
65
- @raw_config = raw_configurations.dup
66
- @env = DEFAULT_ENV.call.to_s
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.
184
+ #
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
193
+ # end
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"
67
199
  end
68
200
 
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
201
+ if self != Base || classes.include?(Base)
202
+ raise NotImplementedError, "connected_to_many can only be called on ActiveRecord::Base."
73
203
  end
74
204
 
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
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
211
+ end
212
+
213
+ # Use a specified connection.
214
+ #
215
+ # This method is useful for ensuring that a specific connection is
216
+ # being used. For example, when booting a console in readonly mode.
217
+ #
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
224
+
225
+ prevent_writes = true if role == reading_role
226
+
227
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
228
+ end
229
+
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
248
+
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
258
+
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
266
+ end
267
+
268
+ # Clears the query cache for all connections associated with the current thread.
269
+ def clear_query_caches_for_current_thread
270
+ if ActiveRecord::Base.legacy_connection_handling
271
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
272
+ clear_on_handler(handler)
83
273
  end
274
+ else
275
+ clear_on_handler(ActiveRecord::Base.connection_handler)
276
+ end
84
277
  end
85
278
 
86
279
  # Returns the connection currently associated with the class. This can
@@ -92,14 +285,18 @@ module ActiveRecord
92
285
 
93
286
  attr_writer :connection_specification_name
94
287
 
95
- # Return the specification name from the current class or its parent.
288
+ # Return the connection specification name from the current class or its parent.
96
289
  def connection_specification_name
97
290
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
98
- return self == Base ? "primary" : superclass.connection_specification_name
291
+ return self == Base ? Base.name : superclass.connection_specification_name
99
292
  end
100
293
  @connection_specification_name
101
294
  end
102
295
 
296
+ def primary_class? # :nodoc:
297
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
298
+ end
299
+
103
300
  # Returns the configuration of the associated connection as a hash:
104
301
  #
105
302
  # ActiveRecord::Base.connection_config
@@ -107,20 +304,32 @@ module ActiveRecord
107
304
  #
108
305
  # Please use only for reading.
109
306
  def connection_config
110
- 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
111
320
  end
112
321
 
113
322
  def connection_pool
114
- 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)
115
324
  end
116
325
 
117
326
  def retrieve_connection
118
- connection_handler.retrieve_connection(connection_specification_name)
327
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
119
328
  end
120
329
 
121
330
  # Returns +true+ if Active Record is connected.
122
331
  def connected?
123
- connection_handler.connected?(connection_specification_name)
332
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
124
333
  end
125
334
 
126
335
  def remove_connection(name = nil)
@@ -128,11 +337,11 @@ module ActiveRecord
128
337
  # if removing a connection that has a pool, we reset the
129
338
  # connection_specification_name so it will use the parent
130
339
  # pool.
131
- if connection_handler.retrieve_connection_pool(name)
340
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
132
341
  self.connection_specification_name = nil
133
342
  end
134
343
 
135
- connection_handler.remove_connection(name)
344
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
136
345
  end
137
346
 
138
347
  def clear_cache! # :nodoc:
@@ -141,5 +350,56 @@ module ActiveRecord
141
350
 
142
351
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
352
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
353
+
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
+
396
+ def swap_connection_handler(handler, &blk) # :nodoc:
397
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
398
+ return_value = yield
399
+ return_value.load if return_value.is_a? ActiveRecord::Relation
400
+ return_value
401
+ ensure
402
+ ActiveRecord::Base.connection_handler = old_handler
403
+ end
144
404
  end
145
405
  end
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
3
4
  require "active_support/core_ext/hash/indifferent_access"
4
5
  require "active_support/core_ext/string/filters"
6
+ require "active_support/parameter_filter"
5
7
  require "concurrent/map"
6
8
 
7
9
  module ActiveRecord
@@ -24,9 +26,21 @@ module ActiveRecord
24
26
  # their relevant queries. Defaults to false.
25
27
  mattr_accessor :verbose_query_logs, instance_writer: false, default: false
26
28
 
29
+ ##
30
+ # :singleton-method:
31
+ #
32
+ # Specifies the names of the queues used by background jobs.
33
+ mattr_accessor :queues, instance_accessor: false, default: {}
34
+
35
+ ##
36
+ # :singleton-method:
37
+ #
38
+ # Specifies the job used to destroy associations in the background
39
+ class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false
40
+
27
41
  ##
28
42
  # Contains the database configuration - as is typically stored in config/database.yml -
29
- # as a Hash.
43
+ # as an ActiveRecord::DatabaseConfigurations object.
30
44
  #
31
45
  # For example, the following database.yml...
32
46
  #
@@ -40,22 +54,18 @@ module ActiveRecord
40
54
  #
41
55
  # ...would result in ActiveRecord::Base.configurations to look like this:
42
56
  #
43
- # {
44
- # 'development' => {
45
- # 'adapter' => 'sqlite3',
46
- # 'database' => 'db/development.sqlite3'
47
- # },
48
- # 'production' => {
49
- # 'adapter' => 'sqlite3',
50
- # 'database' => 'db/production.sqlite3'
51
- # }
52
- # }
57
+ # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
58
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
59
+ # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
60
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
61
+ # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
62
+ # ]>
53
63
  def self.configurations=(config)
54
- @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
64
+ @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
55
65
  end
56
66
  self.configurations = {}
57
67
 
58
- # Returns fully resolved configurations hash
68
+ # Returns fully resolved ActiveRecord::DatabaseConfigurations object
59
69
  def self.configurations
60
70
  @@configurations
61
71
  end
@@ -83,14 +93,6 @@ module ActiveRecord
83
93
  # scope being ignored is error-worthy, rather than a warning.
84
94
  mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
85
95
 
86
- # :singleton-method:
87
- # Specify the behavior for unsafe raw query methods. Values are as follows
88
- # deprecated - Warnings are logged when unsafe raw SQL is passed to
89
- # query methods.
90
- # disabled - Unsafe raw SQL passed to query methods results in
91
- # UnknownAttributeReference exception.
92
- mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
93
-
94
96
  ##
95
97
  # :singleton-method:
96
98
  # Specify whether or not to use timestamps for migration versions
@@ -99,14 +101,14 @@ module ActiveRecord
99
101
  ##
100
102
  # :singleton-method:
101
103
  # Specify whether schema dump should happen at the end of the
102
- # db:migrate rake task. This is true by default, which is useful for the
104
+ # db:migrate rails command. This is true by default, which is useful for the
103
105
  # development environment. This should ideally be false in the production
104
106
  # environment where dumping schema is rarely needed.
105
107
  mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
106
108
 
107
109
  ##
108
110
  # :singleton-method:
109
- # Specifies which database schemas to dump when calling db:structure:dump.
111
+ # Specifies which database schemas to dump when calling db:schema:dump.
110
112
  # If the value is :schema_search_path (the default), any schemas listed in
111
113
  # schema_search_path are dumped. Use :all to dump all schemas regardless
112
114
  # of schema_search_path, or a string of comma separated schemas for a
@@ -121,29 +123,185 @@ module ActiveRecord
121
123
  # potentially cause memory bloat.
122
124
  mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
123
125
 
126
+ ##
127
+ # :singleton-method:
128
+ # Show a warning when Rails couldn't parse your database.yml
129
+ # for multiple databases.
130
+ mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false
131
+
124
132
  mattr_accessor :maintain_test_schema, instance_accessor: false
125
133
 
126
- mattr_accessor :belongs_to_required_by_default, instance_accessor: false
134
+ class_attribute :belongs_to_required_by_default, instance_accessor: false
135
+
136
+ ##
137
+ # :singleton-method:
138
+ # Set the application to log or raise when an association violates strict loading.
139
+ # Defaults to :raise.
140
+ mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
141
+
142
+ class_attribute :strict_loading_by_default, instance_accessor: false, default: false
143
+
144
+ mattr_accessor :writing_role, instance_accessor: false, default: :writing
145
+
146
+ mattr_accessor :reading_role, instance_accessor: false, default: :reading
147
+
148
+ mattr_accessor :has_many_inversing, instance_accessor: false, default: false
127
149
 
128
150
  class_attribute :default_connection_handler, instance_writer: false
129
151
 
152
+ class_attribute :default_role, instance_writer: false
153
+
154
+ class_attribute :default_shard, instance_writer: false
155
+
156
+ mattr_accessor :legacy_connection_handling, instance_writer: false, default: true
157
+
158
+ self.filter_attributes = []
159
+
130
160
  def self.connection_handler
131
- ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
161
+ Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
132
162
  end
133
163
 
134
164
  def self.connection_handler=(handler)
135
- ActiveRecord::RuntimeRegistry.connection_handler = handler
165
+ Thread.current.thread_variable_set(:ar_connection_handler, handler)
136
166
  end
137
167
 
138
- self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
139
- end
168
+ def self.connection_handlers
169
+ unless legacy_connection_handling
170
+ raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
171
+ end
140
172
 
141
- module ClassMethods # :nodoc:
142
- def allocate
143
- define_attribute_methods
144
- super
173
+ @@connection_handlers ||= {}
174
+ end
175
+
176
+ def self.connection_handlers=(handlers)
177
+ unless legacy_connection_handling
178
+ raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers."
179
+ end
180
+
181
+ @@connection_handlers = handlers
145
182
  end
146
183
 
184
+ # Returns the symbol representing the current connected role.
185
+ #
186
+ # ActiveRecord::Base.connected_to(role: :writing) do
187
+ # ActiveRecord::Base.current_role #=> :writing
188
+ # end
189
+ #
190
+ # ActiveRecord::Base.connected_to(role: :reading) do
191
+ # ActiveRecord::Base.current_role #=> :reading
192
+ # end
193
+ def self.current_role
194
+ if ActiveRecord::Base.legacy_connection_handling
195
+ connection_handlers.key(connection_handler) || default_role
196
+ else
197
+ connected_to_stack.reverse_each do |hash|
198
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(connection_classes)
200
+ end
201
+
202
+ default_role
203
+ end
204
+ end
205
+
206
+ # Returns the symbol representing the current connected shard.
207
+ #
208
+ # ActiveRecord::Base.connected_to(role: :reading) do
209
+ # ActiveRecord::Base.current_shard #=> :default
210
+ # end
211
+ #
212
+ # ActiveRecord::Base.connected_to(role: :writing, shard: :one) do
213
+ # ActiveRecord::Base.current_shard #=> :one
214
+ # end
215
+ def self.current_shard
216
+ connected_to_stack.reverse_each do |hash|
217
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(Base)
218
+ return hash[:shard] if hash[:shard] && hash[:klasses].include?(connection_classes)
219
+ end
220
+
221
+ default_shard
222
+ end
223
+
224
+ # Returns the symbol representing the current setting for
225
+ # preventing writes.
226
+ #
227
+ # ActiveRecord::Base.connected_to(role: :reading) do
228
+ # ActiveRecord::Base.current_preventing_writes #=> true
229
+ # end
230
+ #
231
+ # ActiveRecord::Base.connected_to(role: :writing) do
232
+ # ActiveRecord::Base.current_preventing_writes #=> false
233
+ # end
234
+ def self.current_preventing_writes
235
+ if legacy_connection_handling
236
+ connection_handler.prevent_writes
237
+ else
238
+ connected_to_stack.reverse_each do |hash|
239
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(Base)
240
+ return hash[:prevent_writes] if !hash[:prevent_writes].nil? && hash[:klasses].include?(connection_classes)
241
+ end
242
+
243
+ false
244
+ end
245
+ end
246
+
247
+ def self.connected_to_stack # :nodoc:
248
+ if connected_to_stack = Thread.current.thread_variable_get(:ar_connected_to_stack)
249
+ connected_to_stack
250
+ else
251
+ connected_to_stack = Concurrent::Array.new
252
+ Thread.current.thread_variable_set(:ar_connected_to_stack, connected_to_stack)
253
+ connected_to_stack
254
+ end
255
+ end
256
+
257
+ def self.connection_class=(b) # :nodoc:
258
+ @connection_class = b
259
+ end
260
+
261
+ def self.connection_class # :nodoc
262
+ @connection_class ||= false
263
+ end
264
+
265
+ def self.connection_class? # :nodoc:
266
+ self.connection_class
267
+ end
268
+
269
+ def self.connection_classes # :nodoc:
270
+ klass = self
271
+
272
+ until klass == Base
273
+ break if klass.connection_class?
274
+ klass = klass.superclass
275
+ end
276
+
277
+ klass
278
+ end
279
+
280
+ def self.allow_unsafe_raw_sql # :nodoc:
281
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 6.2")
282
+ end
283
+
284
+ def self.allow_unsafe_raw_sql=(value) # :nodoc:
285
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 6.2")
286
+ end
287
+
288
+ self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
289
+ self.default_role = writing_role
290
+ self.default_shard = :default
291
+
292
+ def self.strict_loading_violation!(owner:, reflection:) # :nodoc:
293
+ case action_on_strict_loading_violation
294
+ when :raise
295
+ message = "`#{owner}` is marked for strict_loading. The `#{reflection.klass}` association named `:#{reflection.name}` cannot be lazily loaded."
296
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
297
+ when :log
298
+ name = "strict_loading_violation.active_record"
299
+ ActiveSupport::Notifications.instrument(name, owner: owner, reflection: reflection)
300
+ end
301
+ end
302
+ end
303
+
304
+ module ClassMethods
147
305
  def initialize_find_by_cache # :nodoc:
148
306
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
149
307
  end
@@ -151,16 +309,20 @@ module ActiveRecord
151
309
  def inherited(child_class) # :nodoc:
152
310
  # initialize cache at class definition for thread safety
153
311
  child_class.initialize_find_by_cache
312
+ unless child_class.base_class?
313
+ klass = self
314
+ until klass.base_class?
315
+ klass.initialize_find_by_cache
316
+ klass = klass.superclass
317
+ end
318
+ end
154
319
  super
155
320
  end
156
321
 
157
322
  def find(*ids) # :nodoc:
158
323
  # We don't have cache keys for this stuff yet
159
324
  return super unless ids.length == 1
160
- return super if block_given? ||
161
- primary_key.nil? ||
162
- scope_attributes? ||
163
- columns_hash.include?(inheritance_column)
325
+ return super if block_given? || primary_key.nil? || scope_attributes?
164
326
 
165
327
  id = ids.first
166
328
 
@@ -172,44 +334,49 @@ module ActiveRecord
172
334
  where(key => params.bind).limit(1)
173
335
  }
174
336
 
175
- record = statement.execute([id], connection).first
176
- unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
178
- name, primary_key, id)
179
- end
180
- record
181
- rescue ::RangeError
182
- raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
183
- name, primary_key)
337
+ statement.execute([id], connection).first ||
338
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
184
339
  end
185
340
 
186
341
  def find_by(*args) # :nodoc:
187
- return super if scope_attributes? || reflect_on_all_aggregations.any? ||
188
- columns_hash.key?(inheritance_column) && base_class != self
342
+ return super if scope_attributes?
189
343
 
190
344
  hash = args.first
345
+ return super unless Hash === hash
191
346
 
192
- return super if !(Hash === hash) || hash.values.any? { |v|
193
- StatementCache.unsupported_value?(v)
194
- }
347
+ hash = hash.each_with_object({}) do |(key, value), h|
348
+ key = key.to_s
349
+ key = attribute_aliases[key] || key
195
350
 
196
- # We can't cache Post.find_by(author: david) ...yet
197
- return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
351
+ return super if reflect_on_aggregation(key)
198
352
 
199
- keys = hash.keys
353
+ reflection = _reflect_on_association(key)
354
+
355
+ if !reflection
356
+ value = value.id if value.respond_to?(:id)
357
+ elsif reflection.belongs_to? && !reflection.polymorphic?
358
+ key = reflection.join_foreign_key
359
+ pkey = reflection.join_primary_key
360
+ value = value.public_send(pkey) if value.respond_to?(pkey)
361
+ end
362
+
363
+ if !columns_hash.key?(key) || StatementCache.unsupported_value?(value)
364
+ return super
365
+ end
366
+
367
+ h[key] = value
368
+ end
200
369
 
370
+ keys = hash.keys
201
371
  statement = cached_find_by_statement(keys) { |params|
202
- wheres = keys.each_with_object({}) { |param, o|
203
- o[param] = params.bind
204
- }
372
+ wheres = keys.index_with { params.bind }
205
373
  where(wheres).limit(1)
206
374
  }
375
+
207
376
  begin
208
377
  statement.execute(hash.values, connection).first
209
378
  rescue TypeError
210
379
  raise ActiveRecord::StatementInvalid
211
- rescue ::RangeError
212
- nil
213
380
  end
214
381
  end
215
382
 
@@ -221,7 +388,7 @@ module ActiveRecord
221
388
  generated_association_methods
222
389
  end
223
390
 
224
- def generated_association_methods
391
+ def generated_association_methods # :nodoc:
225
392
  @generated_association_methods ||= begin
226
393
  mod = const_set(:GeneratedAssociationMethods, Module.new)
227
394
  private_constant :GeneratedAssociationMethods
@@ -231,8 +398,20 @@ module ActiveRecord
231
398
  end
232
399
  end
233
400
 
401
+ # Returns columns which shouldn't be exposed while calling +#inspect+.
402
+ def filter_attributes
403
+ if defined?(@filter_attributes)
404
+ @filter_attributes
405
+ else
406
+ superclass.filter_attributes
407
+ end
408
+ end
409
+
410
+ # Specifies columns which shouldn't be exposed while calling +#inspect+.
411
+ attr_writer :filter_attributes
412
+
234
413
  # Returns a string like 'Post(id:integer, title:string, body:text)'
235
- def inspect
414
+ def inspect # :nodoc:
236
415
  if self == Base
237
416
  super
238
417
  elsif abstract_class?
@@ -248,7 +427,7 @@ module ActiveRecord
248
427
  end
249
428
 
250
429
  # Overwrite the default class equality method to provide support for decorated models.
251
- def ===(object)
430
+ def ===(object) # :nodoc:
252
431
  object.is_a?(self)
253
432
  end
254
433
 
@@ -258,13 +437,13 @@ module ActiveRecord
258
437
  # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
259
438
  # end
260
439
  def arel_table # :nodoc:
261
- @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
440
+ @arel_table ||= Arel::Table.new(table_name, klass: self)
262
441
  end
263
442
 
264
443
  def arel_attribute(name, table = arel_table) # :nodoc:
265
- name = attribute_alias(name) if attribute_alias?(name)
266
444
  table[name]
267
445
  end
446
+ deprecate :arel_attribute
268
447
 
269
448
  def predicate_builder # :nodoc:
270
449
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
@@ -274,19 +453,21 @@ module ActiveRecord
274
453
  TypeCaster::Map.new(self)
275
454
  end
276
455
 
277
- private
456
+ def _internal? # :nodoc:
457
+ false
458
+ end
278
459
 
279
- def cached_find_by_statement(key, &block)
280
- cache = @find_by_statement_cache[connection.prepared_statements]
281
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
282
- end
460
+ def cached_find_by_statement(key, &block) # :nodoc:
461
+ cache = @find_by_statement_cache[connection.prepared_statements]
462
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
463
+ end
283
464
 
465
+ private
284
466
  def relation
285
467
  relation = Relation.create(self)
286
468
 
287
469
  if finder_needs_type_condition? && !ignore_default_scope?
288
470
  relation.where!(type_condition)
289
- relation.create_with!(inheritance_column.to_s => sti_name)
290
471
  else
291
472
  relation
292
473
  end
@@ -306,7 +487,7 @@ module ActiveRecord
306
487
  # # Instantiates a single new object
307
488
  # User.new(first_name: 'Jamie')
308
489
  def initialize(attributes = nil)
309
- self.class.define_attribute_methods
490
+ @new_record = true
310
491
  @attributes = self.class._default_attributes.deep_dup
311
492
 
312
493
  init_internals
@@ -332,15 +513,21 @@ module ActiveRecord
332
513
  # post = Post.allocate
333
514
  # post.init_with(coder)
334
515
  # post.title # => 'hello world'
335
- def init_with(coder)
516
+ def init_with(coder, &block)
336
517
  coder = LegacyYamlAdapter.convert(self.class, coder)
337
- @attributes = self.class.yaml_encoder.decode(coder)
338
-
339
- init_internals
518
+ attributes = self.class.yaml_encoder.decode(coder)
519
+ init_with_attributes(attributes, coder["new_record"], &block)
520
+ end
340
521
 
341
- @new_record = coder["new_record"]
522
+ ##
523
+ # Initialize an empty model object from +attributes+.
524
+ # +attributes+ should be an attributes object, and unlike the
525
+ # `initialize` method, no assignment calls are made per attribute.
526
+ def init_with_attributes(attributes, new_record = false) # :nodoc:
527
+ @new_record = new_record
528
+ @attributes = attributes
342
529
 
343
- self.class.define_attribute_methods
530
+ init_internals
344
531
 
345
532
  yield self if block_given?
346
533
 
@@ -379,14 +566,14 @@ module ActiveRecord
379
566
  ##
380
567
  def initialize_dup(other) # :nodoc:
381
568
  @attributes = @attributes.deep_dup
382
- @attributes.reset(self.class.primary_key)
569
+ @attributes.reset(@primary_key)
383
570
 
384
571
  _run_initialize_callbacks
385
572
 
386
573
  @new_record = true
574
+ @previously_new_record = false
387
575
  @destroyed = false
388
- @_start_transaction_state = {}
389
- @transaction_state = nil
576
+ @_start_transaction_state = nil
390
577
 
391
578
  super
392
579
  end
@@ -458,12 +645,35 @@ module ActiveRecord
458
645
  end
459
646
  end
460
647
 
461
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
462
- # attributes will be marked as read only since they cannot be saved.
648
+ def present? # :nodoc:
649
+ true
650
+ end
651
+
652
+ def blank? # :nodoc:
653
+ false
654
+ end
655
+
656
+ # Returns +true+ if the record is read only.
463
657
  def readonly?
464
658
  @readonly
465
659
  end
466
660
 
661
+ # Returns +true+ if the record is in strict_loading mode.
662
+ def strict_loading?
663
+ @strict_loading
664
+ end
665
+
666
+ # Sets the record to strict_loading mode. This will raise an error
667
+ # if the record tries to lazily load an association.
668
+ #
669
+ # user = User.first
670
+ # user.strict_loading!
671
+ # user.comments.to_a
672
+ # => ActiveRecord::StrictLoadingViolationError
673
+ def strict_loading!
674
+ @strict_loading = true
675
+ end
676
+
467
677
  # Marks this record as read only.
468
678
  def readonly!
469
679
  @readonly = true
@@ -479,7 +689,7 @@ module ActiveRecord
479
689
  # allocated but not initialized.
480
690
  inspection = if defined?(@attributes) && @attributes
481
691
  self.class.attribute_names.collect do |name|
482
- if has_attribute?(name)
692
+ if _has_attribute?(name)
483
693
  "#{name}: #{attribute_for_inspect(name)}"
484
694
  end
485
695
  end.compact.join(", ")
@@ -496,15 +706,16 @@ module ActiveRecord
496
706
  return super if custom_inspect_method_defined?
497
707
  pp.object_address_group(self) do
498
708
  if defined?(@attributes) && @attributes
499
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
500
- pp.seplist(column_names, proc { pp.text "," }) do |column_name|
501
- column_value = read_attribute(column_name)
709
+ attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
710
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
502
711
  pp.breakable " "
503
712
  pp.group(1) do
504
- pp.text column_name
713
+ pp.text attr_name
505
714
  pp.text ":"
506
715
  pp.breakable
507
- pp.pp column_value
716
+ value = _read_attribute(attr_name)
717
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
718
+ pp.pp value
508
719
  end
509
720
  end
510
721
  else
@@ -516,11 +727,15 @@ module ActiveRecord
516
727
 
517
728
  # Returns a hash of the given methods with their names as keys and returned values as values.
518
729
  def slice(*methods)
519
- Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
730
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
520
731
  end
521
732
 
522
- private
733
+ # Returns an array of the values returned by the given methods.
734
+ def values_at(*methods)
735
+ methods.flatten.map! { |method| public_send(method) }
736
+ end
523
737
 
738
+ private
524
739
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
525
740
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
526
741
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -534,26 +749,37 @@ module ActiveRecord
534
749
  end
535
750
 
536
751
  def init_internals
752
+ @primary_key = self.class.primary_key
537
753
  @readonly = false
754
+ @previously_new_record = false
538
755
  @destroyed = false
539
756
  @marked_for_destruction = false
540
757
  @destroyed_by_association = nil
541
- @new_record = true
542
- @_start_transaction_state = {}
543
- @transaction_state = nil
758
+ @_start_transaction_state = nil
759
+ @strict_loading = self.class.strict_loading_by_default
760
+
761
+ self.class.define_attribute_methods
544
762
  end
545
763
 
546
764
  def initialize_internals_callback
547
765
  end
548
766
 
549
- def thaw
550
- if frozen?
551
- @attributes = @attributes.dup
767
+ def custom_inspect_method_defined?
768
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
769
+ end
770
+
771
+ class InspectionMask < DelegateClass(::String)
772
+ def pretty_print(pp)
773
+ pp.text __getobj__
552
774
  end
553
775
  end
776
+ private_constant :InspectionMask
554
777
 
555
- def custom_inspect_method_defined?
556
- self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
778
+ def inspection_filter
779
+ @inspection_filter ||= begin
780
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
781
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
782
+ end
557
783
  end
558
784
  end
559
785
  end