activerecord 5.2.8.1 → 6.0.6

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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +919 -573
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +5 -3
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record/advisory_lock_base.rb +18 -0
  7. data/lib/active_record/aggregations.rb +4 -3
  8. data/lib/active_record/association_relation.rb +10 -8
  9. data/lib/active_record/associations/alias_tracker.rb +0 -1
  10. data/lib/active_record/associations/association.rb +55 -19
  11. data/lib/active_record/associations/association_scope.rb +11 -7
  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 -40
  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 +19 -23
  22. data/lib/active_record/associations/collection_proxy.rb +14 -17
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -11
  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/join_association.rb +16 -10
  29. data/lib/active_record/associations/join_dependency/join_part.rb +4 -4
  30. data/lib/active_record/associations/join_dependency.rb +47 -30
  31. data/lib/active_record/associations/preloader/association.rb +61 -41
  32. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  33. data/lib/active_record/associations/preloader.rb +44 -33
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/associations/through_association.rb +1 -1
  36. data/lib/active_record/associations.rb +21 -16
  37. data/lib/active_record/attribute_assignment.rb +7 -11
  38. data/lib/active_record/attribute_decorators.rb +0 -2
  39. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -2
  40. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  41. data/lib/active_record/attribute_methods/primary_key.rb +15 -24
  42. data/lib/active_record/attribute_methods/query.rb +2 -3
  43. data/lib/active_record/attribute_methods/read.rb +15 -54
  44. data/lib/active_record/attribute_methods/serialization.rb +1 -2
  45. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -3
  46. data/lib/active_record/attribute_methods/write.rb +17 -25
  47. data/lib/active_record/attribute_methods.rb +28 -100
  48. data/lib/active_record/attributes.rb +13 -1
  49. data/lib/active_record/autosave_association.rb +12 -14
  50. data/lib/active_record/base.rb +2 -3
  51. data/lib/active_record/callbacks.rb +6 -21
  52. data/lib/active_record/coders/yaml_column.rb +15 -6
  53. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +109 -18
  54. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  55. data/lib/active_record/connection_adapters/abstract/database_statements.rb +102 -124
  56. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -9
  57. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  58. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +20 -14
  59. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +105 -72
  60. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  61. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +175 -79
  62. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -57
  63. data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -43
  64. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +149 -217
  65. data/lib/active_record/connection_adapters/column.rb +17 -13
  66. data/lib/active_record/connection_adapters/connection_specification.rb +54 -45
  67. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  68. data/lib/active_record/connection_adapters/mysql/column.rb +1 -1
  69. data/lib/active_record/connection_adapters/mysql/database_statements.rb +70 -14
  70. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +0 -1
  71. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  72. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +4 -6
  73. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  74. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  75. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +139 -19
  76. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -10
  78. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  79. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +26 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -2
  81. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  82. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +8 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +0 -1
  84. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -2
  85. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +1 -1
  87. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -2
  88. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +25 -7
  89. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +1 -1
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +5 -3
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +1 -1
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +14 -3
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +0 -1
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +63 -75
  98. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +0 -1
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +168 -75
  101. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  102. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  103. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +119 -0
  104. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -7
  105. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -12
  106. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +137 -147
  107. data/lib/active_record/connection_adapters/statement_pool.rb +0 -1
  108. data/lib/active_record/connection_handling.rb +139 -26
  109. data/lib/active_record/core.rb +108 -67
  110. data/lib/active_record/counter_cache.rb +8 -30
  111. data/lib/active_record/database_configurations/database_config.rb +37 -0
  112. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  113. data/lib/active_record/database_configurations/url_config.rb +78 -0
  114. data/lib/active_record/database_configurations.rb +233 -0
  115. data/lib/active_record/dynamic_matchers.rb +3 -4
  116. data/lib/active_record/enum.rb +44 -7
  117. data/lib/active_record/errors.rb +15 -7
  118. data/lib/active_record/explain.rb +1 -2
  119. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  120. data/lib/active_record/fixture_set/render_context.rb +17 -0
  121. data/lib/active_record/fixture_set/table_row.rb +152 -0
  122. data/lib/active_record/fixture_set/table_rows.rb +46 -0
  123. data/lib/active_record/fixtures.rb +144 -474
  124. data/lib/active_record/gem_version.rb +4 -4
  125. data/lib/active_record/inheritance.rb +13 -6
  126. data/lib/active_record/insert_all.rb +179 -0
  127. data/lib/active_record/integration.rb +68 -16
  128. data/lib/active_record/internal_metadata.rb +11 -3
  129. data/lib/active_record/locking/optimistic.rb +14 -7
  130. data/lib/active_record/locking/pessimistic.rb +3 -3
  131. data/lib/active_record/log_subscriber.rb +8 -27
  132. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  133. data/lib/active_record/middleware/database_selector/resolver.rb +87 -0
  134. data/lib/active_record/middleware/database_selector.rb +74 -0
  135. data/lib/active_record/migration/command_recorder.rb +54 -22
  136. data/lib/active_record/migration/compatibility.rb +79 -52
  137. data/lib/active_record/migration/join_table.rb +0 -1
  138. data/lib/active_record/migration.rb +104 -85
  139. data/lib/active_record/model_schema.rb +62 -11
  140. data/lib/active_record/nested_attributes.rb +2 -4
  141. data/lib/active_record/no_touching.rb +9 -2
  142. data/lib/active_record/null_relation.rb +0 -1
  143. data/lib/active_record/persistence.rb +232 -29
  144. data/lib/active_record/query_cache.rb +11 -4
  145. data/lib/active_record/querying.rb +33 -21
  146. data/lib/active_record/railtie.rb +80 -61
  147. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  148. data/lib/active_record/railties/controller_runtime.rb +30 -35
  149. data/lib/active_record/railties/databases.rake +199 -46
  150. data/lib/active_record/reflection.rb +51 -51
  151. data/lib/active_record/relation/batches.rb +13 -11
  152. data/lib/active_record/relation/calculations.rb +55 -49
  153. data/lib/active_record/relation/delegation.rb +35 -50
  154. data/lib/active_record/relation/finder_methods.rb +23 -28
  155. data/lib/active_record/relation/from_clause.rb +4 -0
  156. data/lib/active_record/relation/merger.rb +12 -17
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  158. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  159. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  160. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  161. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  162. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  163. data/lib/active_record/relation/predicate_builder.rb +5 -11
  164. data/lib/active_record/relation/query_attribute.rb +13 -8
  165. data/lib/active_record/relation/query_methods.rb +232 -69
  166. data/lib/active_record/relation/spawn_methods.rb +1 -2
  167. data/lib/active_record/relation/where_clause.rb +14 -11
  168. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  169. data/lib/active_record/relation.rb +326 -81
  170. data/lib/active_record/result.rb +30 -12
  171. data/lib/active_record/sanitization.rb +32 -40
  172. data/lib/active_record/schema.rb +2 -11
  173. data/lib/active_record/schema_dumper.rb +22 -7
  174. data/lib/active_record/schema_migration.rb +6 -2
  175. data/lib/active_record/scoping/default.rb +4 -6
  176. data/lib/active_record/scoping/named.rb +25 -16
  177. data/lib/active_record/scoping.rb +8 -9
  178. data/lib/active_record/statement_cache.rb +30 -3
  179. data/lib/active_record/store.rb +87 -8
  180. data/lib/active_record/suppressor.rb +2 -2
  181. data/lib/active_record/table_metadata.rb +23 -15
  182. data/lib/active_record/tasks/database_tasks.rb +194 -25
  183. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -6
  184. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -8
  185. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -9
  186. data/lib/active_record/test_databases.rb +23 -0
  187. data/lib/active_record/test_fixtures.rb +243 -0
  188. data/lib/active_record/timestamp.rb +39 -26
  189. data/lib/active_record/touch_later.rb +5 -4
  190. data/lib/active_record/transactions.rb +64 -73
  191. data/lib/active_record/translation.rb +1 -1
  192. data/lib/active_record/type/adapter_specific_registry.rb +3 -13
  193. data/lib/active_record/type/hash_lookup_type_map.rb +0 -1
  194. data/lib/active_record/type/serialized.rb +0 -1
  195. data/lib/active_record/type/time.rb +10 -0
  196. data/lib/active_record/type/type_map.rb +0 -1
  197. data/lib/active_record/type/unsigned_integer.rb +0 -1
  198. data/lib/active_record/type.rb +3 -5
  199. data/lib/active_record/type_caster/connection.rb +15 -14
  200. data/lib/active_record/type_caster/map.rb +1 -4
  201. data/lib/active_record/validations/associated.rb +0 -1
  202. data/lib/active_record/validations/uniqueness.rb +15 -27
  203. data/lib/active_record/validations.rb +3 -3
  204. data/lib/active_record.rb +10 -2
  205. data/lib/arel/alias_predication.rb +9 -0
  206. data/lib/arel/attributes/attribute.rb +37 -0
  207. data/lib/arel/attributes.rb +22 -0
  208. data/lib/arel/collectors/bind.rb +24 -0
  209. data/lib/arel/collectors/composite.rb +31 -0
  210. data/lib/arel/collectors/plain_string.rb +20 -0
  211. data/lib/arel/collectors/sql_string.rb +20 -0
  212. data/lib/arel/collectors/substitute_binds.rb +28 -0
  213. data/lib/arel/crud.rb +42 -0
  214. data/lib/arel/delete_manager.rb +18 -0
  215. data/lib/arel/errors.rb +9 -0
  216. data/lib/arel/expressions.rb +29 -0
  217. data/lib/arel/factory_methods.rb +49 -0
  218. data/lib/arel/insert_manager.rb +49 -0
  219. data/lib/arel/math.rb +45 -0
  220. data/lib/arel/nodes/and.rb +32 -0
  221. data/lib/arel/nodes/ascending.rb +23 -0
  222. data/lib/arel/nodes/binary.rb +52 -0
  223. data/lib/arel/nodes/bind_param.rb +36 -0
  224. data/lib/arel/nodes/case.rb +55 -0
  225. data/lib/arel/nodes/casted.rb +50 -0
  226. data/lib/arel/nodes/comment.rb +29 -0
  227. data/lib/arel/nodes/count.rb +12 -0
  228. data/lib/arel/nodes/delete_statement.rb +45 -0
  229. data/lib/arel/nodes/descending.rb +23 -0
  230. data/lib/arel/nodes/equality.rb +18 -0
  231. data/lib/arel/nodes/extract.rb +24 -0
  232. data/lib/arel/nodes/false.rb +16 -0
  233. data/lib/arel/nodes/full_outer_join.rb +8 -0
  234. data/lib/arel/nodes/function.rb +44 -0
  235. data/lib/arel/nodes/grouping.rb +8 -0
  236. data/lib/arel/nodes/in.rb +8 -0
  237. data/lib/arel/nodes/infix_operation.rb +80 -0
  238. data/lib/arel/nodes/inner_join.rb +8 -0
  239. data/lib/arel/nodes/insert_statement.rb +37 -0
  240. data/lib/arel/nodes/join_source.rb +20 -0
  241. data/lib/arel/nodes/matches.rb +18 -0
  242. data/lib/arel/nodes/named_function.rb +23 -0
  243. data/lib/arel/nodes/node.rb +50 -0
  244. data/lib/arel/nodes/node_expression.rb +13 -0
  245. data/lib/arel/nodes/outer_join.rb +8 -0
  246. data/lib/arel/nodes/over.rb +15 -0
  247. data/lib/arel/nodes/regexp.rb +16 -0
  248. data/lib/arel/nodes/right_outer_join.rb +8 -0
  249. data/lib/arel/nodes/select_core.rb +67 -0
  250. data/lib/arel/nodes/select_statement.rb +41 -0
  251. data/lib/arel/nodes/sql_literal.rb +16 -0
  252. data/lib/arel/nodes/string_join.rb +11 -0
  253. data/lib/arel/nodes/table_alias.rb +27 -0
  254. data/lib/arel/nodes/terminal.rb +16 -0
  255. data/lib/arel/nodes/true.rb +16 -0
  256. data/lib/arel/nodes/unary.rb +45 -0
  257. data/lib/arel/nodes/unary_operation.rb +20 -0
  258. data/lib/arel/nodes/unqualified_column.rb +22 -0
  259. data/lib/arel/nodes/update_statement.rb +41 -0
  260. data/lib/arel/nodes/values_list.rb +9 -0
  261. data/lib/arel/nodes/window.rb +126 -0
  262. data/lib/arel/nodes/with.rb +11 -0
  263. data/lib/arel/nodes.rb +68 -0
  264. data/lib/arel/order_predications.rb +13 -0
  265. data/lib/arel/predications.rb +256 -0
  266. data/lib/arel/select_manager.rb +271 -0
  267. data/lib/arel/table.rb +110 -0
  268. data/lib/arel/tree_manager.rb +72 -0
  269. data/lib/arel/update_manager.rb +34 -0
  270. data/lib/arel/visitors/depth_first.rb +203 -0
  271. data/lib/arel/visitors/dot.rb +296 -0
  272. data/lib/arel/visitors/ibm_db.rb +34 -0
  273. data/lib/arel/visitors/informix.rb +62 -0
  274. data/lib/arel/visitors/mssql.rb +156 -0
  275. data/lib/arel/visitors/mysql.rb +83 -0
  276. data/lib/arel/visitors/oracle.rb +158 -0
  277. data/lib/arel/visitors/oracle12.rb +65 -0
  278. data/lib/arel/visitors/postgresql.rb +109 -0
  279. data/lib/arel/visitors/sqlite.rb +38 -0
  280. data/lib/arel/visitors/to_sql.rb +888 -0
  281. data/lib/arel/visitors/visitor.rb +45 -0
  282. data/lib/arel/visitors/where_sql.rb +22 -0
  283. data/lib/arel/visitors.rb +20 -0
  284. data/lib/arel/window_predications.rb +9 -0
  285. data/lib/arel.rb +62 -0
  286. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +0 -1
  287. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  288. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  289. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  290. data/lib/rails/generators/active_record/migration.rb +14 -2
  291. data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
  292. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  293. metadata +112 -25
  294. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -46,41 +46,140 @@ 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
53
+
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 = []
51
70
 
52
- config ||= DEFAULT_ENV.call.to_sym
53
- spec_name = self == Base ? "primary" : name
54
- self.connection_specification_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)
55
74
 
56
- resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
57
- spec = resolver.resolve(config).symbolize_keys
58
- spec[:name] = spec_name
75
+ connections << handler.establish_connection(config_hash)
76
+ end
59
77
 
60
- connection_handler.establish_connection(spec)
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
+ # The `database` kwarg is deprecated in 6.1 and will be removed in 6.2
100
+ #
101
+ # It is not recommended for use as it re-establishes a connection every
102
+ # time it is called.
103
+ def connected_to(database: nil, role: nil, prevent_writes: false, &blk)
104
+ if database && role
105
+ raise ArgumentError, "connected_to can only accept a `database` or a `role` argument, but not both arguments."
106
+ elsif database
107
+ if database.is_a?(Hash)
108
+ role, database = database.first
109
+ role = role.to_sym
110
+ end
111
+
112
+ config_hash = resolve_config_for_connection(database)
113
+ handler = lookup_connection_handler(role)
68
114
 
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
115
+ handler.establish_connection(config_hash)
116
+
117
+ with_handler(role, &blk)
118
+ elsif role
119
+ prevent_writes = true if role == reading_role
120
+
121
+ with_handler(role.to_sym) do
122
+ connection_handler.while_preventing_writes(prevent_writes, &blk)
123
+ end
124
+ else
125
+ raise ArgumentError, "must provide a `database` or a `role`."
73
126
  end
127
+ end
74
128
 
75
- private
76
- def config
77
- @raw_config.dup.tap do |cfg|
78
- if url = ENV["DATABASE_URL"]
79
- cfg[@env] ||= {}
80
- cfg[@env]["url"] ||= url
81
- end
82
- end
129
+ # Returns true if role is the current connected role.
130
+ #
131
+ # ActiveRecord::Base.connected_to(role: :writing) do
132
+ # ActiveRecord::Base.connected_to?(role: :writing) #=> true
133
+ # ActiveRecord::Base.connected_to?(role: :reading) #=> false
134
+ # end
135
+ def connected_to?(role:)
136
+ current_role == role.to_sym
137
+ end
138
+
139
+ # Returns the symbol representing the current connected role.
140
+ #
141
+ # ActiveRecord::Base.connected_to(role: :writing) do
142
+ # ActiveRecord::Base.current_role #=> :writing
143
+ # end
144
+ #
145
+ # ActiveRecord::Base.connected_to(role: :reading) do
146
+ # ActiveRecord::Base.current_role #=> :reading
147
+ # end
148
+ def current_role
149
+ connection_handlers.key(connection_handler)
150
+ end
151
+
152
+ def lookup_connection_handler(handler_key) # :nodoc:
153
+ handler_key ||= ActiveRecord::Base.writing_role
154
+ connection_handlers[handler_key] ||= ActiveRecord::ConnectionAdapters::ConnectionHandler.new
155
+ end
156
+
157
+ def with_handler(handler_key, &blk) # :nodoc:
158
+ handler = lookup_connection_handler(handler_key)
159
+ swap_connection_handler(handler, &blk)
160
+ end
161
+
162
+ def resolve_config_for_connection(config_or_env) # :nodoc:
163
+ raise "Anonymous class is not allowed." unless name
164
+
165
+ config_or_env ||= DEFAULT_ENV.call.to_sym
166
+ pool_name = primary_class? ? "primary" : name
167
+ self.connection_specification_name = pool_name
168
+
169
+ resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
170
+ config_hash = resolver.resolve(config_or_env, pool_name).symbolize_keys
171
+ config_hash[:name] = pool_name
172
+
173
+ config_hash
174
+ end
175
+
176
+ # Clears the query cache for all connections associated with the current thread.
177
+ def clear_query_caches_for_current_thread
178
+ ActiveRecord::Base.connection_handlers.each_value do |handler|
179
+ handler.connection_pool_list.each do |pool|
180
+ pool.connection.clear_query_cache if pool.active_connection?
83
181
  end
182
+ end
84
183
  end
85
184
 
86
185
  # Returns the connection currently associated with the class. This can
@@ -100,6 +199,10 @@ module ActiveRecord
100
199
  @connection_specification_name
101
200
  end
102
201
 
202
+ def primary_class? # :nodoc:
203
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
204
+ end
205
+
103
206
  # Returns the configuration of the associated connection as a hash:
104
207
  #
105
208
  # ActiveRecord::Base.connection_config
@@ -141,5 +244,15 @@ module ActiveRecord
141
244
 
142
245
  delegate :clear_active_connections!, :clear_reloadable_connections!,
143
246
  :clear_all_connections!, :flush_idle_connections!, to: :connection_handler
247
+
248
+ private
249
+ def swap_connection_handler(handler, &blk) # :nodoc:
250
+ old_handler, ActiveRecord::Base.connection_handler = ActiveRecord::Base.connection_handler, handler
251
+ return_value = yield
252
+ return_value.load if return_value.is_a? ActiveRecord::Relation
253
+ return_value
254
+ ensure
255
+ ActiveRecord::Base.connection_handler = old_handler
256
+ end
144
257
  end
145
258
  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,6 +122,12 @@ 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
  ##
129
132
  # :singleton-method:
130
133
  # Application configurable boolean that instructs the YAML Coder to use
@@ -133,27 +136,24 @@ module ActiveRecord
133
136
 
134
137
  # Application configurable array that provides additional permitted classes
135
138
  # to Psych safe_load in the YAML Coder
136
- mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: []
139
+ mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: [Symbol]
137
140
 
138
141
  class_attribute :default_connection_handler, instance_writer: false
139
142
 
143
+ self.filter_attributes = []
144
+
140
145
  def self.connection_handler
141
- ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
146
+ Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
142
147
  end
143
148
 
144
149
  def self.connection_handler=(handler)
145
- ActiveRecord::RuntimeRegistry.connection_handler = handler
150
+ Thread.current.thread_variable_set("ar_connection_handler", handler)
146
151
  end
147
152
 
148
153
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
149
154
  end
150
155
 
151
- module ClassMethods # :nodoc:
152
- def allocate
153
- define_attribute_methods
154
- super
155
- end
156
-
156
+ module ClassMethods
157
157
  def initialize_find_by_cache # :nodoc:
158
158
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
159
159
  end
@@ -170,7 +170,7 @@ module ActiveRecord
170
170
  return super if block_given? ||
171
171
  primary_key.nil? ||
172
172
  scope_attributes? ||
173
- columns_hash.include?(inheritance_column)
173
+ columns_hash.key?(inheritance_column) && !base_class?
174
174
 
175
175
  id = ids.first
176
176
 
@@ -182,20 +182,16 @@ module ActiveRecord
182
182
  where(key => params.bind).limit(1)
183
183
  }
184
184
 
185
- record = statement.execute([id], connection).first
185
+ record = statement.execute([id], connection)&.first
186
186
  unless record
187
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
188
- name, primary_key, id)
187
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
189
188
  end
190
189
  record
191
- rescue ::RangeError
192
- raise RecordNotFound.new("Couldn't find #{name} with an out of range value for '#{primary_key}'",
193
- name, primary_key)
194
190
  end
195
191
 
196
192
  def find_by(*args) # :nodoc:
197
193
  return super if scope_attributes? || reflect_on_all_aggregations.any? ||
198
- columns_hash.key?(inheritance_column) && base_class != self
194
+ columns_hash.key?(inheritance_column) && !base_class?
199
195
 
200
196
  hash = args.first
201
197
 
@@ -215,11 +211,9 @@ module ActiveRecord
215
211
  where(wheres).limit(1)
216
212
  }
217
213
  begin
218
- statement.execute(hash.values, connection).first
214
+ statement.execute(hash.values, connection)&.first
219
215
  rescue TypeError
220
216
  raise ActiveRecord::StatementInvalid
221
- rescue ::RangeError
222
- nil
223
217
  end
224
218
  end
225
219
 
@@ -231,7 +225,7 @@ module ActiveRecord
231
225
  generated_association_methods
232
226
  end
233
227
 
234
- def generated_association_methods
228
+ def generated_association_methods # :nodoc:
235
229
  @generated_association_methods ||= begin
236
230
  mod = const_set(:GeneratedAssociationMethods, Module.new)
237
231
  private_constant :GeneratedAssociationMethods
@@ -241,8 +235,20 @@ module ActiveRecord
241
235
  end
242
236
  end
243
237
 
238
+ # Returns columns which shouldn't be exposed while calling +#inspect+.
239
+ def filter_attributes
240
+ if defined?(@filter_attributes)
241
+ @filter_attributes
242
+ else
243
+ superclass.filter_attributes
244
+ end
245
+ end
246
+
247
+ # Specifies columns which shouldn't be exposed while calling +#inspect+.
248
+ attr_writer :filter_attributes
249
+
244
250
  # Returns a string like 'Post(id:integer, title:string, body:text)'
245
- def inspect
251
+ def inspect # :nodoc:
246
252
  if self == Base
247
253
  super
248
254
  elsif abstract_class?
@@ -258,7 +264,7 @@ module ActiveRecord
258
264
  end
259
265
 
260
266
  # Overwrite the default class equality method to provide support for decorated models.
261
- def ===(object)
267
+ def ===(object) # :nodoc:
262
268
  object.is_a?(self)
263
269
  end
264
270
 
@@ -272,7 +278,8 @@ module ActiveRecord
272
278
  end
273
279
 
274
280
  def arel_attribute(name, table = arel_table) # :nodoc:
275
- name = attribute_alias(name) if attribute_alias?(name)
281
+ name = name.to_s
282
+ name = attribute_aliases[name] || name
276
283
  table[name]
277
284
  end
278
285
 
@@ -284,19 +291,21 @@ module ActiveRecord
284
291
  TypeCaster::Map.new(self)
285
292
  end
286
293
 
287
- private
294
+ def _internal? # :nodoc:
295
+ false
296
+ end
288
297
 
289
- def cached_find_by_statement(key, &block)
290
- cache = @find_by_statement_cache[connection.prepared_statements]
291
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
292
- end
298
+ def cached_find_by_statement(key, &block) # :nodoc:
299
+ cache = @find_by_statement_cache[connection.prepared_statements]
300
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
301
+ end
293
302
 
303
+ private
294
304
  def relation
295
305
  relation = Relation.create(self)
296
306
 
297
307
  if finder_needs_type_condition? && !ignore_default_scope?
298
308
  relation.where!(type_condition)
299
- relation.create_with!(inheritance_column.to_s => sti_name)
300
309
  else
301
310
  relation
302
311
  end
@@ -316,7 +325,7 @@ module ActiveRecord
316
325
  # # Instantiates a single new object
317
326
  # User.new(first_name: 'Jamie')
318
327
  def initialize(attributes = nil)
319
- self.class.define_attribute_methods
328
+ @new_record = true
320
329
  @attributes = self.class._default_attributes.deep_dup
321
330
 
322
331
  init_internals
@@ -342,15 +351,21 @@ module ActiveRecord
342
351
  # post = Post.allocate
343
352
  # post.init_with(coder)
344
353
  # post.title # => 'hello world'
345
- def init_with(coder)
354
+ def init_with(coder, &block)
346
355
  coder = LegacyYamlAdapter.convert(self.class, coder)
347
- @attributes = self.class.yaml_encoder.decode(coder)
348
-
349
- init_internals
356
+ attributes = self.class.yaml_encoder.decode(coder)
357
+ init_with_attributes(attributes, coder["new_record"], &block)
358
+ end
350
359
 
351
- @new_record = coder["new_record"]
360
+ ##
361
+ # Initialize an empty model object from +attributes+.
362
+ # +attributes+ should be an attributes object, and unlike the
363
+ # `initialize` method, no assignment calls are made per attribute.
364
+ def init_with_attributes(attributes, new_record = false) # :nodoc:
365
+ @new_record = new_record
366
+ @attributes = attributes
352
367
 
353
- self.class.define_attribute_methods
368
+ init_internals
354
369
 
355
370
  yield self if block_given?
356
371
 
@@ -389,13 +404,13 @@ module ActiveRecord
389
404
  ##
390
405
  def initialize_dup(other) # :nodoc:
391
406
  @attributes = @attributes.deep_dup
392
- @attributes.reset(self.class.primary_key)
407
+ @attributes.reset(@primary_key)
393
408
 
394
409
  _run_initialize_callbacks
395
410
 
396
411
  @new_record = true
397
412
  @destroyed = false
398
- @_start_transaction_state = {}
413
+ @_start_transaction_state = nil
399
414
  @transaction_state = nil
400
415
 
401
416
  super
@@ -456,6 +471,7 @@ module ActiveRecord
456
471
 
457
472
  # Returns +true+ if the attributes hash has been frozen.
458
473
  def frozen?
474
+ sync_with_transaction_state if @transaction_state&.finalized?
459
475
  @attributes.frozen?
460
476
  end
461
477
 
@@ -468,6 +484,14 @@ module ActiveRecord
468
484
  end
469
485
  end
470
486
 
487
+ def present? # :nodoc:
488
+ true
489
+ end
490
+
491
+ def blank? # :nodoc:
492
+ false
493
+ end
494
+
471
495
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
472
496
  # attributes will be marked as read only since they cannot be saved.
473
497
  def readonly?
@@ -490,7 +514,14 @@ module ActiveRecord
490
514
  inspection = if defined?(@attributes) && @attributes
491
515
  self.class.attribute_names.collect do |name|
492
516
  if has_attribute?(name)
493
- "#{name}: #{attribute_for_inspect(name)}"
517
+ attr = _read_attribute(name)
518
+ value = if attr.nil?
519
+ attr.inspect
520
+ else
521
+ attr = format_for_inspect(attr)
522
+ inspection_filter.filter_param(name, attr)
523
+ end
524
+ "#{name}: #{value}"
494
525
  end
495
526
  end.compact.join(", ")
496
527
  else
@@ -506,15 +537,16 @@ module ActiveRecord
506
537
  return super if custom_inspect_method_defined?
507
538
  pp.object_address_group(self) do
508
539
  if defined?(@attributes) && @attributes
509
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
510
- pp.seplist(column_names, proc { pp.text "," }) do |column_name|
511
- column_value = read_attribute(column_name)
540
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
541
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
512
542
  pp.breakable " "
513
543
  pp.group(1) do
514
- pp.text column_name
544
+ pp.text attr_name
515
545
  pp.text ":"
516
546
  pp.breakable
517
- pp.pp column_value
547
+ value = _read_attribute(attr_name)
548
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
549
+ pp.pp value
518
550
  end
519
551
  end
520
552
  else
@@ -530,7 +562,6 @@ module ActiveRecord
530
562
  end
531
563
 
532
564
  private
533
-
534
565
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
535
566
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
536
567
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -544,26 +575,36 @@ module ActiveRecord
544
575
  end
545
576
 
546
577
  def init_internals
578
+ @primary_key = self.class.primary_key
547
579
  @readonly = false
548
580
  @destroyed = false
549
581
  @marked_for_destruction = false
550
582
  @destroyed_by_association = nil
551
- @new_record = true
552
- @_start_transaction_state = {}
583
+ @_start_transaction_state = nil
553
584
  @transaction_state = nil
585
+
586
+ self.class.define_attribute_methods
554
587
  end
555
588
 
556
589
  def initialize_internals_callback
557
590
  end
558
591
 
559
- def thaw
560
- if frozen?
561
- @attributes = @attributes.dup
592
+ def custom_inspect_method_defined?
593
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
594
+ end
595
+
596
+ class InspectionMask < DelegateClass(::String)
597
+ def pretty_print(pp)
598
+ pp.text __getobj__
562
599
  end
563
600
  end
601
+ private_constant :InspectionMask
564
602
 
565
- def custom_inspect_method_defined?
566
- self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
603
+ def inspection_filter
604
+ @inspection_filter ||= begin
605
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
606
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
607
+ end
567
608
  end
568
609
  end
569
610
  end
@@ -51,7 +51,10 @@ module ActiveRecord
51
51
 
52
52
  if touch
53
53
  names = touch if touch != true
54
- updates.merge!(touch_attributes_with_time(*names))
54
+ names = Array.wrap(names)
55
+ options = names.extract_options!
56
+ touch_updates = touch_attributes_with_time(*names, **options)
57
+ updates.merge!(touch_updates)
55
58
  end
56
59
 
57
60
  unscoped.where(primary_key => object.id).update_all(updates)
@@ -102,27 +105,7 @@ module ActiveRecord
102
105
  # # `updated_at` = '2016-10-13T09:59:23-05:00'
103
106
  # # WHERE id IN (10, 15)
104
107
  def update_counters(id, counters)
105
- touch = counters.delete(:touch)
106
-
107
- updates = counters.map do |counter_name, value|
108
- operator = value < 0 ? "-" : "+"
109
- quoted_column = connection.quote_column_name(counter_name)
110
- "#{quoted_column} = COALESCE(#{quoted_column}, 0) #{operator} #{value.abs}"
111
- end
112
-
113
- if touch
114
- names = touch if touch != true
115
- touch_updates = touch_attributes_with_time(*names)
116
- updates << sanitize_sql_for_assignment(touch_updates) unless touch_updates.empty?
117
- end
118
-
119
- if id.is_a?(Relation) && self == id.klass
120
- relation = id
121
- else
122
- relation = unscoped.where!(primary_key => id)
123
- end
124
-
125
- relation.update_all updates.join(", ")
108
+ unscoped.where!(primary_key => id).update_counters(counters)
126
109
  end
127
110
 
128
111
  # Increment a numeric field by one, via a direct SQL update.
@@ -179,14 +162,11 @@ module ActiveRecord
179
162
  end
180
163
 
181
164
  private
182
-
183
- def _create_record(*)
165
+ def _create_record(attribute_names = self.attribute_names)
184
166
  id = super
185
167
 
186
168
  each_counter_cached_associations do |association|
187
- if send(association.reflection.name)
188
- association.increment_counters
189
- end
169
+ association.increment_counters
190
170
  end
191
171
 
192
172
  id
@@ -199,9 +179,7 @@ module ActiveRecord
199
179
  each_counter_cached_associations do |association|
200
180
  foreign_key = association.reflection.foreign_key.to_sym
201
181
  unless destroyed_by_association && destroyed_by_association.foreign_key.to_sym == foreign_key
202
- if send(association.reflection.name)
203
- association.decrement_counters
204
- end
182
+ association.decrement_counters
205
183
  end
206
184
  end
207
185
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ class DatabaseConfigurations
5
+ # ActiveRecord::Base.configurations will return either a HashConfig or
6
+ # UrlConfig respectively. It will never return a DatabaseConfig object,
7
+ # as this is the parent class for the types of database configuration objects.
8
+ class DatabaseConfig # :nodoc:
9
+ attr_reader :env_name, :spec_name
10
+
11
+ def initialize(env_name, spec_name)
12
+ @env_name = env_name
13
+ @spec_name = spec_name
14
+ end
15
+
16
+ def replica?
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def migrations_paths
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def url_config?
25
+ false
26
+ end
27
+
28
+ def to_legacy_hash
29
+ { env_name => config }
30
+ end
31
+
32
+ def for_current_env?
33
+ env_name == ActiveRecord::ConnectionHandling::DEFAULT_ENV.call
34
+ end
35
+ end
36
+ end
37
+ end