activerecord 5.2.7 → 6.0.6.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (294) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +987 -584
  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 +23 -2
  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 +77 -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 +117 -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 +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 -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 +234 -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,38 @@ 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
+
131
+ ##
132
+ # :singleton-method:
133
+ # Application configurable boolean that instructs the YAML Coder to use
134
+ # an unsafe load if set to true.
135
+ mattr_accessor :use_yaml_unsafe_load, instance_writer: false, default: false
136
+
137
+ # Application configurable array that provides additional permitted classes
138
+ # to Psych safe_load in the YAML Coder
139
+ mattr_accessor :yaml_column_permitted_classes, instance_writer: false, default: [Symbol]
140
+
128
141
  class_attribute :default_connection_handler, instance_writer: false
129
142
 
143
+ self.filter_attributes = []
144
+
130
145
  def self.connection_handler
131
- ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
146
+ Thread.current.thread_variable_get("ar_connection_handler") || default_connection_handler
132
147
  end
133
148
 
134
149
  def self.connection_handler=(handler)
135
- ActiveRecord::RuntimeRegistry.connection_handler = handler
150
+ Thread.current.thread_variable_set("ar_connection_handler", handler)
136
151
  end
137
152
 
138
153
  self.default_connection_handler = ConnectionAdapters::ConnectionHandler.new
139
154
  end
140
155
 
141
- module ClassMethods # :nodoc:
142
- def allocate
143
- define_attribute_methods
144
- super
145
- end
146
-
156
+ module ClassMethods
147
157
  def initialize_find_by_cache # :nodoc:
148
158
  @find_by_statement_cache = { true => Concurrent::Map.new, false => Concurrent::Map.new }
149
159
  end
@@ -160,7 +170,7 @@ module ActiveRecord
160
170
  return super if block_given? ||
161
171
  primary_key.nil? ||
162
172
  scope_attributes? ||
163
- columns_hash.include?(inheritance_column)
173
+ columns_hash.key?(inheritance_column) && !base_class?
164
174
 
165
175
  id = ids.first
166
176
 
@@ -172,20 +182,16 @@ module ActiveRecord
172
182
  where(key => params.bind).limit(1)
173
183
  }
174
184
 
175
- record = statement.execute([id], connection).first
185
+ record = statement.execute([id], connection)&.first
176
186
  unless record
177
- raise RecordNotFound.new("Couldn't find #{name} with '#{primary_key}'=#{id}",
178
- name, primary_key, id)
187
+ raise RecordNotFound.new("Couldn't find #{name} with '#{key}'=#{id}", name, key, id)
179
188
  end
180
189
  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
190
  end
185
191
 
186
192
  def find_by(*args) # :nodoc:
187
193
  return super if scope_attributes? || reflect_on_all_aggregations.any? ||
188
- columns_hash.key?(inheritance_column) && base_class != self
194
+ columns_hash.key?(inheritance_column) && !base_class?
189
195
 
190
196
  hash = args.first
191
197
 
@@ -205,11 +211,9 @@ module ActiveRecord
205
211
  where(wheres).limit(1)
206
212
  }
207
213
  begin
208
- statement.execute(hash.values, connection).first
214
+ statement.execute(hash.values, connection)&.first
209
215
  rescue TypeError
210
216
  raise ActiveRecord::StatementInvalid
211
- rescue ::RangeError
212
- nil
213
217
  end
214
218
  end
215
219
 
@@ -221,7 +225,7 @@ module ActiveRecord
221
225
  generated_association_methods
222
226
  end
223
227
 
224
- def generated_association_methods
228
+ def generated_association_methods # :nodoc:
225
229
  @generated_association_methods ||= begin
226
230
  mod = const_set(:GeneratedAssociationMethods, Module.new)
227
231
  private_constant :GeneratedAssociationMethods
@@ -231,8 +235,20 @@ module ActiveRecord
231
235
  end
232
236
  end
233
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
+
234
250
  # Returns a string like 'Post(id:integer, title:string, body:text)'
235
- def inspect
251
+ def inspect # :nodoc:
236
252
  if self == Base
237
253
  super
238
254
  elsif abstract_class?
@@ -248,7 +264,7 @@ module ActiveRecord
248
264
  end
249
265
 
250
266
  # Overwrite the default class equality method to provide support for decorated models.
251
- def ===(object)
267
+ def ===(object) # :nodoc:
252
268
  object.is_a?(self)
253
269
  end
254
270
 
@@ -262,7 +278,8 @@ module ActiveRecord
262
278
  end
263
279
 
264
280
  def arel_attribute(name, table = arel_table) # :nodoc:
265
- name = attribute_alias(name) if attribute_alias?(name)
281
+ name = name.to_s
282
+ name = attribute_aliases[name] || name
266
283
  table[name]
267
284
  end
268
285
 
@@ -274,19 +291,21 @@ module ActiveRecord
274
291
  TypeCaster::Map.new(self)
275
292
  end
276
293
 
277
- private
294
+ def _internal? # :nodoc:
295
+ false
296
+ end
278
297
 
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
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
283
302
 
303
+ private
284
304
  def relation
285
305
  relation = Relation.create(self)
286
306
 
287
307
  if finder_needs_type_condition? && !ignore_default_scope?
288
308
  relation.where!(type_condition)
289
- relation.create_with!(inheritance_column.to_s => sti_name)
290
309
  else
291
310
  relation
292
311
  end
@@ -306,7 +325,7 @@ module ActiveRecord
306
325
  # # Instantiates a single new object
307
326
  # User.new(first_name: 'Jamie')
308
327
  def initialize(attributes = nil)
309
- self.class.define_attribute_methods
328
+ @new_record = true
310
329
  @attributes = self.class._default_attributes.deep_dup
311
330
 
312
331
  init_internals
@@ -332,15 +351,21 @@ module ActiveRecord
332
351
  # post = Post.allocate
333
352
  # post.init_with(coder)
334
353
  # post.title # => 'hello world'
335
- def init_with(coder)
354
+ def init_with(coder, &block)
336
355
  coder = LegacyYamlAdapter.convert(self.class, coder)
337
- @attributes = self.class.yaml_encoder.decode(coder)
338
-
339
- init_internals
356
+ attributes = self.class.yaml_encoder.decode(coder)
357
+ init_with_attributes(attributes, coder["new_record"], &block)
358
+ end
340
359
 
341
- @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
342
367
 
343
- self.class.define_attribute_methods
368
+ init_internals
344
369
 
345
370
  yield self if block_given?
346
371
 
@@ -379,13 +404,13 @@ module ActiveRecord
379
404
  ##
380
405
  def initialize_dup(other) # :nodoc:
381
406
  @attributes = @attributes.deep_dup
382
- @attributes.reset(self.class.primary_key)
407
+ @attributes.reset(@primary_key)
383
408
 
384
409
  _run_initialize_callbacks
385
410
 
386
411
  @new_record = true
387
412
  @destroyed = false
388
- @_start_transaction_state = {}
413
+ @_start_transaction_state = nil
389
414
  @transaction_state = nil
390
415
 
391
416
  super
@@ -446,6 +471,7 @@ module ActiveRecord
446
471
 
447
472
  # Returns +true+ if the attributes hash has been frozen.
448
473
  def frozen?
474
+ sync_with_transaction_state if @transaction_state&.finalized?
449
475
  @attributes.frozen?
450
476
  end
451
477
 
@@ -458,6 +484,14 @@ module ActiveRecord
458
484
  end
459
485
  end
460
486
 
487
+ def present? # :nodoc:
488
+ true
489
+ end
490
+
491
+ def blank? # :nodoc:
492
+ false
493
+ end
494
+
461
495
  # Returns +true+ if the record is read only. Records loaded through joins with piggy-back
462
496
  # attributes will be marked as read only since they cannot be saved.
463
497
  def readonly?
@@ -480,7 +514,14 @@ module ActiveRecord
480
514
  inspection = if defined?(@attributes) && @attributes
481
515
  self.class.attribute_names.collect do |name|
482
516
  if has_attribute?(name)
483
- "#{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}"
484
525
  end
485
526
  end.compact.join(", ")
486
527
  else
@@ -496,15 +537,16 @@ module ActiveRecord
496
537
  return super if custom_inspect_method_defined?
497
538
  pp.object_address_group(self) do
498
539
  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)
540
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
541
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
502
542
  pp.breakable " "
503
543
  pp.group(1) do
504
- pp.text column_name
544
+ pp.text attr_name
505
545
  pp.text ":"
506
546
  pp.breakable
507
- 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
508
550
  end
509
551
  end
510
552
  else
@@ -520,7 +562,6 @@ module ActiveRecord
520
562
  end
521
563
 
522
564
  private
523
-
524
565
  # +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
525
566
  # the array, and then rescues from the possible +NoMethodError+. If those elements are
526
567
  # +ActiveRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
@@ -534,26 +575,36 @@ module ActiveRecord
534
575
  end
535
576
 
536
577
  def init_internals
578
+ @primary_key = self.class.primary_key
537
579
  @readonly = false
538
580
  @destroyed = false
539
581
  @marked_for_destruction = false
540
582
  @destroyed_by_association = nil
541
- @new_record = true
542
- @_start_transaction_state = {}
583
+ @_start_transaction_state = nil
543
584
  @transaction_state = nil
585
+
586
+ self.class.define_attribute_methods
544
587
  end
545
588
 
546
589
  def initialize_internals_callback
547
590
  end
548
591
 
549
- def thaw
550
- if frozen?
551
- @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__
552
599
  end
553
600
  end
601
+ private_constant :InspectionMask
554
602
 
555
- def custom_inspect_method_defined?
556
- 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
557
608
  end
558
609
  end
559
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