activerecord 5.2.2.1 → 6.0.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 (269) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +734 -508
  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 +5 -15
  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 +16 -28
  22. data/lib/active_record/associations/collection_proxy.rb +19 -48
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +3 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +20 -25
  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 +27 -7
  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 +22 -8
  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 +137 -26
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +114 -130
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +26 -11
  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 +135 -56
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +189 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +151 -198
  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 +7 -11
  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 +47 -0
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +61 -77
  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 +45 -5
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +131 -143
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +104 -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 +30 -16
  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 +3 -3
  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 +91 -64
  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 +231 -25
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +33 -22
  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 +199 -46
  135. data/lib/active_record/reflection.rb +42 -44
  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 +67 -57
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +28 -28
  141. data/lib/active_record/relation/merger.rb +17 -23
  142. data/lib/active_record/relation/predicate_builder.rb +18 -15
  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 +17 -10
  150. data/lib/active_record/relation/query_methods.rb +247 -73
  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 +6 -7
  161. data/lib/active_record/scoping/named.rb +20 -15
  162. data/lib/active_record/statement_cache.rb +32 -5
  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 +225 -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 +57 -66
  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 +111 -26
  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,19 +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
- return super if scope_attributes? || reflect_on_all_aggregations.any?
183
+ return super if scope_attributes? || reflect_on_all_aggregations.any? ||
184
+ columns_hash.key?(inheritance_column) && !base_class?
188
185
 
189
186
  hash = args.first
190
187
 
@@ -204,11 +201,9 @@ module ActiveRecord
204
201
  where(wheres).limit(1)
205
202
  }
206
203
  begin
207
- statement.execute(hash.values, connection).first
204
+ statement.execute(hash.values, connection)&.first
208
205
  rescue TypeError
209
206
  raise ActiveRecord::StatementInvalid
210
- rescue ::RangeError
211
- nil
212
207
  end
213
208
  end
214
209
 
@@ -220,7 +215,7 @@ module ActiveRecord
220
215
  generated_association_methods
221
216
  end
222
217
 
223
- def generated_association_methods
218
+ def generated_association_methods # :nodoc:
224
219
  @generated_association_methods ||= begin
225
220
  mod = const_set(:GeneratedAssociationMethods, Module.new)
226
221
  private_constant :GeneratedAssociationMethods
@@ -230,8 +225,20 @@ module ActiveRecord
230
225
  end
231
226
  end
232
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
+
233
240
  # Returns a string like 'Post(id:integer, title:string, body:text)'
234
- def inspect
241
+ def inspect # :nodoc:
235
242
  if self == Base
236
243
  super
237
244
  elsif abstract_class?
@@ -247,7 +254,7 @@ module ActiveRecord
247
254
  end
248
255
 
249
256
  # Overwrite the default class equality method to provide support for decorated models.
250
- def ===(object)
257
+ def ===(object) # :nodoc:
251
258
  object.is_a?(self)
252
259
  end
253
260
 
@@ -261,7 +268,8 @@ module ActiveRecord
261
268
  end
262
269
 
263
270
  def arel_attribute(name, table = arel_table) # :nodoc:
264
- name = attribute_alias(name) if attribute_alias?(name)
271
+ name = name.to_s
272
+ name = attribute_aliases[name] || name
265
273
  table[name]
266
274
  end
267
275
 
@@ -273,6 +281,10 @@ module ActiveRecord
273
281
  TypeCaster::Map.new(self)
274
282
  end
275
283
 
284
+ def _internal? # :nodoc:
285
+ false
286
+ end
287
+
276
288
  private
277
289
 
278
290
  def cached_find_by_statement(key, &block)
@@ -305,7 +317,7 @@ module ActiveRecord
305
317
  # # Instantiates a single new object
306
318
  # User.new(first_name: 'Jamie')
307
319
  def initialize(attributes = nil)
308
- self.class.define_attribute_methods
320
+ @new_record = true
309
321
  @attributes = self.class._default_attributes.deep_dup
310
322
 
311
323
  init_internals
@@ -331,15 +343,21 @@ module ActiveRecord
331
343
  # post = Post.allocate
332
344
  # post.init_with(coder)
333
345
  # post.title # => 'hello world'
334
- def init_with(coder)
346
+ def init_with(coder, &block)
335
347
  coder = LegacyYamlAdapter.convert(self.class, coder)
336
- @attributes = self.class.yaml_encoder.decode(coder)
337
-
338
- init_internals
348
+ attributes = self.class.yaml_encoder.decode(coder)
349
+ init_with_attributes(attributes, coder["new_record"], &block)
350
+ end
339
351
 
340
- @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
341
359
 
342
- self.class.define_attribute_methods
360
+ init_internals
343
361
 
344
362
  yield self if block_given?
345
363
 
@@ -378,13 +396,13 @@ module ActiveRecord
378
396
  ##
379
397
  def initialize_dup(other) # :nodoc:
380
398
  @attributes = @attributes.deep_dup
381
- @attributes.reset(self.class.primary_key)
399
+ @attributes.reset(@primary_key)
382
400
 
383
401
  _run_initialize_callbacks
384
402
 
385
403
  @new_record = true
386
404
  @destroyed = false
387
- @_start_transaction_state = {}
405
+ @_start_transaction_state = nil
388
406
  @transaction_state = nil
389
407
 
390
408
  super
@@ -445,6 +463,7 @@ module ActiveRecord
445
463
 
446
464
  # Returns +true+ if the attributes hash has been frozen.
447
465
  def frozen?
466
+ sync_with_transaction_state if @transaction_state&.finalized?
448
467
  @attributes.frozen?
449
468
  end
450
469
 
@@ -457,6 +476,14 @@ module ActiveRecord
457
476
  end
458
477
  end
459
478
 
479
+ def present? # :nodoc:
480
+ true
481
+ end
482
+
483
+ def blank? # :nodoc:
484
+ false
485
+ end
486
+
460
487
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
461
488
  # attributes will be marked as read only since they cannot be saved.
462
489
  def readonly?
@@ -479,7 +506,14 @@ module ActiveRecord
479
506
  inspection = if defined?(@attributes) && @attributes
480
507
  self.class.attribute_names.collect do |name|
481
508
  if has_attribute?(name)
482
- "#{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}"
483
517
  end
484
518
  end.compact.join(", ")
485
519
  else
@@ -495,15 +529,16 @@ module ActiveRecord
495
529
  return super if custom_inspect_method_defined?
496
530
  pp.object_address_group(self) do
497
531
  if defined?(@attributes) && @attributes
498
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
499
- pp.seplist(column_names, proc { pp.text "," }) do |column_name|
500
- 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|
501
534
  pp.breakable " "
502
535
  pp.group(1) do
503
- pp.text column_name
536
+ pp.text attr_name
504
537
  pp.text ":"
505
538
  pp.breakable
506
- 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
507
542
  end
508
543
  end
509
544
  else
@@ -533,26 +568,36 @@ module ActiveRecord
533
568
  end
534
569
 
535
570
  def init_internals
571
+ @primary_key = self.class.primary_key
536
572
  @readonly = false
537
573
  @destroyed = false
538
574
  @marked_for_destruction = false
539
575
  @destroyed_by_association = nil
540
- @new_record = true
541
- @_start_transaction_state = {}
576
+ @_start_transaction_state = nil
542
577
  @transaction_state = nil
578
+
579
+ self.class.define_attribute_methods
543
580
  end
544
581
 
545
582
  def initialize_internals_callback
546
583
  end
547
584
 
548
- def thaw
549
- if frozen?
550
- @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__
551
592
  end
552
593
  end
594
+ private_constant :InspectionMask
553
595
 
554
- def custom_inspect_method_defined?
555
- 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
556
601
  end
557
602
  end
558
603
  end