activerecord 5.2.4.1 → 6.0.1

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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +676 -572
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +9 -2
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/association_relation.rb +15 -6
  9. data/lib/active_record/associations.rb +20 -15
  10. data/lib/active_record/associations/association.rb +61 -20
  11. data/lib/active_record/associations/association_scope.rb +4 -6
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +6 -21
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency.rb +28 -28
  29. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +40 -32
  32. data/lib/active_record/associations/preloader/association.rb +38 -36
  33. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/attribute_assignment.rb +7 -10
  36. data/lib/active_record/attribute_methods.rb +28 -100
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  38. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  40. data/lib/active_record/attribute_methods/query.rb +2 -3
  41. data/lib/active_record/attribute_methods/read.rb +15 -53
  42. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  44. data/lib/active_record/attribute_methods/write.rb +17 -24
  45. data/lib/active_record/attributes.rb +13 -0
  46. data/lib/active_record/autosave_association.rb +2 -2
  47. data/lib/active_record/base.rb +2 -3
  48. data/lib/active_record/callbacks.rb +5 -19
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +104 -16
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +99 -123
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -8
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +187 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +138 -195
  61. data/lib/active_record/connection_adapters/column.rb +17 -13
  62. data/lib/active_record/connection_adapters/connection_specification.rb +53 -43
  63. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  66. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  67. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  68. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  69. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  70. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  71. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  81. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  82. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  83. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  87. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  88. data/lib/active_record/connection_adapters/postgresql_adapter.rb +164 -74
  89. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  90. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +129 -141
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +103 -59
  97. data/lib/active_record/counter_cache.rb +4 -29
  98. data/lib/active_record/database_configurations.rb +233 -0
  99. data/lib/active_record/database_configurations/database_config.rb +37 -0
  100. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  101. data/lib/active_record/database_configurations/url_config.rb +79 -0
  102. data/lib/active_record/dynamic_matchers.rb +1 -1
  103. data/lib/active_record/enum.rb +37 -7
  104. data/lib/active_record/errors.rb +15 -7
  105. data/lib/active_record/explain.rb +1 -1
  106. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  107. data/lib/active_record/fixture_set/render_context.rb +17 -0
  108. data/lib/active_record/fixture_set/table_row.rb +153 -0
  109. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  110. data/lib/active_record/fixtures.rb +145 -472
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +13 -3
  113. data/lib/active_record/insert_all.rb +179 -0
  114. data/lib/active_record/integration.rb +68 -16
  115. data/lib/active_record/internal_metadata.rb +10 -2
  116. data/lib/active_record/locking/optimistic.rb +5 -6
  117. data/lib/active_record/locking/pessimistic.rb +3 -3
  118. data/lib/active_record/log_subscriber.rb +7 -26
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  121. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/migration/command_recorder.rb +50 -6
  124. data/lib/active_record/migration/compatibility.rb +76 -49
  125. data/lib/active_record/model_schema.rb +33 -9
  126. data/lib/active_record/nested_attributes.rb +2 -2
  127. data/lib/active_record/no_touching.rb +7 -0
  128. data/lib/active_record/persistence.rb +228 -24
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +32 -20
  131. data/lib/active_record/railtie.rb +80 -43
  132. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  133. data/lib/active_record/railties/controller_runtime.rb +30 -35
  134. data/lib/active_record/railties/databases.rake +196 -46
  135. data/lib/active_record/reflection.rb +32 -30
  136. data/lib/active_record/relation.rb +311 -80
  137. data/lib/active_record/relation/batches.rb +13 -10
  138. data/lib/active_record/relation/calculations.rb +53 -47
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +23 -27
  141. data/lib/active_record/relation/merger.rb +11 -20
  142. data/lib/active_record/relation/predicate_builder.rb +4 -6
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  145. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  146. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  147. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  148. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  149. data/lib/active_record/relation/query_attribute.rb +13 -8
  150. data/lib/active_record/relation/query_methods.rb +202 -64
  151. data/lib/active_record/relation/spawn_methods.rb +1 -1
  152. data/lib/active_record/relation/where_clause.rb +14 -10
  153. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  154. data/lib/active_record/result.rb +30 -11
  155. data/lib/active_record/sanitization.rb +32 -40
  156. data/lib/active_record/schema.rb +2 -11
  157. data/lib/active_record/schema_dumper.rb +22 -7
  158. data/lib/active_record/schema_migration.rb +5 -1
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/scoping/default.rb +4 -5
  161. data/lib/active_record/scoping/named.rb +19 -15
  162. data/lib/active_record/statement_cache.rb +30 -3
  163. data/lib/active_record/store.rb +87 -8
  164. data/lib/active_record/table_metadata.rb +10 -17
  165. data/lib/active_record/tasks/database_tasks.rb +194 -25
  166. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  167. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  168. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  169. data/lib/active_record/test_databases.rb +23 -0
  170. data/lib/active_record/test_fixtures.rb +224 -0
  171. data/lib/active_record/timestamp.rb +39 -25
  172. data/lib/active_record/touch_later.rb +4 -2
  173. data/lib/active_record/transactions.rb +56 -65
  174. data/lib/active_record/translation.rb +1 -1
  175. data/lib/active_record/type.rb +3 -4
  176. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  177. data/lib/active_record/type_caster/connection.rb +15 -14
  178. data/lib/active_record/type_caster/map.rb +1 -4
  179. data/lib/active_record/validations.rb +1 -0
  180. data/lib/active_record/validations/uniqueness.rb +15 -27
  181. data/lib/arel.rb +58 -0
  182. data/lib/arel/alias_predication.rb +9 -0
  183. data/lib/arel/attributes.rb +22 -0
  184. data/lib/arel/attributes/attribute.rb +37 -0
  185. data/lib/arel/collectors/bind.rb +24 -0
  186. data/lib/arel/collectors/composite.rb +31 -0
  187. data/lib/arel/collectors/plain_string.rb +20 -0
  188. data/lib/arel/collectors/sql_string.rb +20 -0
  189. data/lib/arel/collectors/substitute_binds.rb +28 -0
  190. data/lib/arel/crud.rb +42 -0
  191. data/lib/arel/delete_manager.rb +18 -0
  192. data/lib/arel/errors.rb +9 -0
  193. data/lib/arel/expressions.rb +29 -0
  194. data/lib/arel/factory_methods.rb +49 -0
  195. data/lib/arel/insert_manager.rb +49 -0
  196. data/lib/arel/math.rb +45 -0
  197. data/lib/arel/nodes.rb +68 -0
  198. data/lib/arel/nodes/and.rb +32 -0
  199. data/lib/arel/nodes/ascending.rb +23 -0
  200. data/lib/arel/nodes/binary.rb +52 -0
  201. data/lib/arel/nodes/bind_param.rb +36 -0
  202. data/lib/arel/nodes/case.rb +55 -0
  203. data/lib/arel/nodes/casted.rb +50 -0
  204. data/lib/arel/nodes/comment.rb +29 -0
  205. data/lib/arel/nodes/count.rb +12 -0
  206. data/lib/arel/nodes/delete_statement.rb +45 -0
  207. data/lib/arel/nodes/descending.rb +23 -0
  208. data/lib/arel/nodes/equality.rb +18 -0
  209. data/lib/arel/nodes/extract.rb +24 -0
  210. data/lib/arel/nodes/false.rb +16 -0
  211. data/lib/arel/nodes/full_outer_join.rb +8 -0
  212. data/lib/arel/nodes/function.rb +44 -0
  213. data/lib/arel/nodes/grouping.rb +8 -0
  214. data/lib/arel/nodes/in.rb +8 -0
  215. data/lib/arel/nodes/infix_operation.rb +80 -0
  216. data/lib/arel/nodes/inner_join.rb +8 -0
  217. data/lib/arel/nodes/insert_statement.rb +37 -0
  218. data/lib/arel/nodes/join_source.rb +20 -0
  219. data/lib/arel/nodes/matches.rb +18 -0
  220. data/lib/arel/nodes/named_function.rb +23 -0
  221. data/lib/arel/nodes/node.rb +50 -0
  222. data/lib/arel/nodes/node_expression.rb +13 -0
  223. data/lib/arel/nodes/outer_join.rb +8 -0
  224. data/lib/arel/nodes/over.rb +15 -0
  225. data/lib/arel/nodes/regexp.rb +16 -0
  226. data/lib/arel/nodes/right_outer_join.rb +8 -0
  227. data/lib/arel/nodes/select_core.rb +67 -0
  228. data/lib/arel/nodes/select_statement.rb +41 -0
  229. data/lib/arel/nodes/sql_literal.rb +16 -0
  230. data/lib/arel/nodes/string_join.rb +11 -0
  231. data/lib/arel/nodes/table_alias.rb +27 -0
  232. data/lib/arel/nodes/terminal.rb +16 -0
  233. data/lib/arel/nodes/true.rb +16 -0
  234. data/lib/arel/nodes/unary.rb +45 -0
  235. data/lib/arel/nodes/unary_operation.rb +20 -0
  236. data/lib/arel/nodes/unqualified_column.rb +22 -0
  237. data/lib/arel/nodes/update_statement.rb +41 -0
  238. data/lib/arel/nodes/values_list.rb +9 -0
  239. data/lib/arel/nodes/window.rb +126 -0
  240. data/lib/arel/nodes/with.rb +11 -0
  241. data/lib/arel/order_predications.rb +13 -0
  242. data/lib/arel/predications.rb +257 -0
  243. data/lib/arel/select_manager.rb +271 -0
  244. data/lib/arel/table.rb +110 -0
  245. data/lib/arel/tree_manager.rb +72 -0
  246. data/lib/arel/update_manager.rb +34 -0
  247. data/lib/arel/visitors.rb +20 -0
  248. data/lib/arel/visitors/depth_first.rb +204 -0
  249. data/lib/arel/visitors/dot.rb +297 -0
  250. data/lib/arel/visitors/ibm_db.rb +34 -0
  251. data/lib/arel/visitors/informix.rb +62 -0
  252. data/lib/arel/visitors/mssql.rb +157 -0
  253. data/lib/arel/visitors/mysql.rb +83 -0
  254. data/lib/arel/visitors/oracle.rb +159 -0
  255. data/lib/arel/visitors/oracle12.rb +66 -0
  256. data/lib/arel/visitors/postgresql.rb +110 -0
  257. data/lib/arel/visitors/sqlite.rb +39 -0
  258. data/lib/arel/visitors/to_sql.rb +889 -0
  259. data/lib/arel/visitors/visitor.rb +46 -0
  260. data/lib/arel/visitors/where_sql.rb +23 -0
  261. data/lib/arel/window_predications.rb +9 -0
  262. data/lib/rails/generators/active_record/migration.rb +14 -1
  263. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  264. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  265. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  266. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  267. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  268. metadata +110 -25
  269. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -46,41 +46,157 @@ 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_hash = resolve_config_for_connection(config_or_env)
51
+ connection_handler.establish_connection(config_hash)
52
+ end
51
53
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_name = spec_name
54
+ # Connects a model to the databases specified. The +database+ keyword
55
+ # takes a hash consisting of a +role+ and a +database_key+.
56
+ #
57
+ # This will create a connection handler for switching between connections,
58
+ # look up the config hash using the +database_key+ and finally
59
+ # establishes a connection to that config.
60
+ #
61
+ # class AnimalsModel < ApplicationRecord
62
+ # self.abstract_class = true
63
+ #
64
+ # connects_to database: { writing: :primary, reading: :primary_replica }
65
+ # end
66
+ #
67
+ # Returns an array of established connections.
68
+ def connects_to(database: {})
69
+ connections = []
55
70
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[:name] = spec_name
71
+ database.each do |role, database_key|
72
+ config_hash = resolve_config_for_connection(database_key)
73
+ handler = lookup_connection_handler(role.to_sym)
59
74
 
60
- connection_handler.establish_connection(spec)
75
+ connections << handler.establish_connection(config_hash)
76
+ end
77
+
78
+ connections
61
79
  end
62
80
 
63
- class MergeAndResolveDefaultUrlConfig # :nodoc:
64
- def initialize(raw_configurations)
65
- @raw_config = raw_configurations.dup
66
- @env = DEFAULT_ENV.call.to_s
67
- end
81
+ # Connects to a database or role (ex writing, reading, or another
82
+ # custom role) for the duration of the block.
83
+ #
84
+ # If a role is passed, Active Record will look up the connection
85
+ # based on the requested role:
86
+ #
87
+ # ActiveRecord::Base.connected_to(role: :writing) do
88
+ # Dog.create! # creates dog using dog writing connection
89
+ # end
90
+ #
91
+ # ActiveRecord::Base.connected_to(role: :reading) do
92
+ # Dog.create! # throws exception because we're on a replica
93
+ # end
94
+ #
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.
102
+ #
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:
110
+ #
111
+ # ActiveRecord::Base.connected_to(database: { readonly_slow: :animals_slow_replica }) do
112
+ # # runs a long query while connected to the +animals_slow_replica+ using the readonly_slow role.
113
+ # Dog.run_a_long_query
114
+ # end
115
+ #
116
+ # When using the database key a new connection will be established every time. It is not
117
+ # recommended to use this outside of one-off scripts.
118
+ def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
119
+ if database && role
120
+ raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
121
+ elsif database
122
+ if database.is_a?(Hash)
123
+ role, database = database.first
124
+ role = role.to_sym
125
+ end
68
126
 
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
73
- end
127
+ config_hash = resolve_config_for_connection(database)
128
+ handler = lookup_connection_handler(role)
129
+
130
+ handler.establish_connection(config_hash)
74
131
 
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
132
+ with_handler(role, &blk)
133
+ elsif role
134
+ if role == writing_role
135
+ with_handler(role.to_sym) do
136
+ connection_handler.while_preventing_writes(prevent_writes, &blk)
82
137
  end
138
+ else
139
+ with_handler(role.to_sym, &blk)
140
+ end
141
+ else
142
+ raise ArgumentError, "must provide a `database` or a `role`."
143
+ end
144
+ end
145
+
146
+ # Returns true if role is the current connected role.
147
+ #
148
+ # ActiveRecord::Base.connected_to(role: :writing) do
149
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
150
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
151
+ # end
152
+ def connected_to?(role:)
153
+ current_role == role.to_sym
154
+ end
155
+
156
+ # Returns the symbol representing the current connected role.
157
+ #
158
+ # ActiveRecord::Base.connected_to(role: :writing) do
159
+ # ActiveRecord::Base.current_role #=> :writing
160
+ # end
161
+ #
162
+ # ActiveRecord::Base.connected_to(role: :reading) do
163
+ # ActiveRecord::Base.current_role #=> :reading
164
+ # end
165
+ def current_role
166
+ connection_handlers.key(connection_handler)
167
+ end
168
+
169
+ def lookup_connection_handler(handler_key) # :nodoc:
170
+ handler_key ||= ActiveRecord::Base.writing_role
171
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
172
+ end
173
+
174
+ def with_handler(handler_key, &blk) # :nodoc:
175
+ handler = lookup_connection_handler(handler_key)
176
+ swap_connection_handler(handler, &blk)
177
+ end
178
+
179
+ def resolve_config_for_connection(config_or_env) # :nodoc:
180
+ raise "Anonymous class is not allowed." unless name
181
+
182
+ config_or_env ||= DEFAULT_ENV.call.to_sym
183
+ pool_name = primary_class? ? "primary" : name
184
+ self.connection_specification_name = pool_name
185
+
186
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
187
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
188
+ config_hash[:name] = pool_name
189
+
190
+ config_hash
191
+ end
192
+
193
+ # Clears the query cache for all connections associated with the current thread.
194
+ def clear_query_caches_for_current_thread
195
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
196
+ handler.connection_pool_list.each do |pool|
197
+ pool.connection.clear_query_cache if pool.active_connection?
83
198
  end
199
+ end
84
200
  end
85
201
 
86
202
  # Returns the connection currently associated with the class. This can
@@ -100,6 +216,10 @@ module ActiveRecord
100
216
  @connection_specification_name
101
217
  end
102
218
 
219
+ def primary_class? # :nodoc:
220
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
221
+ end
222
+
103
223
  # Returns the configuration of the associated connection as a hash:
104
224
  #
105
225
  # ActiveRecord::Base.connection_config
@@ -141,5 +261,14 @@ module ActiveRecord
141
261
 
142
262
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
263
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
264
+
265
+ private
266
+
267
+ def swap_connection_handler(handler, &blk) # :nodoc:
268
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
269
+ yield
270
+ ensure
271
+ ActiveRecord::Base.connection_handler = old_handler
272
+ end
144
273
  end
145
274
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "active_support/core_ext/hash/indifferent_access"
4
4
  require "active_support/core_ext/string/filters"
5
+ require "active_support/parameter_filter"
5
6
  require "concurrent/map"
6
7
 
7
8
  module ActiveRecord
@@ -26,7 +27,7 @@ module ActiveRecord
26
27
 
27
28
  ##
28
29
  # Contains the database configuration - as is typically stored in config/database.yml -
29
- # as a Hash.
30
+ # as an ActiveRecord::DatabaseConfigurations object.
30
31
  #
31
32
  # For example, the following database.yml...
32
33
  #
@@ -40,22 +41,18 @@ module ActiveRecord
40
41
  #
41
42
  # ...would result in ActiveRecord::Base.configurations to look like this:
42
43
  #
43
- # {
44
- # 'development' => {
45
- # 'adapter' => 'sqlite3',
46
- # 'database' => 'db/development.sqlite3'
47
- # },
48
- # 'production' => {
49
- # 'adapter' => 'sqlite3',
50
- # 'database' => 'db/production.sqlite3'
51
- # }
52
- # }
44
+ # #<ActiveRecord::DatabaseConfigurations:0x00007fd1acbdf800 @configurations=[
45
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbded10 @env_name="development",
46
+ # @spec_name="primary", @config={"adapter"=>"sqlite3", "database"=>"db/development.sqlite3"}>,
47
+ # #<ActiveRecord::DatabaseConfigurations::HashConfig:0x00007fd1acbdea90 @env_name="production",
48
+ # @spec_name="primary", @config={"adapter"=>"mysql2", "database"=>"db/production.sqlite3"}>
49
+ # ]>
53
50
  def self.configurations=(config)
54
- @@configurations = ActiveRecord::ConnectionHandling::MergeAndResolveDefaultUrlConfig.new(config).resolve
51
+ @@configurations = ActiveRecord::DatabaseConfigurations.new(config)
55
52
  end
56
53
  self.configurations = {}
57
54
 
58
- # Returns fully resolved configurations hash
55
+ # Returns fully resolved ActiveRecord::DatabaseConfigurations object
59
56
  def self.configurations
60
57
  @@configurations
61
58
  end
@@ -99,7 +96,7 @@ module ActiveRecord
99
96
  ##
100
97
  # :singleton-method:
101
98
  # 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
99
+ # db:migrate rails command. This is true by default, which is useful for the
103
100
  # development environment. This should ideally be false in the production
104
101
  # environment where dumping schema is rarely needed.
105
102
  mattr_accessor :dump_schema_after_migration, instance_writer: false, default: true
@@ -125,25 +122,28 @@ module ActiveRecord
125
122
 
126
123
  mattr_accessor :belongs_to_required_by_default, instance_accessor: false
127
124
 
125
+ mattr_accessor :connection_handlers, instance_accessor: false, default: {}
126
+
127
+ mattr_accessor :writing_role, instance_accessor: false, default: :writing
128
+
129
+ mattr_accessor :reading_role, instance_accessor: false, default: :reading
130
+
128
131
  class_attribute :default_connection_handler, instance_writer: false
129
132
 
133
+ self.filter_attributes = []
134
+
130
135
  def self.connection_handler
131
- ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
136
+ Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
132
137
  end
133
138
 
134
139
  def self.connection_handler=(handler)
135
- ActiveRecord::RuntimeRegistry.connection_handler = handler
140
+ Thread.current.thread_variable_set("ar_connection_handler", handler)
136
141
  end
137
142
 
138
143
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
139
144
  end
140
145
 
141
- module ClassMethods # :nodoc:
142
- def allocate
143
- define_attribute_methods
144
- super
145
- end
146
-
146
+ module ClassMethods
147
147
  def initialize_find_by_cache # :nodoc:
148
148
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
149
149
  end
@@ -160,7 +160,7 @@ module ActiveRecord
160
160
  return super if block_given? ||
161
161
  primary_key.nil? ||
162
162
  scope_attributes? ||
163
- columns_hash.include?(inheritance_column)
163
+ columns_hash.key?(inheritance_column) && !base_class?
164
164
 
165
165
  id = ids.first
166
166
 
@@ -172,20 +172,16 @@ module ActiveRecord
172
172
  where(key => params.bind).limit(1)
173
173
  }
174
174
 
175
- record = statement.execute([id], connection).first
175
+ record = statement.execute([id], connection)&.first
176
176
  unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
178
- name, primary_key, id)
177
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
179
178
  end
180
179
  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)
184
180
  end
185
181
 
186
182
  def find_by(*args) # :nodoc:
187
183
  return super if scope_attributes? || reflect_on_all_aggregations.any? ||
188
- columns_hash.key?(inheritance_column) && base_class != self
184
+ columns_hash.key?(inheritance_column) && !base_class?
189
185
 
190
186
  hash = args.first
191
187
 
@@ -205,11 +201,9 @@ module ActiveRecord
205
201
  where(wheres).limit(1)
206
202
  }
207
203
  begin
208
- statement.execute(hash.values, connection).first
204
+ statement.execute(hash.values, connection)&.first
209
205
  rescue TypeError
210
206
  raise ActiveRecord::StatementInvalid
211
- rescue ::RangeError
212
- nil
213
207
  end
214
208
  end
215
209
 
@@ -221,7 +215,7 @@ module ActiveRecord
221
215
  generated_association_methods
222
216
  end
223
217
 
224
- def generated_association_methods
218
+ def generated_association_methods # :nodoc:
225
219
  @generated_association_methods ||= begin
226
220
  mod = const_set(:GeneratedAssociationMethods, Module.new)
227
221
  private_constant :GeneratedAssociationMethods
@@ -231,8 +225,20 @@ module ActiveRecord
231
225
  end
232
226
  end
233
227
 
228
+ # Returns columns which shouldn't be exposed while calling +#inspect+.
229
+ def filter_attributes
230
+ if defined?(@filter_attributes)
231
+ @filter_attributes
232
+ else
233
+ superclass.filter_attributes
234
+ end
235
+ end
236
+
237
+ # Specifies columns which shouldn't be exposed while calling +#inspect+.
238
+ attr_writer :filter_attributes
239
+
234
240
  # Returns a string like 'Post(id:integer, title:string, body:text)'
235
- def inspect
241
+ def inspect # :nodoc:
236
242
  if self == Base
237
243
  super
238
244
  elsif abstract_class?
@@ -248,7 +254,7 @@ module ActiveRecord
248
254
  end
249
255
 
250
256
  # Overwrite the default class equality method to provide support for decorated models.
251
- def ===(object)
257
+ def ===(object) # :nodoc:
252
258
  object.is_a?(self)
253
259
  end
254
260
 
@@ -262,7 +268,8 @@ module ActiveRecord
262
268
  end
263
269
 
264
270
  def arel_attribute(name, table = arel_table) # :nodoc:
265
- name = attribute_alias(name) if attribute_alias?(name)
271
+ name = name.to_s
272
+ name = attribute_aliases[name] || name
266
273
  table[name]
267
274
  end
268
275
 
@@ -274,6 +281,10 @@ module ActiveRecord
274
281
  TypeCaster::Map.new(self)
275
282
  end
276
283
 
284
+ def _internal? # :nodoc:
285
+ false
286
+ end
287
+
277
288
  private
278
289
 
279
290
  def cached_find_by_statement(key, &block)
@@ -306,7 +317,7 @@ module ActiveRecord
306
317
  # # Instantiates a single new object
307
318
  # User.new(first_name: 'Jamie')
308
319
  def initialize(attributes = nil)
309
- self.class.define_attribute_methods
320
+ @new_record = true
310
321
  @attributes = self.class._default_attributes.deep_dup
311
322
 
312
323
  init_internals
@@ -332,15 +343,21 @@ module ActiveRecord
332
343
  # post = Post.allocate
333
344
  # post.init_with(coder)
334
345
  # post.title # => 'hello world'
335
- def init_with(coder)
346
+ def init_with(coder, &block)
336
347
  coder = LegacyYamlAdapter.convert(self.class, coder)
337
- @attributes = self.class.yaml_encoder.decode(coder)
338
-
339
- init_internals
348
+ attributes = self.class.yaml_encoder.decode(coder)
349
+ init_with_attributes(attributes, coder["new_record"], &block)
350
+ end
340
351
 
341
- @new_record = coder["new_record"]
352
+ ##
353
+ # Initialize an empty model object from +attributes+.
354
+ # +attributes+ should be an attributes object, and unlike the
355
+ # `initialize` method, no assignment calls are made per attribute.
356
+ def init_with_attributes(attributes, new_record = false) # :nodoc:
357
+ @new_record = new_record
358
+ @attributes = attributes
342
359
 
343
- self.class.define_attribute_methods
360
+ init_internals
344
361
 
345
362
  yield self if block_given?
346
363
 
@@ -379,13 +396,13 @@ module ActiveRecord
379
396
  ##
380
397
  def initialize_dup(other) # :nodoc:
381
398
  @attributes = @attributes.deep_dup
382
- @attributes.reset(self.class.primary_key)
399
+ @attributes.reset(@primary_key)
383
400
 
384
401
  _run_initialize_callbacks
385
402
 
386
403
  @new_record = true
387
404
  @destroyed = false
388
- @_start_transaction_state = {}
405
+ @_start_transaction_state = nil
389
406
  @transaction_state = nil
390
407
 
391
408
  super
@@ -446,6 +463,7 @@ module ActiveRecord
446
463
 
447
464
  # Returns +true+ if the attributes hash has been frozen.
448
465
  def frozen?
466
+ sync_with_transaction_state if @transaction_state&.finalized?
449
467
  @attributes.frozen?
450
468
  end
451
469
 
@@ -458,6 +476,14 @@ module ActiveRecord
458
476
  end
459
477
  end
460
478
 
479
+ def present? # :nodoc:
480
+ true
481
+ end
482
+
483
+ def blank? # :nodoc:
484
+ false
485
+ end
486
+
461
487
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
462
488
  # attributes will be marked as read only since they cannot be saved.
463
489
  def readonly?
@@ -480,7 +506,14 @@ module ActiveRecord
480
506
  inspection = if defined?(@attributes) && @attributes
481
507
  self.class.attribute_names.collect do |name|
482
508
  if has_attribute?(name)
483
- "#{name}: #{attribute_for_inspect(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}"
484
517
  end
485
518
  end.compact.join(", ")
486
519
  else
@@ -496,15 +529,16 @@ module ActiveRecord
496
529
  return super if custom_inspect_method_defined?
497
530
  pp.object_address_group(self) do
498
531
  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)
532
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
533
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
502
534
  pp.breakable " "
503
535
  pp.group(1) do
504
- pp.text column_name
536
+ pp.text attr_name
505
537
  pp.text ":"
506
538
  pp.breakable
507
- pp.pp column_value
539
+ value = _read_attribute(attr_name)
540
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
541
+ pp.pp value
508
542
  end
509
543
  end
510
544
  else
@@ -534,26 +568,36 @@ module ActiveRecord
534
568
  end
535
569
 
536
570
  def init_internals
571
+ @primary_key = self.class.primary_key
537
572
  @readonly = false
538
573
  @destroyed = false
539
574
  @marked_for_destruction = false
540
575
  @destroyed_by_association = nil
541
- @new_record = true
542
- @_start_transaction_state = {}
576
+ @_start_transaction_state = nil
543
577
  @transaction_state = nil
578
+
579
+ self.class.define_attribute_methods
544
580
  end
545
581
 
546
582
  def initialize_internals_callback
547
583
  end
548
584
 
549
- def thaw
550
- if frozen?
551
- @attributes = @attributes.dup
585
+ def custom_inspect_method_defined?
586
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
587
+ end
588
+
589
+ class InspectionMask < DelegateClass(::String)
590
+ def pretty_print(pp)
591
+ pp.text __getobj__
552
592
  end
553
593
  end
594
+ private_constant :InspectionMask
554
595
 
555
- def custom_inspect_method_defined?
556
- self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
596
+ def inspection_filter
597
+ @inspection_filter ||= begin
598
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
599
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
600
+ end
557
601
  end
558
602
  end
559
603
  end