activerecord 6.0.0 → 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 (268) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +872 -582
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -3
  5. data/lib/active_record.rb +7 -13
  6. data/lib/active_record/aggregations.rb +1 -2
  7. data/lib/active_record/association_relation.rb +22 -12
  8. data/lib/active_record/associations.rb +116 -13
  9. data/lib/active_record/associations/alias_tracker.rb +19 -16
  10. data/lib/active_record/associations/association.rb +49 -29
  11. data/lib/active_record/associations/association_scope.rb +17 -15
  12. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -1
  14. data/lib/active_record/associations/builder/association.rb +9 -3
  15. data/lib/active_record/associations/builder/belongs_to.rb +10 -7
  16. data/lib/active_record/associations/builder/collection_association.rb +5 -4
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +0 -3
  18. data/lib/active_record/associations/builder/has_many.rb +6 -2
  19. data/lib/active_record/associations/builder/has_one.rb +11 -14
  20. data/lib/active_record/associations/builder/singular_association.rb +1 -1
  21. data/lib/active_record/associations/collection_association.rb +25 -8
  22. data/lib/active_record/associations/collection_proxy.rb +14 -7
  23. data/lib/active_record/associations/foreign_association.rb +13 -0
  24. data/lib/active_record/associations/has_many_association.rb +24 -3
  25. data/lib/active_record/associations/has_many_through_association.rb +10 -4
  26. data/lib/active_record/associations/has_one_association.rb +15 -1
  27. data/lib/active_record/associations/join_dependency.rb +77 -42
  28. data/lib/active_record/associations/join_dependency/join_association.rb +36 -14
  29. data/lib/active_record/associations/join_dependency/join_part.rb +3 -3
  30. data/lib/active_record/associations/preloader.rb +13 -8
  31. data/lib/active_record/associations/preloader/association.rb +51 -25
  32. data/lib/active_record/associations/preloader/through_association.rb +2 -2
  33. data/lib/active_record/associations/singular_association.rb +1 -1
  34. data/lib/active_record/associations/through_association.rb +1 -1
  35. data/lib/active_record/attribute_assignment.rb +10 -9
  36. data/lib/active_record/attribute_methods.rb +64 -54
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +13 -10
  38. data/lib/active_record/attribute_methods/dirty.rb +3 -13
  39. data/lib/active_record/attribute_methods/primary_key.rb +6 -4
  40. data/lib/active_record/attribute_methods/query.rb +3 -6
  41. data/lib/active_record/attribute_methods/read.rb +8 -12
  42. data/lib/active_record/attribute_methods/serialization.rb +11 -6
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -15
  44. data/lib/active_record/attribute_methods/write.rb +12 -21
  45. data/lib/active_record/attributes.rb +32 -8
  46. data/lib/active_record/autosave_association.rb +63 -44
  47. data/lib/active_record/base.rb +2 -14
  48. data/lib/active_record/callbacks.rb +153 -24
  49. data/lib/active_record/coders/yaml_column.rb +1 -2
  50. data/lib/active_record/connection_adapters.rb +50 -0
  51. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +202 -138
  52. data/lib/active_record/connection_adapters/abstract/database_limits.rb +2 -44
  53. data/lib/active_record/connection_adapters/abstract/database_statements.rb +86 -37
  54. data/lib/active_record/connection_adapters/abstract/query_cache.rb +4 -9
  55. data/lib/active_record/connection_adapters/abstract/quoting.rb +34 -34
  56. data/lib/active_record/connection_adapters/abstract/savepoints.rb +3 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +152 -116
  58. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -52
  59. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +3 -3
  60. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +263 -107
  61. data/lib/active_record/connection_adapters/abstract/transaction.rb +82 -35
  62. data/lib/active_record/connection_adapters/abstract_adapter.rb +74 -76
  63. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -115
  64. data/lib/active_record/connection_adapters/column.rb +15 -1
  65. data/lib/active_record/connection_adapters/deduplicable.rb +29 -0
  66. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +31 -0
  67. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  68. data/lib/active_record/connection_adapters/mysql/database_statements.rb +30 -36
  69. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +1 -2
  70. data/lib/active_record/connection_adapters/mysql/quoting.rb +1 -1
  71. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +32 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +8 -0
  73. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  74. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +17 -13
  75. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql2_adapter.rb +31 -13
  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 +24 -1
  80. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +21 -56
  81. data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +0 -1
  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 +0 -1
  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 +2 -3
  90. data/lib/active_record/connection_adapters/postgresql/oid/macaddr.rb +25 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  92. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +2 -3
  93. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +24 -6
  94. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  95. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +11 -2
  96. data/lib/active_record/connection_adapters/postgresql/quoting.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -2
  98. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +7 -3
  99. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +1 -1
  100. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  101. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +72 -54
  102. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +8 -0
  103. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  104. data/lib/active_record/connection_adapters/postgresql_adapter.rb +81 -57
  105. data/lib/active_record/connection_adapters/schema_cache.rb +98 -15
  106. data/lib/active_record/connection_adapters/sql_type_metadata.rb +10 -0
  107. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +38 -12
  108. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +1 -2
  109. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +5 -1
  110. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -5
  111. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -57
  112. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  113. data/lib/active_record/connection_handling.rb +211 -81
  114. data/lib/active_record/core.rb +237 -69
  115. data/lib/active_record/counter_cache.rb +4 -1
  116. data/lib/active_record/database_configurations.rb +124 -85
  117. data/lib/active_record/database_configurations/connection_url_resolver.rb +98 -0
  118. data/lib/active_record/database_configurations/database_config.rb +52 -9
  119. data/lib/active_record/database_configurations/hash_config.rb +54 -8
  120. data/lib/active_record/database_configurations/url_config.rb +15 -41
  121. data/lib/active_record/delegated_type.rb +209 -0
  122. data/lib/active_record/destroy_association_async_job.rb +36 -0
  123. data/lib/active_record/dynamic_matchers.rb +2 -3
  124. data/lib/active_record/enum.rb +40 -16
  125. data/lib/active_record/errors.rb +47 -12
  126. data/lib/active_record/explain.rb +9 -5
  127. data/lib/active_record/explain_subscriber.rb +1 -1
  128. data/lib/active_record/fixture_set/file.rb +10 -17
  129. data/lib/active_record/fixture_set/model_metadata.rb +1 -2
  130. data/lib/active_record/fixture_set/render_context.rb +1 -1
  131. data/lib/active_record/fixture_set/table_row.rb +2 -3
  132. data/lib/active_record/fixture_set/table_rows.rb +0 -1
  133. data/lib/active_record/fixtures.rb +54 -11
  134. data/lib/active_record/gem_version.rb +1 -1
  135. data/lib/active_record/inheritance.rb +40 -21
  136. data/lib/active_record/insert_all.rb +39 -10
  137. data/lib/active_record/integration.rb +3 -5
  138. data/lib/active_record/internal_metadata.rb +16 -7
  139. data/lib/active_record/legacy_yaml_adapter.rb +7 -3
  140. data/lib/active_record/locking/optimistic.rb +22 -17
  141. data/lib/active_record/locking/pessimistic.rb +6 -2
  142. data/lib/active_record/log_subscriber.rb +27 -9
  143. data/lib/active_record/middleware/database_selector.rb +4 -2
  144. data/lib/active_record/middleware/database_selector/resolver.rb +14 -14
  145. data/lib/active_record/middleware/database_selector/resolver/session.rb +3 -0
  146. data/lib/active_record/migration.rb +114 -84
  147. data/lib/active_record/migration/command_recorder.rb +53 -45
  148. data/lib/active_record/migration/compatibility.rb +70 -20
  149. data/lib/active_record/migration/join_table.rb +0 -1
  150. data/lib/active_record/model_schema.rb +120 -15
  151. data/lib/active_record/nested_attributes.rb +2 -5
  152. data/lib/active_record/no_touching.rb +1 -1
  153. data/lib/active_record/null_relation.rb +0 -1
  154. data/lib/active_record/persistence.rb +50 -46
  155. data/lib/active_record/query_cache.rb +15 -5
  156. data/lib/active_record/querying.rb +12 -7
  157. data/lib/active_record/railtie.rb +65 -45
  158. data/lib/active_record/railties/databases.rake +267 -93
  159. data/lib/active_record/readonly_attributes.rb +4 -0
  160. data/lib/active_record/reflection.rb +77 -63
  161. data/lib/active_record/relation.rb +108 -67
  162. data/lib/active_record/relation/batches.rb +38 -32
  163. data/lib/active_record/relation/batches/batch_enumerator.rb +25 -9
  164. data/lib/active_record/relation/calculations.rb +102 -45
  165. data/lib/active_record/relation/delegation.rb +9 -7
  166. data/lib/active_record/relation/finder_methods.rb +55 -17
  167. data/lib/active_record/relation/from_clause.rb +5 -1
  168. data/lib/active_record/relation/merger.rb +27 -26
  169. data/lib/active_record/relation/predicate_builder.rb +55 -35
  170. data/lib/active_record/relation/predicate_builder/array_handler.rb +8 -9
  171. data/lib/active_record/relation/predicate_builder/association_query_value.rb +4 -5
  172. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +3 -3
  173. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  174. data/lib/active_record/relation/query_methods.rb +340 -180
  175. data/lib/active_record/relation/record_fetch_warning.rb +3 -3
  176. data/lib/active_record/relation/spawn_methods.rb +8 -8
  177. data/lib/active_record/relation/where_clause.rb +104 -58
  178. data/lib/active_record/result.rb +41 -34
  179. data/lib/active_record/runtime_registry.rb +2 -2
  180. data/lib/active_record/sanitization.rb +6 -17
  181. data/lib/active_record/schema_dumper.rb +34 -4
  182. data/lib/active_record/schema_migration.rb +2 -8
  183. data/lib/active_record/scoping.rb +0 -1
  184. data/lib/active_record/scoping/default.rb +0 -1
  185. data/lib/active_record/scoping/named.rb +7 -18
  186. data/lib/active_record/secure_token.rb +16 -8
  187. data/lib/active_record/serialization.rb +5 -3
  188. data/lib/active_record/signed_id.rb +116 -0
  189. data/lib/active_record/statement_cache.rb +20 -4
  190. data/lib/active_record/store.rb +3 -3
  191. data/lib/active_record/suppressor.rb +2 -2
  192. data/lib/active_record/table_metadata.rb +39 -36
  193. data/lib/active_record/tasks/database_tasks.rb +139 -113
  194. data/lib/active_record/tasks/mysql_database_tasks.rb +34 -36
  195. data/lib/active_record/tasks/postgresql_database_tasks.rb +24 -27
  196. data/lib/active_record/tasks/sqlite_database_tasks.rb +13 -10
  197. data/lib/active_record/test_databases.rb +5 -4
  198. data/lib/active_record/test_fixtures.rb +38 -16
  199. data/lib/active_record/timestamp.rb +4 -7
  200. data/lib/active_record/touch_later.rb +20 -21
  201. data/lib/active_record/transactions.rb +22 -71
  202. data/lib/active_record/type.rb +8 -2
  203. data/lib/active_record/type/adapter_specific_registry.rb +2 -5
  204. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  205. data/lib/active_record/type/serialized.rb +6 -3
  206. data/lib/active_record/type/time.rb +10 -0
  207. data/lib/active_record/type/type_map.rb +0 -1
  208. data/lib/active_record/type/unsigned_integer.rb +0 -1
  209. data/lib/active_record/type_caster/connection.rb +0 -1
  210. data/lib/active_record/type_caster/map.rb +8 -5
  211. data/lib/active_record/validations.rb +3 -3
  212. data/lib/active_record/validations/associated.rb +1 -2
  213. data/lib/active_record/validations/numericality.rb +35 -0
  214. data/lib/active_record/validations/uniqueness.rb +24 -4
  215. data/lib/arel.rb +15 -12
  216. data/lib/arel/attributes/attribute.rb +4 -0
  217. data/lib/arel/collectors/bind.rb +5 -0
  218. data/lib/arel/collectors/composite.rb +8 -0
  219. data/lib/arel/collectors/sql_string.rb +7 -0
  220. data/lib/arel/collectors/substitute_binds.rb +7 -0
  221. data/lib/arel/nodes.rb +3 -1
  222. data/lib/arel/nodes/binary.rb +82 -8
  223. data/lib/arel/nodes/bind_param.rb +8 -0
  224. data/lib/arel/nodes/casted.rb +21 -9
  225. data/lib/arel/nodes/equality.rb +6 -9
  226. data/lib/arel/nodes/grouping.rb +3 -0
  227. data/lib/arel/nodes/homogeneous_in.rb +72 -0
  228. data/lib/arel/nodes/in.rb +8 -1
  229. data/lib/arel/nodes/infix_operation.rb +13 -1
  230. data/lib/arel/nodes/join_source.rb +1 -1
  231. data/lib/arel/nodes/node.rb +7 -6
  232. data/lib/arel/nodes/ordering.rb +27 -0
  233. data/lib/arel/nodes/sql_literal.rb +3 -0
  234. data/lib/arel/nodes/table_alias.rb +7 -3
  235. data/lib/arel/nodes/unary.rb +0 -1
  236. data/lib/arel/predications.rb +17 -24
  237. data/lib/arel/select_manager.rb +1 -2
  238. data/lib/arel/table.rb +13 -5
  239. data/lib/arel/visitors.rb +0 -7
  240. data/lib/arel/visitors/dot.rb +14 -3
  241. data/lib/arel/visitors/mysql.rb +11 -1
  242. data/lib/arel/visitors/postgresql.rb +15 -5
  243. data/lib/arel/visitors/sqlite.rb +0 -1
  244. data/lib/arel/visitors/to_sql.rb +89 -79
  245. data/lib/arel/visitors/visitor.rb +0 -1
  246. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  247. data/lib/rails/generators/active_record/migration.rb +6 -2
  248. data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -0
  249. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +2 -0
  250. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -4
  251. data/lib/rails/generators/active_record/model/model_generator.rb +38 -2
  252. data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +7 -0
  253. metadata +27 -24
  254. data/lib/active_record/attribute_decorators.rb +0 -90
  255. data/lib/active_record/connection_adapters/connection_specification.rb +0 -297
  256. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +0 -29
  257. data/lib/active_record/define_callbacks.rb +0 -22
  258. data/lib/active_record/railties/collection_cache_association_loading.rb +0 -34
  259. data/lib/active_record/relation/predicate_builder/base_handler.rb +0 -18
  260. data/lib/active_record/relation/where_clause_factory.rb +0 -33
  261. data/lib/arel/attributes.rb +0 -22
  262. data/lib/arel/visitors/depth_first.rb +0 -204
  263. data/lib/arel/visitors/ibm_db.rb +0 -34
  264. data/lib/arel/visitors/informix.rb +0 -62
  265. data/lib/arel/visitors/mssql.rb +0 -157
  266. data/lib/arel/visitors/oracle.rb +0 -159
  267. data/lib/arel/visitors/oracle12.rb +0 -66
  268. data/lib/arel/visitors/where_sql.rb +0 -23
@@ -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
@@ -47,8 +47,9 @@ module ActiveRecord
47
47
  # The exceptions AdapterNotSpecified, AdapterNotFound and +ArgumentError+
48
48
  # may be returned on an error.
49
49
  def establish_connection(config_or_env = nil)
50
- config_hash = resolve_config_for_connection(config_or_env)
51
- connection_handler.establish_connection(config_hash)
50
+ config_or_env ||= DEFAULT_ENV.call.to_sym
51
+ db_config, owner_name = resolve_config_for_connection(config_or_env)
52
+ connection_handler.establish_connection(db_config, owner_name: owner_name, role: current_role, shard: current_shard)
52
53
  end
53
54
 
54
55
  # Connects a model to the databases specified. The +database+ keyword
@@ -64,25 +65,54 @@ module ActiveRecord
64
65
  # connects_to database: { writing: :primary, reading: :primary_replica }
65
66
  # end
66
67
  #
67
- # Returns an array of established connections.
68
- def connects_to(database: {})
68
+ # +connects_to+ also supports horizontal sharding. The horizontal sharding API
69
+ # also supports read replicas. Connect a model to a list of shards like this:
70
+ #
71
+ # class AnimalsModel < ApplicationRecord
72
+ # self.abstract_class = true
73
+ #
74
+ # connects_to shards: {
75
+ # default: { writing: :primary, reading: :primary_replica },
76
+ # shard_two: { writing: :primary_shard_two, reading: :primary_shard_replica_two }
77
+ # }
78
+ # end
79
+ #
80
+ # Returns an array of database connections.
81
+ def connects_to(database: {}, shards: {})
82
+ raise NotImplementedError, "`connects_to` can only be called on ActiveRecord::Base or abstract classes" unless self == Base || abstract_class?
83
+
84
+ if database.present? && shards.present?
85
+ raise ArgumentError, "`connects_to` can only accept a `database` or `shards` argument, but not both arguments."
86
+ end
87
+
69
88
  connections = []
70
89
 
71
90
  database.each do |role, database_key|
72
- config_hash = resolve_config_for_connection(database_key)
91
+ db_config, owner_name = resolve_config_for_connection(database_key)
73
92
  handler = lookup_connection_handler(role.to_sym)
74
93
 
75
- connections << handler.establish_connection(config_hash)
94
+ 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
76
104
  end
77
105
 
78
106
  connections
79
107
  end
80
108
 
81
- # Connects to a database or role (ex writing, reading, or another
82
- # custom role) for the duration of the block.
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.
83
112
  #
84
- # If a role is passed, Active Record will look up the connection
85
- # based on the requested role:
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:
86
116
  #
87
117
  # ActiveRecord::Base.connected_to(role: :writing) do
88
118
  # Dog.create! # creates dog using dog writing connection
@@ -92,103 +122,149 @@ module ActiveRecord
92
122
  # Dog.create! # throws exception because we're on a replica
93
123
  # end
94
124
  #
95
- # ActiveRecord::Base.connected_to(role: :unknown_role) do
96
- # # raises exception due to non-existent role
97
- # end
98
- #
99
- # For cases where you may want to connect to a database outside of the model,
100
- # you can use +connected_to+ with a +database+ argument. The +database+ argument
101
- # expects a symbol that corresponds to the database key in your config.
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.
102
128
  #
103
- # ActiveRecord::Base.connected_to(database: :animals_slow_replica) do
104
- # Dog.run_a_long_query # runs a long query while connected to the +animals_slow_replica+
105
- # end
106
- #
107
- # This will connect to a new database for the queries inside the block. By
108
- # default the `:writing` role will be used since all connections must be assigned
109
- # a role. If you would like to use a different role you can pass a hash to database:
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.
110
131
  #
111
- # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
- # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
- # Dog.run_a_long_query
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
114
134
  # end
115
135
  #
116
- # When using the database key a new connection will be established every time.
117
- def connected_to(database: nil, role: nil, &blk)
118
- if database && role
119
- raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
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."
120
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
+
121
153
  if database.is_a?(Hash)
122
154
  role, database = database.first
123
155
  role = role.to_sym
124
156
  end
125
157
 
126
- config_hash = resolve_config_for_connection(database)
158
+ db_config, owner_name = resolve_config_for_connection(database)
127
159
  handler = lookup_connection_handler(role)
128
160
 
129
- handler.establish_connection(config_hash)
161
+ handler.establish_connection(db_config, owner_name: owner_name, role: role)
130
162
 
131
163
  with_handler(role, &blk)
132
- elsif role
133
- with_handler(role.to_sym, &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)
134
170
  else
135
- raise ArgumentError, "must provide a `database` or a `role`."
171
+ raise ArgumentError, "must provide a `shard` and/or `role`."
136
172
  end
137
173
  end
138
174
 
139
- # Returns true if role is the current connected role.
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.
140
178
  #
141
- # ActiveRecord::Base.connected_to(role: :writing) do
142
- # ActiveRecord::Base.connected_to?(role: :writing) #=> true
143
- # ActiveRecord::Base.connected_to?(role: :reading) #=> false
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
144
187
  # end
145
- def connected_to?(role:)
146
- current_role == role.to_sym
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"
191
+ end
192
+
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
147
203
  end
148
204
 
149
- # Returns the symbol representing the current connected role.
205
+ # Use a specified connection.
150
206
  #
151
- # ActiveRecord::Base.connected_to(role: :writing) do
152
- # ActiveRecord::Base.current_role #=> :writing
153
- # end
207
+ # This method is useful for ensuring that a specific connection is
208
+ # being used. For example, when booting a console in readonly mode.
154
209
  #
155
- # ActiveRecord::Base.connected_to(role: :reading) do
156
- # ActiveRecord::Base.current_role #=> :reading
157
- # end
158
- def current_role
159
- connection_handlers.key(connection_handler)
160
- end
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
161
216
 
162
- def lookup_connection_handler(handler_key) # :nodoc:
163
- handler_key ||= ActiveRecord::Base.writing_role
164
- connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
165
- end
217
+ prevent_writes = true if role == reading_role
166
218
 
167
- def with_handler(handler_key, &blk) # :nodoc:
168
- handler = lookup_connection_handler(handler_key)
169
- swap_connection_handler(handler, &blk)
219
+ self.connected_to_stack << { role: role, shard: shard, prevent_writes: prevent_writes, klasses: [self] }
170
220
  end
171
221
 
172
- def resolve_config_for_connection(config_or_env) # :nodoc:
173
- raise "Anonymous class is not allowed." unless name
174
-
175
- config_or_env ||= DEFAULT_ENV.call.to_sym
176
- pool_name = primary_class? ? "primary" : name
177
- self.connection_specification_name = pool_name
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
178
240
 
179
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
180
- config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
181
- config_hash[:name] = pool_name
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
182
250
 
183
- config_hash
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
184
258
  end
185
259
 
186
260
  # Clears the query cache for all connections associated with the current thread.
187
261
  def clear_query_caches_for_current_thread
188
- ActiveRecord::Base.connection_handlers.each_value do |handler|
189
- handler.connection_pool_list.each do |pool|
190
- pool.connection.clear_query_cache if pool.active_connection?
262
+ if ActiveRecord::Base.legacy_connection_handling
263
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
264
+ clear_on_handler(handler)
191
265
  end
266
+ else
267
+ clear_on_handler(ActiveRecord::Base.connection_handler)
192
268
  end
193
269
  end
194
270
 
@@ -201,10 +277,10 @@ module ActiveRecord
201
277
 
202
278
  attr_writer :connection_specification_name
203
279
 
204
- # Return the specification name from the current class or its parent.
280
+ # Return the connection specification name from the current class or its parent.
205
281
  def connection_specification_name
206
282
  if !defined?(@connection_specification_name) || @connection_specification_name.nil?
207
- return primary_class? ? "primary" : superclass.connection_specification_name
283
+ return self == Base ? Base.name : superclass.connection_specification_name
208
284
  end
209
285
  @connection_specification_name
210
286
  end
@@ -220,20 +296,32 @@ module ActiveRecord
220
296
  #
221
297
  # Please use only for reading.
222
298
  def connection_config
223
- 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
224
312
  end
225
313
 
226
314
  def connection_pool
227
- 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)
228
316
  end
229
317
 
230
318
  def retrieve_connection
231
- connection_handler.retrieve_connection(connection_specification_name)
319
+ connection_handler.retrieve_connection(connection_specification_name, role: current_role, shard: current_shard)
232
320
  end
233
321
 
234
322
  # Returns +true+ if Active Record is connected.
235
323
  def connected?
236
- connection_handler.connected?(connection_specification_name)
324
+ connection_handler.connected?(connection_specification_name, role: current_role, shard: current_shard)
237
325
  end
238
326
 
239
327
  def remove_connection(name = nil)
@@ -241,11 +329,11 @@ module ActiveRecord
241
329
  # if removing a connection that has a pool, we reset the
242
330
  # connection_specification_name so it will use the parent
243
331
  # pool.
244
- if connection_handler.retrieve_connection_pool(name)
332
+ if connection_handler.retrieve_connection_pool(name, role: current_role, shard: current_shard)
245
333
  self.connection_specification_name = nil
246
334
  end
247
335
 
248
- connection_handler.remove_connection(name)
336
+ connection_handler.remove_connection_pool(name, role: current_role, shard: current_shard)
249
337
  end
250
338
 
251
339
  def clear_cache! # :nodoc:
@@ -256,10 +344,52 @@ module ActiveRecord
256
344
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
257
345
 
258
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
259
387
 
260
388
  def swap_connection_handler(handler, &blk) # :nodoc:
261
389
  old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
262
- yield
390
+ return_value = yield
391
+ return_value.load if return_value.is_a? ActiveRecord::Relation
392
+ return_value
263
393
  ensure
264
394
  ActiveRecord::Base.connection_handler = old_handler
265
395
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/enumerable"
3
4
  require "active_support/core_ext/hash/indifferent_access"
4
5
  require "active_support/core_ext/string/filters"
5
6
  require "active_support/parameter_filter"
@@ -25,6 +26,18 @@ module ActiveRecord
25
26
  # their relevant queries. Defaults to false.
26
27
  mattr_accessor :verbose_query_logs, instance_writer: false, default: false
27
28
 
29
+ ##
30
+ # :singleton-method:
31
+ #
32
+ # Specifies the names of the queues used by background jobs.
33
+ mattr_accessor :queues, instance_accessor: false, default: {}
34
+
35
+ ##
36
+ # :singleton-method:
37
+ #
38
+ # Specifies the job used to destroy associations in the background
39
+ class_attribute :destroy_association_async_job, instance_writer: false, instance_predicate: false, default: false
40
+
28
41
  ##
29
42
  # Contains the database configuration - as is typically stored in config/database.yml -
30
43
  # as an ActiveRecord::DatabaseConfigurations object.
@@ -43,9 +56,9 @@ module ActiveRecord
43
56
  #
44
57
  # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
45
58
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
46
- # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
59
+ # @name="primary", @config={adapter: "sqlite3", database: "db/development.sqlite3"}>,
47
60
  # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
48
- # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
61
+ # @name="primary", @config={adapter: "sqlite3", database: "db/production.sqlite3"}>
49
62
  # ]>
50
63
  def self.configurations=(config)
51
64
  @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
@@ -80,14 +93,6 @@ module ActiveRecord
80
93
  # scope being ignored is error-worthy, rather than a warning.
81
94
  mattr_accessor :error_on_ignored_order, instance_writer: false, default: false
82
95
 
83
- # :singleton-method:
84
- # Specify the behavior for unsafe raw query methods. Values are as follows
85
- # deprecated - Warnings are logged when unsafe raw SQL is passed to
86
- # query methods.
87
- # disabled - Unsafe raw SQL passed to query methods results in
88
- # UnknownAttributeReference exception.
89
- mattr_accessor :allow_unsafe_raw_sql, instance_writer: false, default: :deprecated
90
-
91
96
  ##
92
97
  # :singleton-method:
93
98
  # Specify whether or not to use timestamps for migration versions
@@ -103,7 +108,7 @@ module ActiveRecord
103
108
 
104
109
  ##
105
110
  # :singleton-method:
106
- # Specifies which database schemas to dump when calling db:structure:dump.
111
+ # Specifies which database schemas to dump when calling db:schema:dump.
107
112
  # If the value is :schema_search_path (the default), any schemas listed in
108
113
  # schema_search_path are dumped. Use :all to dump all schemas regardless
109
114
  # of schema_search_path, or a string of comma separated schemas for a
@@ -118,29 +123,170 @@ module ActiveRecord
118
123
  # potentially cause memory bloat.
119
124
  mattr_accessor :warn_on_records_fetched_greater_than, instance_writer: false
120
125
 
126
+ ##
127
+ # :singleton-method:
128
+ # Show a warning when Rails couldn't parse your database.yml
129
+ # for multiple databases.
130
+ mattr_accessor :suppress_multiple_database_warning, instance_writer: false, default: false
131
+
121
132
  mattr_accessor :maintain_test_schema, instance_accessor: false
122
133
 
123
- mattr_accessor :belongs_to_required_by_default, instance_accessor: false
134
+ class_attribute :belongs_to_required_by_default, instance_accessor: false
124
135
 
125
- mattr_accessor :connection_handlers, instance_accessor: false, default: {}
136
+ ##
137
+ # :singleton-method:
138
+ # Set the application to log or raise when an association violates strict loading.
139
+ # Defaults to :raise.
140
+ mattr_accessor :action_on_strict_loading_violation, instance_accessor: false, default: :raise
141
+
142
+ class_attribute :strict_loading_by_default, instance_accessor: false, default: false
126
143
 
127
144
  mattr_accessor :writing_role, instance_accessor: false, default: :writing
128
145
 
129
146
  mattr_accessor :reading_role, instance_accessor: false, default: :reading
130
147
 
148
+ mattr_accessor :has_many_inversing, instance_accessor: false, default: false
149
+
131
150
  class_attribute :default_connection_handler, instance_writer: false
132
151
 
152
+ class_attribute :default_role, instance_writer: false
153
+
154
+ class_attribute :default_shard, instance_writer: false
155
+
156
+ mattr_accessor :legacy_connection_handling, instance_writer: false, default: true
157
+
133
158
  self.filter_attributes = []
134
159
 
135
160
  def self.connection_handler
136
- Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
161
+ Thread.current.thread_variable_get(:ar_connection_handler) || default_connection_handler
137
162
  end
138
163
 
139
164
  def self.connection_handler=(handler)
140
- Thread.current.thread_variable_set("ar_connection_handler", handler)
165
+ Thread.current.thread_variable_set(:ar_connection_handler, handler)
166
+ end
167
+
168
+ def self.connection_handlers
169
+ unless legacy_connection_handling
170
+ raise NotImplementedError, "The new connection handling does not support accessing multiple connection handlers."
171
+ end
172
+
173
+ @@connection_handlers ||= {}
174
+ end
175
+
176
+ def self.connection_handlers=(handlers)
177
+ unless legacy_connection_handling
178
+ raise NotImplementedError, "The new connection handling does not setting support multiple connection handlers."
179
+ end
180
+
181
+ @@connection_handlers = handlers
182
+ end
183
+
184
+ # Returns the symbol representing the current connected role.
185
+ #
186
+ # ActiveRecord::Base.connected_to(role: :writing) do
187
+ # ActiveRecord::Base.current_role #=> :writing
188
+ # end
189
+ #
190
+ # ActiveRecord::Base.connected_to(role: :reading) do
191
+ # ActiveRecord::Base.current_role #=> :reading
192
+ # end
193
+ def self.current_role
194
+ if ActiveRecord::Base.legacy_connection_handling
195
+ connection_handlers.key(connection_handler) || default_role
196
+ else
197
+ connected_to_stack.reverse_each do |hash|
198
+ return hash[:role] if hash[:role] && hash[:klasses].include?(Base)
199
+ return hash[:role] if hash[:role] && hash[:klasses].include?(abstract_base_class)
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?(abstract_base_class)
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?(abstract_base_class)
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.abstract_base_class # :nodoc:
258
+ klass = self
259
+
260
+ until klass == Base
261
+ break if klass.abstract_class?
262
+ klass = klass.superclass
263
+ end
264
+
265
+ klass
266
+ end
267
+
268
+ def self.allow_unsafe_raw_sql # :nodoc:
269
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql is deprecated and will be removed in Rails 6.2")
270
+ end
271
+
272
+ def self.allow_unsafe_raw_sql=(value) # :nodoc:
273
+ ActiveSupport::Deprecation.warn("ActiveRecord::Base.allow_unsafe_raw_sql= is deprecated and will be removed in Rails 6.2")
141
274
  end
142
275
 
143
276
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
277
+ self.default_role = writing_role
278
+ self.default_shard = :default
279
+
280
+ def self.strict_loading_violation!(owner:, association:) # :nodoc:
281
+ case action_on_strict_loading_violation
282
+ when :raise
283
+ message = "`#{association}` called on `#{owner}` is marked for strict_loading and cannot be lazily loaded."
284
+ raise ActiveRecord::StrictLoadingViolationError.new(message)
285
+ when :log
286
+ name = "strict_loading_violation.active_record"
287
+ ActiveSupport::Notifications.instrument(name, owner: owner, association: association)
288
+ end
289
+ end
144
290
  end
145
291
 
146
292
  module ClassMethods
@@ -151,16 +297,20 @@ module ActiveRecord
151
297
  def inherited(child_class) # :nodoc:
152
298
  # initialize cache at class definition for thread safety
153
299
  child_class.initialize_find_by_cache
300
+ unless child_class.base_class?
301
+ klass = self
302
+ until klass.base_class?
303
+ klass.initialize_find_by_cache
304
+ klass = klass.superclass
305
+ end
306
+ end
154
307
  super
155
308
  end
156
309
 
157
310
  def find(*ids) # :nodoc:
158
311
  # We don't have cache keys for this stuff yet
159
312
  return super unless ids.length == 1
160
- return super if block_given? ||
161
- primary_key.nil? ||
162
- scope_attributes? ||
163
- columns_hash.key?(inheritance_column) && !base_class?
313
+ return super if block_given? || primary_key.nil? || scope_attributes?
164
314
 
165
315
  id = ids.first
166
316
 
@@ -172,36 +322,41 @@ module ActiveRecord
172
322
  where(key => params.bind).limit(1)
173
323
  }
174
324
 
175
- record = statement.execute([id], connection)&.first
176
- unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
178
- end
179
- record
325
+ statement.execute([id], connection).first ||
326
+ raise(RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id))
180
327
  end
181
328
 
182
329
  def find_by(*args) # :nodoc:
183
- return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
- columns_hash.key?(inheritance_column) && !base_class?
330
+ return super if scope_attributes?
185
331
 
186
332
  hash = args.first
333
+ return super unless Hash === hash
334
+
335
+ values = hash.values.map! { |value| value.respond_to?(:id) ? value.id : value }
336
+ return super if values.any? { |v| StatementCache.unsupported_value?(v) }
337
+
338
+ keys = hash.keys.map! do |key|
339
+ attribute_aliases[name = key.to_s] || begin
340
+ reflection = _reflect_on_association(name)
341
+ if reflection&.belongs_to? && !reflection.polymorphic?
342
+ reflection.join_foreign_key
343
+ elsif reflect_on_aggregation(name)
344
+ return super
345
+ else
346
+ name
347
+ end
348
+ end
349
+ end
187
350
 
188
- return super if !(Hash === hash) || hash.values.any? { |v|
189
- StatementCache.unsupported_value?(v)
190
- }
191
-
192
- # We can't cache Post.find_by(author: david) ...yet
193
- return super unless hash.keys.all? { |k| columns_hash.has_key?(k.to_s) }
194
-
195
- keys = hash.keys
351
+ return super unless keys.all? { |k| columns_hash.key?(k) }
196
352
 
197
353
  statement = cached_find_by_statement(keys) { |params|
198
- wheres = keys.each_with_object({}) { |param, o|
199
- o[param] = params.bind
200
- }
354
+ wheres = keys.index_with { params.bind }
201
355
  where(wheres).limit(1)
202
356
  }
357
+
203
358
  begin
204
- statement.execute(hash.values, connection)&.first
359
+ statement.execute(values, connection).first
205
360
  rescue TypeError
206
361
  raise ActiveRecord::StatementInvalid
207
362
  end
@@ -264,14 +419,13 @@ module ActiveRecord
264
419
  # scope :published_and_commented, -> { published.and(arel_table[:comments_count].gt(0)) }
265
420
  # end
266
421
  def arel_table # :nodoc:
267
- @arel_table ||= Arel::Table.new(table_name, type_caster: type_caster)
422
+ @arel_table ||= Arel::Table.new(table_name, klass: self)
268
423
  end
269
424
 
270
425
  def arel_attribute(name, table = arel_table) # :nodoc:
271
- name = name.to_s
272
- name = attribute_aliases[name] || name
273
426
  table[name]
274
427
  end
428
+ deprecate :arel_attribute
275
429
 
276
430
  def predicate_builder # :nodoc:
277
431
  @predicate_builder ||= PredicateBuilder.new(table_metadata)
@@ -285,19 +439,17 @@ module ActiveRecord
285
439
  false
286
440
  end
287
441
 
288
- private
289
-
290
- def cached_find_by_statement(key, &block)
291
- cache = @find_by_statement_cache[connection.prepared_statements]
292
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
293
- end
442
+ def cached_find_by_statement(key, &block) # :nodoc:
443
+ cache = @find_by_statement_cache[connection.prepared_statements]
444
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
445
+ end
294
446
 
447
+ private
295
448
  def relation
296
449
  relation = Relation.create(self)
297
450
 
298
451
  if finder_needs_type_condition? && !ignore_default_scope?
299
452
  relation.where!(type_condition)
300
- relation.create_with!(inheritance_column.to_s => sti_name)
301
453
  else
302
454
  relation
303
455
  end
@@ -401,9 +553,9 @@ module ActiveRecord
401
553
  _run_initialize_callbacks
402
554
 
403
555
  @new_record = true
556
+ @previously_new_record = false
404
557
  @destroyed = false
405
558
  @_start_transaction_state = nil
406
- @transaction_state = nil
407
559
 
408
560
  super
409
561
  end
@@ -463,7 +615,6 @@ module ActiveRecord
463
615
 
464
616
  # Returns +true+ if the attributes hash has been frozen.
465
617
  def frozen?
466
- sync_with_transaction_state if @transaction_state&.finalized?
467
618
  @attributes.frozen?
468
619
  end
469
620
 
@@ -484,12 +635,27 @@ module ActiveRecord
484
635
  false
485
636
  end
486
637
 
487
- # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
488
- # attributes will be marked as read only since they cannot be saved.
638
+ # Returns +true+ if the record is read only.
489
639
  def readonly?
490
640
  @readonly
491
641
  end
492
642
 
643
+ # Returns +true+ if the record is in strict_loading mode.
644
+ def strict_loading?
645
+ @strict_loading
646
+ end
647
+
648
+ # Sets the record to strict_loading mode. This will raise an error
649
+ # if the record tries to lazily load an association.
650
+ #
651
+ # user = User.first
652
+ # user.strict_loading!
653
+ # user.comments.to_a
654
+ # => ActiveRecord::StrictLoadingViolationError
655
+ def strict_loading!
656
+ @strict_loading = true
657
+ end
658
+
493
659
  # Marks this record as read only.
494
660
  def readonly!
495
661
  @readonly = true
@@ -505,15 +671,8 @@ module ActiveRecord
505
671
  # allocated but not initialized.
506
672
  inspection = if defined?(@attributes) && @attributes
507
673
  self.class.attribute_names.collect do |name|
508
- if has_attribute?(name)
509
- attr = _read_attribute(name)
510
- value = if attr.nil?
511
- attr.inspect
512
- else
513
- attr = format_for_inspect(attr)
514
- inspection_filter.filter_param(name, attr)
515
- end
516
- "#{name}: #{value}"
674
+ if _has_attribute?(name)
675
+ "#{name}: #{attribute_for_inspect(name)}"
517
676
  end
518
677
  end.compact.join(", ")
519
678
  else
@@ -529,7 +688,7 @@ module ActiveRecord
529
688
  return super if custom_inspect_method_defined?
530
689
  pp.object_address_group(self) do
531
690
  if defined?(@attributes) && @attributes
532
- attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
691
+ attr_names = self.class.attribute_names.select { |name| _has_attribute?(name) }
533
692
  pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
534
693
  pp.breakable " "
535
694
  pp.group(1) do
@@ -550,11 +709,15 @@ module ActiveRecord
550
709
 
551
710
  # Returns a hash of the given methods with their names as keys and returned values as values.
552
711
  def slice(*methods)
553
- Hash[methods.flatten.map! { |method| [method, public_send(method)] }].with_indifferent_access
712
+ methods.flatten.index_with { |method| public_send(method) }.with_indifferent_access
554
713
  end
555
714
 
556
- private
715
+ # Returns an array of the values returned by the given methods.
716
+ def values_at(*methods)
717
+ methods.flatten.map! { |method| public_send(method) }
718
+ end
557
719
 
720
+ private
558
721
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
559
722
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
560
723
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -570,11 +733,12 @@ module ActiveRecord
570
733
  def init_internals
571
734
  @primary_key = self.class.primary_key
572
735
  @readonly = false
736
+ @previously_new_record = false
573
737
  @destroyed = false
574
738
  @marked_for_destruction = false
575
739
  @destroyed_by_association = nil
576
740
  @_start_transaction_state = nil
577
- @transaction_state = nil
741
+ @strict_loading = self.class.strict_loading_by_default
578
742
 
579
743
  self.class.define_attribute_methods
580
744
  end
@@ -586,12 +750,16 @@ module ActiveRecord
586
750
  self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
587
751
  end
588
752
 
753
+ class InspectionMask < DelegateClass(::String)
754
+ def pretty_print(pp)
755
+ pp.text __getobj__
756
+ end
757
+ end
758
+ private_constant :InspectionMask
759
+
589
760
  def inspection_filter
590
761
  @inspection_filter ||= begin
591
- mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
592
- def mask.pretty_print(pp)
593
- pp.text __getobj__
594
- end
762
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
595
763
  ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
596
764
  end
597
765
  end