activerecord 7.1.5.1 → 8.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +369 -2484
  3. data/README.rdoc +15 -15
  4. data/examples/performance.rb +2 -2
  5. data/lib/active_record/association_relation.rb +2 -1
  6. data/lib/active_record/associations/alias_tracker.rb +31 -23
  7. data/lib/active_record/associations/association.rb +43 -12
  8. data/lib/active_record/associations/belongs_to_association.rb +21 -8
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  10. data/lib/active_record/associations/builder/association.rb +7 -6
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -0
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
  13. data/lib/active_record/associations/builder/has_many.rb +3 -4
  14. data/lib/active_record/associations/builder/has_one.rb +3 -4
  15. data/lib/active_record/associations/collection_association.rb +17 -9
  16. data/lib/active_record/associations/collection_proxy.rb +14 -1
  17. data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
  18. data/lib/active_record/associations/errors.rb +265 -0
  19. data/lib/active_record/associations/has_many_association.rb +1 -1
  20. data/lib/active_record/associations/has_many_through_association.rb +10 -3
  21. data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
  22. data/lib/active_record/associations/nested_error.rb +47 -0
  23. data/lib/active_record/associations/preloader/association.rb +4 -3
  24. data/lib/active_record/associations/preloader/branch.rb +7 -1
  25. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  26. data/lib/active_record/associations/singular_association.rb +14 -3
  27. data/lib/active_record/associations/through_association.rb +1 -1
  28. data/lib/active_record/associations.rb +92 -295
  29. data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
  30. data/lib/active_record/attribute_assignment.rb +0 -2
  31. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  32. data/lib/active_record/attribute_methods/primary_key.rb +25 -61
  33. data/lib/active_record/attribute_methods/read.rb +1 -13
  34. data/lib/active_record/attribute_methods/serialization.rb +4 -24
  35. data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -18
  36. data/lib/active_record/attribute_methods.rb +71 -75
  37. data/lib/active_record/attributes.rb +63 -49
  38. data/lib/active_record/autosave_association.rb +92 -57
  39. data/lib/active_record/base.rb +2 -3
  40. data/lib/active_record/callbacks.rb +1 -1
  41. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +48 -122
  42. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +0 -1
  43. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +1 -1
  44. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +286 -77
  45. data/lib/active_record/connection_adapters/abstract/database_statements.rb +119 -55
  46. data/lib/active_record/connection_adapters/abstract/query_cache.rb +197 -76
  47. data/lib/active_record/connection_adapters/abstract/quoting.rb +66 -92
  48. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +4 -5
  49. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +12 -3
  50. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -12
  51. data/lib/active_record/connection_adapters/abstract/transaction.rb +140 -67
  52. data/lib/active_record/connection_adapters/abstract_adapter.rb +85 -90
  53. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +71 -52
  54. data/lib/active_record/connection_adapters/mysql/database_statements.rb +9 -1
  55. data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
  56. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +2 -8
  57. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +56 -45
  58. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +92 -101
  59. data/lib/active_record/connection_adapters/mysql2_adapter.rb +13 -31
  60. data/lib/active_record/connection_adapters/pool_config.rb +14 -13
  61. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +86 -41
  62. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  63. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  64. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  66. data/lib/active_record/connection_adapters/postgresql/quoting.rb +58 -58
  67. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
  68. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +1 -11
  69. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +36 -20
  70. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +3 -2
  71. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +75 -28
  72. data/lib/active_record/connection_adapters/postgresql_adapter.rb +73 -113
  73. data/lib/active_record/connection_adapters/schema_cache.rb +124 -131
  74. data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
  75. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +81 -97
  76. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -46
  77. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
  78. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
  79. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +29 -0
  80. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +35 -3
  81. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +183 -87
  82. data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
  83. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +39 -69
  84. data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
  85. data/lib/active_record/connection_adapters.rb +65 -0
  86. data/lib/active_record/connection_handling.rb +74 -37
  87. data/lib/active_record/core.rb +132 -51
  88. data/lib/active_record/counter_cache.rb +19 -10
  89. data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
  90. data/lib/active_record/database_configurations/database_config.rb +23 -4
  91. data/lib/active_record/database_configurations/hash_config.rb +46 -34
  92. data/lib/active_record/database_configurations/url_config.rb +20 -1
  93. data/lib/active_record/database_configurations.rb +1 -1
  94. data/lib/active_record/delegated_type.rb +41 -17
  95. data/lib/active_record/dynamic_matchers.rb +2 -2
  96. data/lib/active_record/encryption/config.rb +3 -1
  97. data/lib/active_record/encryption/encryptable_record.rb +7 -7
  98. data/lib/active_record/encryption/encrypted_attribute_type.rb +33 -4
  99. data/lib/active_record/encryption/encryptor.rb +28 -6
  100. data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
  101. data/lib/active_record/encryption/key_provider.rb +1 -1
  102. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  103. data/lib/active_record/encryption/message_serializer.rb +4 -0
  104. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  105. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  106. data/lib/active_record/encryption/scheme.rb +8 -1
  107. data/lib/active_record/enum.rb +20 -16
  108. data/lib/active_record/errors.rb +54 -20
  109. data/lib/active_record/explain.rb +13 -24
  110. data/lib/active_record/fixtures.rb +37 -33
  111. data/lib/active_record/future_result.rb +21 -13
  112. data/lib/active_record/gem_version.rb +4 -4
  113. data/lib/active_record/inheritance.rb +4 -2
  114. data/lib/active_record/insert_all.rb +19 -16
  115. data/lib/active_record/integration.rb +4 -1
  116. data/lib/active_record/internal_metadata.rb +48 -34
  117. data/lib/active_record/locking/optimistic.rb +8 -7
  118. data/lib/active_record/log_subscriber.rb +5 -32
  119. data/lib/active_record/message_pack.rb +1 -1
  120. data/lib/active_record/migration/command_recorder.rb +33 -14
  121. data/lib/active_record/migration/compatibility.rb +8 -3
  122. data/lib/active_record/migration/default_strategy.rb +4 -5
  123. data/lib/active_record/migration/pending_migration_connection.rb +2 -2
  124. data/lib/active_record/migration.rb +104 -98
  125. data/lib/active_record/model_schema.rb +32 -70
  126. data/lib/active_record/nested_attributes.rb +15 -9
  127. data/lib/active_record/normalization.rb +3 -7
  128. data/lib/active_record/persistence.rb +127 -451
  129. data/lib/active_record/query_cache.rb +19 -8
  130. data/lib/active_record/query_logs.rb +104 -37
  131. data/lib/active_record/query_logs_formatter.rb +17 -28
  132. data/lib/active_record/querying.rb +24 -12
  133. data/lib/active_record/railtie.rb +26 -68
  134. data/lib/active_record/railties/controller_runtime.rb +13 -4
  135. data/lib/active_record/railties/databases.rake +43 -61
  136. data/lib/active_record/reflection.rb +112 -53
  137. data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
  138. data/lib/active_record/relation/batches.rb +138 -72
  139. data/lib/active_record/relation/calculations.rb +122 -82
  140. data/lib/active_record/relation/delegation.rb +30 -22
  141. data/lib/active_record/relation/finder_methods.rb +32 -18
  142. data/lib/active_record/relation/merger.rb +12 -14
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +10 -2
  145. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -1
  146. data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
  147. data/lib/active_record/relation/predicate_builder.rb +16 -3
  148. data/lib/active_record/relation/query_attribute.rb +1 -1
  149. data/lib/active_record/relation/query_methods.rb +317 -101
  150. data/lib/active_record/relation/spawn_methods.rb +3 -19
  151. data/lib/active_record/relation/where_clause.rb +7 -19
  152. data/lib/active_record/relation.rb +561 -119
  153. data/lib/active_record/result.rb +95 -46
  154. data/lib/active_record/runtime_registry.rb +39 -0
  155. data/lib/active_record/sanitization.rb +31 -25
  156. data/lib/active_record/schema.rb +8 -6
  157. data/lib/active_record/schema_dumper.rb +53 -20
  158. data/lib/active_record/schema_migration.rb +31 -14
  159. data/lib/active_record/scoping/named.rb +6 -2
  160. data/lib/active_record/signed_id.rb +24 -4
  161. data/lib/active_record/statement_cache.rb +19 -19
  162. data/lib/active_record/store.rb +7 -3
  163. data/lib/active_record/table_metadata.rb +2 -13
  164. data/lib/active_record/tasks/database_tasks.rb +87 -58
  165. data/lib/active_record/tasks/mysql_database_tasks.rb +1 -3
  166. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  167. data/lib/active_record/tasks/sqlite_database_tasks.rb +4 -3
  168. data/lib/active_record/test_fixtures.rb +98 -89
  169. data/lib/active_record/testing/query_assertions.rb +121 -0
  170. data/lib/active_record/timestamp.rb +2 -2
  171. data/lib/active_record/token_for.rb +22 -12
  172. data/lib/active_record/touch_later.rb +1 -1
  173. data/lib/active_record/transaction.rb +132 -0
  174. data/lib/active_record/transactions.rb +72 -17
  175. data/lib/active_record/translation.rb +0 -2
  176. data/lib/active_record/type/serialized.rb +1 -3
  177. data/lib/active_record/type_caster/connection.rb +4 -4
  178. data/lib/active_record/validations/associated.rb +9 -3
  179. data/lib/active_record/validations/uniqueness.rb +23 -18
  180. data/lib/active_record/validations.rb +4 -1
  181. data/lib/active_record.rb +138 -57
  182. data/lib/arel/alias_predication.rb +1 -1
  183. data/lib/arel/collectors/bind.rb +4 -2
  184. data/lib/arel/collectors/composite.rb +7 -0
  185. data/lib/arel/collectors/sql_string.rb +2 -2
  186. data/lib/arel/collectors/substitute_binds.rb +3 -3
  187. data/lib/arel/nodes/binary.rb +1 -7
  188. data/lib/arel/nodes/bound_sql_literal.rb +9 -5
  189. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  190. data/lib/arel/nodes/node.rb +5 -4
  191. data/lib/arel/nodes/sql_literal.rb +8 -1
  192. data/lib/arel/nodes.rb +2 -2
  193. data/lib/arel/predications.rb +1 -1
  194. data/lib/arel/select_manager.rb +1 -1
  195. data/lib/arel/table.rb +3 -7
  196. data/lib/arel/tree_manager.rb +3 -2
  197. data/lib/arel/update_manager.rb +2 -1
  198. data/lib/arel/visitors/dot.rb +1 -0
  199. data/lib/arel/visitors/mysql.rb +9 -4
  200. data/lib/arel/visitors/postgresql.rb +1 -12
  201. data/lib/arel/visitors/sqlite.rb +25 -0
  202. data/lib/arel/visitors/to_sql.rb +29 -16
  203. data/lib/arel.rb +7 -3
  204. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  205. metadata +18 -16
  206. data/lib/active_record/relation/record_fetch_warning.rb +0 -49
@@ -13,6 +13,16 @@ module ActiveRecord
13
13
  class_attribute :signed_id_verifier_secret, instance_writer: false
14
14
  end
15
15
 
16
+ module RelationMethods # :nodoc:
17
+ def find_signed(...)
18
+ scoping { model.find_signed(...) }
19
+ end
20
+
21
+ def find_signed!(...)
22
+ scoping { model.find_signed!(...) }
23
+ end
24
+ end
25
+
16
26
  module ClassMethods
17
27
  # Lets you find a record based on a signed id that's safe to put into the world without risk of tampering.
18
28
  # This is particularly useful for things like password reset or email verification, where you want
@@ -66,8 +76,9 @@ module ActiveRecord
66
76
  end
67
77
 
68
78
  # The verifier instance that all signed ids are generated and verified from. By default, it'll be initialized
69
- # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
70
- # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
79
+ # with the class-level +signed_id_verifier_secret+, which within Rails comes from
80
+ # {Rails.application.key_generator}[rdoc-ref:Rails::Application#key_generator].
81
+ # By default, it's SHA256 for the digest and JSON for the serialization.
71
82
  def signed_id_verifier
72
83
  @signed_id_verifier ||= begin
73
84
  secret = signed_id_verifier_secret
@@ -76,14 +87,14 @@ module ActiveRecord
76
87
  if secret.nil?
77
88
  raise ArgumentError, "You must set ActiveRecord::Base.signed_id_verifier_secret to use signed ids"
78
89
  else
79
- ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON
90
+ ActiveSupport::MessageVerifier.new secret, digest: "SHA256", serializer: JSON, url_safe: true
80
91
  end
81
92
  end
82
93
  end
83
94
 
84
95
  # Allows you to pass in a custom verifier used for the signed ids. This also allows you to use different
85
96
  # verifiers for different classes. This is also helpful if you need to rotate keys, as you can prepare
86
- # your custom verifier for that in advance. See +ActiveSupport::MessageVerifier+ for details.
97
+ # your custom verifier for that in advance. See ActiveSupport::MessageVerifier for details.
87
98
  def signed_id_verifier=(verifier)
88
99
  @signed_id_verifier = verifier
89
100
  end
@@ -96,7 +107,16 @@ module ActiveRecord
96
107
 
97
108
 
98
109
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
110
+ #
99
111
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
112
+ # However, as with any message signed with a +ActiveSupport::MessageVerifier+,
113
+ # {the signed id is not encrypted}[link:classes/ActiveSupport/MessageVerifier.html#class-ActiveSupport::MessageVerifier-label-Signing+is+not+encryption].
114
+ # It's just encoded and protected against tampering.
115
+ #
116
+ # This means that the ID can be decoded by anyone; however, if tampered with (so to point to a different ID),
117
+ # the cryptographic signature will no longer match, and the signed id will be considered invalid and return nil
118
+ # when passed to +find_signed+ (or raise with +find_signed!+).
119
+ #
100
120
  # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
121
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
122
  # record. If a purpose is set, this too must match.
@@ -4,14 +4,14 @@ module ActiveRecord
4
4
  # Statement cache is used to cache a single statement in order to avoid creating the AST again.
5
5
  # Initializing the cache is done by passing the statement in the create block:
6
6
  #
7
- # cache = StatementCache.create(Book.connection) do |params|
7
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
8
8
  # Book.where(name: "my book").where("author_id > 3")
9
9
  # end
10
10
  #
11
11
  # The cached statement is executed by using the
12
12
  # {connection.execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute] method:
13
13
  #
14
- # cache.execute([], Book.connection)
14
+ # cache.execute([], ClothingItem.lease_connection)
15
15
  #
16
16
  # The relation returned by the block is cached, and for each
17
17
  # {execute}[rdoc-ref:ConnectionAdapters::DatabaseStatements#execute]
@@ -20,13 +20,13 @@ module ActiveRecord
20
20
  # If you want to cache the statement without the values you can use the +bind+ method of the
21
21
  # block parameter.
22
22
  #
23
- # cache = StatementCache.create(Book.connection) do |params|
23
+ # cache = StatementCache.create(ClothingItem.lease_connection) do |params|
24
24
  # Book.where(name: params.bind)
25
25
  # end
26
26
  #
27
27
  # And pass the bind values as the first argument of +execute+ call.
28
28
  #
29
- # cache.execute(["my book"], Book.connection)
29
+ # cache.execute(["my book"], ClothingItem.lease_connection)
30
30
  class StatementCache # :nodoc:
31
31
  class Substitute; end # :nodoc:
32
32
 
@@ -62,7 +62,7 @@ module ActiveRecord
62
62
  end
63
63
 
64
64
  class PartialQueryCollector
65
- attr_accessor :preparable
65
+ attr_accessor :preparable, :retryable
66
66
 
67
67
  def initialize
68
68
  @parts = []
@@ -74,13 +74,13 @@ module ActiveRecord
74
74
  self
75
75
  end
76
76
 
77
- def add_bind(obj)
77
+ def add_bind(obj, &)
78
78
  @binds << obj
79
79
  @parts << Substitute.new
80
80
  self
81
81
  end
82
82
 
83
- def add_binds(binds, proc_for_binds = nil)
83
+ def add_binds(binds, proc_for_binds = nil, &)
84
84
  @binds.concat proc_for_binds ? binds.map(&proc_for_binds) : binds
85
85
  binds.size.times do |i|
86
86
  @parts << ", " unless i == 0
@@ -133,23 +133,26 @@ module ActiveRecord
133
133
  relation = (callable || block).call Params.new
134
134
  query_builder, binds = connection.cacheable_query(self, relation.arel)
135
135
  bind_map = BindMap.new(binds)
136
- new(query_builder, bind_map, relation.klass)
136
+ new(query_builder, bind_map, relation.model)
137
137
  end
138
138
 
139
- def initialize(query_builder, bind_map, klass)
139
+ def initialize(query_builder, bind_map, model)
140
140
  @query_builder = query_builder
141
141
  @bind_map = bind_map
142
- @klass = klass
142
+ @model = model
143
143
  end
144
144
 
145
- def execute(params, connection, &block)
146
- bind_values = bind_map.bind params
145
+ def execute(params, connection, allow_retry: false, async: false, &block)
146
+ bind_values = @bind_map.bind params
147
+ sql = @query_builder.sql_for bind_values, connection
147
148
 
148
- sql = query_builder.sql_for bind_values, connection
149
-
150
- klass.find_by_sql(sql, bind_values, preparable: true, &block)
149
+ if async
150
+ @model.async_find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
151
+ else
152
+ @model.find_by_sql(sql, bind_values, preparable: true, allow_retry: allow_retry, &block)
153
+ end
151
154
  rescue ::RangeError
152
- []
155
+ async ? Promise.wrap([]) : []
153
156
  end
154
157
 
155
158
  def self.unsupported_value?(value)
@@ -157,8 +160,5 @@ module ActiveRecord
157
160
  when NilClass, Array, Range, Hash, Relation, Base then true
158
161
  end
159
162
  end
160
-
161
- private
162
- attr_reader :query_builder, :bind_map, :klass
163
163
  end
164
164
  end
@@ -25,8 +25,8 @@ module ActiveRecord
25
25
  # You can set custom coder to encode/decode your serialized attributes to/from different formats.
26
26
  # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+.
27
27
  #
28
- # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, or MySQL 5.7+
29
- # +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
28
+ # NOTE: If you are using structured database data types (e.g. PostgreSQL +hstore+/+json+, MySQL 5.7+
29
+ # +json+, or SQLite 3.38+ +json+) there is no need for the serialization provided by {.store}[rdoc-ref:rdoc-ref:ClassMethods#store].
30
30
  # Simply use {.store_accessor}[rdoc-ref:ClassMethods#store_accessor] instead to generate
31
31
  # the accessor methods. Be aware that these columns use a string keyed hash and do not allow access
32
32
  # using a symbol.
@@ -217,7 +217,11 @@ module ActiveRecord
217
217
  end
218
218
 
219
219
  def store_accessor_for(store_attribute)
220
- type_for_attribute(store_attribute).accessor
220
+ type_for_attribute(store_attribute).tap do |type|
221
+ unless type.respond_to?(:accessor)
222
+ raise ConfigurationError, "the column '#{store_attribute}' has not been configured as a store. Please make sure the column is declared serializable via 'ActiveRecord.store' or, if your database supports it, use a structured column type like hstore or json."
223
+ end
224
+ end.accessor
221
225
  end
222
226
 
223
227
  class HashAccessor # :nodoc:
@@ -23,16 +23,7 @@ module ActiveRecord
23
23
  end
24
24
 
25
25
  def associated_with?(table_name)
26
- if reflection = klass&._reflect_on_association(table_name)
27
- reflection
28
- elsif ActiveRecord.allow_deprecated_singular_associations_name && reflection = klass&._reflect_on_association(table_name.singularize)
29
- ActiveRecord.deprecator.warn(<<~MSG)
30
- Referring to a singular association (e.g. `#{reflection.name}`) by its plural name (e.g. `#{reflection.plural_name}`) is deprecated.
31
-
32
- To convert this deprecation warning to an error and enable more performant behavior, set config.active_record.allow_deprecated_singular_associations_name = false.
33
- MSG
34
- reflection
35
- end
26
+ klass&._reflect_on_association(table_name)
36
27
  end
37
28
 
38
29
  def associated_table(table_name)
@@ -78,9 +69,7 @@ module ActiveRecord
78
69
 
79
70
  def predicate_builder
80
71
  if klass
81
- predicate_builder = klass.predicate_builder.dup
82
- predicate_builder.instance_variable_set(:@table, self)
83
- predicate_builder
72
+ klass.predicate_builder.with(self)
84
73
  else
85
74
  PredicateBuilder.new(self)
86
75
  end
@@ -125,11 +125,11 @@ module ActiveRecord
125
125
  end
126
126
 
127
127
  def create_all
128
- each_local_configuration do |db_config|
129
- with_temporary_connection(db_config) do
130
- create(db_config)
131
- end
132
- end
128
+ db_config = migration_connection.pool.db_config
129
+
130
+ each_local_configuration { |db_config| create(db_config) }
131
+
132
+ migration_class.establish_connection(db_config)
133
133
  end
134
134
 
135
135
  def setup_initial_database_yaml # :nodoc:
@@ -178,22 +178,9 @@ module ActiveRecord
178
178
  dump_db_configs = []
179
179
 
180
180
  each_current_configuration(env) do |db_config|
181
- with_temporary_pool(db_config) do
182
- begin
183
- database_initialized = migration_connection.schema_migration.table_exists?
184
- rescue ActiveRecord::NoDatabaseError
185
- create(db_config)
186
- retry
187
- end
181
+ database_initialized = initialize_database(db_config)
188
182
 
189
- unless database_initialized
190
- if File.exist?(schema_dump_path(db_config))
191
- load_schema(db_config, ActiveRecord.schema_format, nil)
192
- end
193
-
194
- seed = true
195
- end
196
- end
183
+ seed = true if database_initialized && db_config.seeds?
197
184
  end
198
185
 
199
186
  each_current_environment(env) do |environment|
@@ -253,13 +240,34 @@ module ActiveRecord
253
240
  end
254
241
  end
255
242
 
256
- def migrate(version = nil)
243
+ def migrate_all
244
+ db_configs = ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env)
245
+ db_configs.each { |db_config| initialize_database(db_config) }
246
+
247
+ if db_configs.size == 1 && db_configs.first.primary?
248
+ ActiveRecord::Tasks::DatabaseTasks.migrate(skip_initialize: true)
249
+ else
250
+ mapped_versions = ActiveRecord::Tasks::DatabaseTasks.db_configs_with_versions
251
+
252
+ mapped_versions.sort.each do |version, db_configs|
253
+ db_configs.each do |db_config|
254
+ ActiveRecord::Tasks::DatabaseTasks.with_temporary_connection(db_config) do
255
+ ActiveRecord::Tasks::DatabaseTasks.migrate(version, skip_initialize: true)
256
+ end
257
+ end
258
+ end
259
+ end
260
+ end
261
+
262
+ def migrate(version = nil, skip_initialize: false)
257
263
  scope = ENV["SCOPE"]
258
264
  verbose_was, Migration.verbose = Migration.verbose, verbose?
259
265
 
260
266
  check_target_version
261
267
 
262
- migration_connection.migration_context.migrate(target_version) do |migration|
268
+ initialize_database(migration_connection_pool.db_config) unless skip_initialize
269
+
270
+ migration_connection_pool.migration_context.migrate(target_version) do |migration|
263
271
  if version.blank?
264
272
  scope.blank? || scope == migration.scope
265
273
  else
@@ -269,7 +277,7 @@ module ActiveRecord
269
277
  Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
270
278
  end
271
279
 
272
- migration_connection.schema_cache.clear!
280
+ migration_connection_pool.schema_cache.clear!
273
281
  ensure
274
282
  Migration.verbose = verbose_was
275
283
  end
@@ -277,9 +285,9 @@ module ActiveRecord
277
285
  def db_configs_with_versions(environment = env) # :nodoc:
278
286
  db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
279
287
 
280
- with_temporary_connection_for_each(env: environment) do |conn|
281
- db_config = conn.pool.db_config
282
- versions_to_run = conn.migration_context.pending_migration_versions
288
+ with_temporary_pool_for_each(env: environment) do |pool|
289
+ db_config = pool.db_config
290
+ versions_to_run = pool.migration_context.pending_migration_versions
283
291
  target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
284
292
 
285
293
  versions_to_run.each do |version|
@@ -292,15 +300,15 @@ module ActiveRecord
292
300
  end
293
301
 
294
302
  def migrate_status
295
- unless migration_connection.schema_migration.table_exists?
303
+ unless migration_connection_pool.schema_migration.table_exists?
296
304
  Kernel.abort "Schema migrations table does not exist yet."
297
305
  end
298
306
 
299
307
  # output
300
- puts "\ndatabase: #{migration_connection.pool.db_config.database}\n\n"
308
+ puts "\ndatabase: #{migration_connection_pool.db_config.database}\n\n"
301
309
  puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
302
310
  puts "-" * 50
303
- migration_connection.migration_context.migrations_status.each do |status, version, name|
311
+ migration_connection_pool.migration_context.migrations_status.each do |status, version, name|
304
312
  puts "#{status.center(8)} #{version.ljust(14)} #{name}"
305
313
  end
306
314
  puts
@@ -381,7 +389,7 @@ module ActiveRecord
381
389
  raise ArgumentError, "unknown format #{format.inspect}"
382
390
  end
383
391
 
384
- migration_connection.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
392
+ migration_connection_pool.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
385
393
  ensure
386
394
  Migration.verbose = verbose_was
387
395
  end
@@ -393,11 +401,12 @@ module ActiveRecord
393
401
 
394
402
  return true unless file && File.exist?(file)
395
403
 
396
- with_temporary_connection(db_config) do |connection|
397
- return false unless connection.internal_metadata.enabled?
398
- return false unless connection.internal_metadata.table_exists?
404
+ with_temporary_pool(db_config) do |pool|
405
+ internal_metadata = pool.internal_metadata
406
+ return false unless internal_metadata.enabled?
407
+ return false unless internal_metadata.table_exists?
399
408
 
400
- connection.internal_metadata[:schema_sha1] == schema_sha1(file)
409
+ internal_metadata[:schema_sha1] == schema_sha1(file)
401
410
  end
402
411
  end
403
412
 
@@ -408,7 +417,7 @@ module ActiveRecord
408
417
 
409
418
  with_temporary_pool(db_config, clobber: true) do
410
419
  if schema_up_to_date?(db_config, format, file)
411
- truncate_tables(db_config)
420
+ truncate_tables(db_config) unless ENV["SKIP_TEST_DATABASE_TRUNCATE"]
412
421
  else
413
422
  purge(db_config)
414
423
  load_schema(db_config, format, file)
@@ -430,11 +439,11 @@ module ActiveRecord
430
439
  case format
431
440
  when :ruby
432
441
  File.open(filename, "w:utf-8") do |file|
433
- ActiveRecord::SchemaDumper.dump(migration_connection, file)
442
+ ActiveRecord::SchemaDumper.dump(migration_connection_pool, file)
434
443
  end
435
444
  when :sql
436
445
  structure_dump(db_config, filename)
437
- if migration_connection.schema_migration.table_exists?
446
+ if migration_connection_pool.schema_migration.table_exists?
438
447
  File.open(filename, "a") do |f|
439
448
  f.puts migration_connection.dump_schema_information
440
449
  f.print "\n"
@@ -456,14 +465,10 @@ module ActiveRecord
456
465
  end
457
466
  end
458
467
 
459
- def cache_dump_filename(db_config_name, schema_cache_path: nil)
460
- filename = if ActiveRecord::Base.configurations.primary?(db_config_name)
461
- "schema_cache.yml"
462
- else
463
- "#{db_config_name}_schema_cache.yml"
464
- end
465
-
466
- schema_cache_path || ENV["SCHEMA_CACHE"] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, filename)
468
+ def cache_dump_filename(db_config, schema_cache_path: nil)
469
+ schema_cache_path ||
470
+ db_config.schema_cache_path ||
471
+ db_config.default_schema_cache_path(ActiveRecord::Tasks::DatabaseTasks.db_dir)
467
472
  end
468
473
 
469
474
  def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
@@ -495,29 +500,29 @@ module ActiveRecord
495
500
  # Dumps the schema cache in YAML format for the connection into the file
496
501
  #
497
502
  # ==== Examples
498
- # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
499
- def dump_schema_cache(conn, filename)
500
- conn.schema_cache.dump_to(filename)
503
+ # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.lease_connection, "tmp/schema_dump.yaml")
504
+ def dump_schema_cache(conn_or_pool, filename)
505
+ conn_or_pool.schema_cache.dump_to(filename)
501
506
  end
502
507
 
503
508
  def clear_schema_cache(filename)
504
509
  FileUtils.rm_f filename, verbose: false
505
510
  end
506
511
 
507
- def with_temporary_connection_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
512
+ def with_temporary_pool_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
508
513
  if name
509
514
  db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
510
- with_temporary_connection(db_config, clobber: clobber, &block)
515
+ with_temporary_pool(db_config, clobber: clobber, &block)
511
516
  else
512
517
  ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
513
- with_temporary_connection(db_config, clobber: clobber, &block)
518
+ with_temporary_pool(db_config, clobber: clobber, &block)
514
519
  end
515
520
  end
516
521
  end
517
522
 
518
- def with_temporary_connection(db_config, clobber: false) # :nodoc:
523
+ def with_temporary_connection(db_config, clobber: false, &block) # :nodoc:
519
524
  with_temporary_pool(db_config, clobber: clobber) do |pool|
520
- yield pool.connection
525
+ pool.with_connection(&block)
521
526
  end
522
527
  end
523
528
 
@@ -526,7 +531,11 @@ module ActiveRecord
526
531
  end
527
532
 
528
533
  def migration_connection # :nodoc:
529
- migration_class.connection
534
+ migration_class.lease_connection
535
+ end
536
+
537
+ def migration_connection_pool # :nodoc:
538
+ migration_class.connection_pool
530
539
  end
531
540
 
532
541
  private
@@ -625,11 +634,11 @@ module ActiveRecord
625
634
 
626
635
  def check_current_protected_environment!(db_config)
627
636
  with_temporary_pool(db_config) do |pool|
628
- connection = pool.connection
629
- current = connection.migration_context.current_environment
630
- stored = connection.migration_context.last_stored_environment
637
+ migration_context = pool.migration_context
638
+ current = migration_context.current_environment
639
+ stored = migration_context.last_stored_environment
631
640
 
632
- if connection.migration_context.protected_environment?
641
+ if migration_context.protected_environment?
633
642
  raise ActiveRecord::ProtectedEnvironmentError.new(stored)
634
643
  end
635
644
 
@@ -639,6 +648,26 @@ module ActiveRecord
639
648
  rescue ActiveRecord::NoDatabaseError
640
649
  end
641
650
  end
651
+
652
+ def initialize_database(db_config)
653
+ with_temporary_pool(db_config) do
654
+ begin
655
+ database_already_initialized = migration_connection_pool.schema_migration.table_exists?
656
+ rescue ActiveRecord::NoDatabaseError
657
+ create(db_config)
658
+ retry
659
+ end
660
+
661
+ unless database_already_initialized
662
+ schema_dump_path = schema_dump_path(db_config)
663
+ if schema_dump_path && File.exist?(schema_dump_path)
664
+ load_schema(db_config, ActiveRecord.schema_format, nil)
665
+ end
666
+ end
667
+
668
+ !database_already_initialized
669
+ end
670
+ end
642
671
  end
643
672
  end
644
673
  end
@@ -3,8 +3,6 @@
3
3
  module ActiveRecord
4
4
  module Tasks # :nodoc:
5
5
  class MySQLDatabaseTasks # :nodoc:
6
- ER_DB_CREATE_EXISTS = 1007
7
-
8
6
  def self.using_database_configurations?
9
7
  true
10
8
  end
@@ -71,7 +69,7 @@ module ActiveRecord
71
69
  attr_reader :db_config, :configuration_hash
72
70
 
73
71
  def connection
74
- ActiveRecord::Base.connection
72
+ ActiveRecord::Base.lease_connection
75
73
  end
76
74
 
77
75
  def establish_connection(config = db_config)
@@ -88,7 +88,7 @@ module ActiveRecord
88
88
  attr_reader :db_config, :configuration_hash
89
89
 
90
90
  def connection
91
- ActiveRecord::Base.connection
91
+ ActiveRecord::Base.lease_connection
92
92
  end
93
93
 
94
94
  def establish_connection(config = db_config)
@@ -50,9 +50,9 @@ module ActiveRecord
50
50
  if ignore_tables.any?
51
51
  ignore_tables = connection.data_sources.select { |table| ignore_tables.any? { |pattern| pattern === table } }
52
52
  condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
53
- args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
53
+ args << "SELECT sql || ';' FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
54
54
  else
55
- args << ".schema"
55
+ args << ".schema --nosys"
56
56
  end
57
57
  run_cmd("sqlite3", args, filename)
58
58
  end
@@ -66,11 +66,12 @@ module ActiveRecord
66
66
  attr_reader :db_config, :root
67
67
 
68
68
  def connection
69
- ActiveRecord::Base.connection
69
+ ActiveRecord::Base.lease_connection
70
70
  end
71
71
 
72
72
  def establish_connection(config = db_config)
73
73
  ActiveRecord::Base.establish_connection(config)
74
+ connection.connect!
74
75
  end
75
76
 
76
77
  def run_cmd(cmd, args, out)