activerecord 5.2.6 → 6.0.5

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 +928 -559
  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 +0 -1
  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 +107 -66
  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 +3 -3
  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 -43
  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 +116 -29
  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,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,19 +281,21 @@ module ActiveRecord
274
281
  TypeCaster::Map.new(self)
275
282
  end
276
283
 
277
- private
284
+ def _internal? # :nodoc:
285
+ false
286
+ end
278
287
 
279
- def cached_find_by_statement(key, &block)
280
- cache = @find_by_statement_cache[connection.prepared_statements]
281
- cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
282
- end
288
+ def cached_find_by_statement(key, &block) # :nodoc:
289
+ cache = @find_by_statement_cache[connection.prepared_statements]
290
+ cache.compute_if_absent(key) { StatementCache.create(connection, &block) }
291
+ end
283
292
 
293
+ private
284
294
  def relation
285
295
  relation = Relation.create(self)
286
296
 
287
297
  if finder_needs_type_condition? && !ignore_default_scope?
288
298
  relation.where!(type_condition)
289
- relation.create_with!(inheritance_column.to_s => sti_name)
290
299
  else
291
300
  relation
292
301
  end
@@ -306,7 +315,7 @@ module ActiveRecord
306
315
  # # Instantiates a single new object
307
316
  # User.new(first_name: 'Jamie')
308
317
  def initialize(attributes = nil)
309
- self.class.define_attribute_methods
318
+ @new_record = true
310
319
  @attributes = self.class._default_attributes.deep_dup
311
320
 
312
321
  init_internals
@@ -332,15 +341,21 @@ module ActiveRecord
332
341
  # post = Post.allocate
333
342
  # post.init_with(coder)
334
343
  # post.title # => 'hello world'
335
- def init_with(coder)
344
+ def init_with(coder, &block)
336
345
  coder = LegacyYamlAdapter.convert(self.class, coder)
337
- @attributes = self.class.yaml_encoder.decode(coder)
338
-
339
- init_internals
346
+ attributes = self.class.yaml_encoder.decode(coder)
347
+ init_with_attributes(attributes, coder["new_record"], &block)
348
+ end
340
349
 
341
- @new_record = coder["new_record"]
350
+ ##
351
+ # Initialize an empty model object from +attributes+.
352
+ # +attributes+ should be an attributes object, and unlike the
353
+ # `initialize` method, no assignment calls are made per attribute.
354
+ def init_with_attributes(attributes, new_record = false) # :nodoc:
355
+ @new_record = new_record
356
+ @attributes = attributes
342
357
 
343
- self.class.define_attribute_methods
358
+ init_internals
344
359
 
345
360
  yield self if block_given?
346
361
 
@@ -379,13 +394,13 @@ module ActiveRecord
379
394
  ##
380
395
  def initialize_dup(other) # :nodoc:
381
396
  @attributes = @attributes.deep_dup
382
- @attributes.reset(self.class.primary_key)
397
+ @attributes.reset(@primary_key)
383
398
 
384
399
  _run_initialize_callbacks
385
400
 
386
401
  @new_record = true
387
402
  @destroyed = false
388
- @_start_transaction_state = {}
403
+ @_start_transaction_state = nil
389
404
  @transaction_state = nil
390
405
 
391
406
  super
@@ -446,6 +461,7 @@ module ActiveRecord
446
461
 
447
462
  # Returns +true+ if the attributes hash has been frozen.
448
463
  def frozen?
464
+ sync_with_transaction_state if @transaction_state&.finalized?
449
465
  @attributes.frozen?
450
466
  end
451
467
 
@@ -458,6 +474,14 @@ module ActiveRecord
458
474
  end
459
475
  end
460
476
 
477
+ def present? # :nodoc:
478
+ true
479
+ end
480
+
481
+ def blank? # :nodoc:
482
+ false
483
+ end
484
+
461
485
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
462
486
  # attributes will be marked as read only since they cannot be saved.
463
487
  def readonly?
@@ -480,7 +504,14 @@ module ActiveRecord
480
504
  inspection = if defined?(@attributes) && @attributes
481
505
  self.class.attribute_names.collect do |name|
482
506
  if has_attribute?(name)
483
- "#{name}: #{attribute_for_inspect(name)}"
507
+ attr = _read_attribute(name)
508
+ value = if attr.nil?
509
+ attr.inspect
510
+ else
511
+ attr = format_for_inspect(attr)
512
+ inspection_filter.filter_param(name, attr)
513
+ end
514
+ "#{name}: #{value}"
484
515
  end
485
516
  end.compact.join(", ")
486
517
  else
@@ -496,15 +527,16 @@ module ActiveRecord
496
527
  return super if custom_inspect_method_defined?
497
528
  pp.object_address_group(self) do
498
529
  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)
530
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
531
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
502
532
  pp.breakable " "
503
533
  pp.group(1) do
504
- pp.text column_name
534
+ pp.text attr_name
505
535
  pp.text ":"
506
536
  pp.breakable
507
- pp.pp column_value
537
+ value = _read_attribute(attr_name)
538
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
539
+ pp.pp value
508
540
  end
509
541
  end
510
542
  else
@@ -520,7 +552,6 @@ module ActiveRecord
520
552
  end
521
553
 
522
554
  private
523
-
524
555
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
525
556
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
526
557
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -534,26 +565,36 @@ module ActiveRecord
534
565
  end
535
566
 
536
567
  def init_internals
568
+ @primary_key = self.class.primary_key
537
569
  @readonly = false
538
570
  @destroyed = false
539
571
  @marked_for_destruction = false
540
572
  @destroyed_by_association = nil
541
- @new_record = true
542
- @_start_transaction_state = {}
573
+ @_start_transaction_state = nil
543
574
  @transaction_state = nil
575
+
576
+ self.class.define_attribute_methods
544
577
  end
545
578
 
546
579
  def initialize_internals_callback
547
580
  end
548
581
 
549
- def thaw
550
- if frozen?
551
- @attributes = @attributes.dup
582
+ def custom_inspect_method_defined?
583
+ self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
584
+ end
585
+
586
+ class InspectionMask < DelegateClass(::String)
587
+ def pretty_print(pp)
588
+ pp.text __getobj__
552
589
  end
553
590
  end
591
+ private_constant :InspectionMask
554
592
 
555
- def custom_inspect_method_defined?
556
- self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
593
+ def inspection_filter
594
+ @inspection_filter ||= begin
595
+ mask = InspectionMask.new(ActiveSupport::ParameterFilter::FILTERED)
596
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
597
+ end
557
598
  end
558
599
  end
559
600
  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