activerecord 7.0.0 → 7.1.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 (251) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1701 -1039
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +18 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +362 -236
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +52 -34
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  77. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  80. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  81. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  82. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  83. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
  84. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  85. data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
  86. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  87. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  88. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
  89. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  90. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  91. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  92. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  93. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  94. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  95. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  96. data/lib/active_record/connection_adapters.rb +3 -1
  97. data/lib/active_record/connection_handling.rb +73 -96
  98. data/lib/active_record/core.rb +142 -153
  99. data/lib/active_record/counter_cache.rb +46 -25
  100. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  101. data/lib/active_record/database_configurations/database_config.rb +9 -3
  102. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  103. data/lib/active_record/database_configurations/url_config.rb +17 -11
  104. data/lib/active_record/database_configurations.rb +87 -34
  105. data/lib/active_record/delegated_type.rb +9 -4
  106. data/lib/active_record/deprecator.rb +7 -0
  107. data/lib/active_record/destroy_association_async_job.rb +2 -0
  108. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  109. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  110. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  111. data/lib/active_record/encryption/config.rb +25 -1
  112. data/lib/active_record/encryption/configurable.rb +13 -14
  113. data/lib/active_record/encryption/context.rb +10 -3
  114. data/lib/active_record/encryption/contexts.rb +8 -4
  115. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  116. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  117. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  118. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  119. data/lib/active_record/encryption/encryptor.rb +7 -7
  120. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  121. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  122. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  123. data/lib/active_record/encryption/key_generator.rb +12 -1
  124. data/lib/active_record/encryption/message.rb +1 -1
  125. data/lib/active_record/encryption/message_serializer.rb +2 -0
  126. data/lib/active_record/encryption/properties.rb +4 -4
  127. data/lib/active_record/encryption/scheme.rb +20 -23
  128. data/lib/active_record/encryption.rb +1 -0
  129. data/lib/active_record/enum.rb +113 -29
  130. data/lib/active_record/errors.rb +108 -15
  131. data/lib/active_record/explain.rb +23 -3
  132. data/lib/active_record/explain_subscriber.rb +1 -1
  133. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  134. data/lib/active_record/fixture_set/render_context.rb +2 -0
  135. data/lib/active_record/fixture_set/table_row.rb +29 -8
  136. data/lib/active_record/fixtures.rb +121 -73
  137. data/lib/active_record/future_result.rb +30 -5
  138. data/lib/active_record/gem_version.rb +3 -3
  139. data/lib/active_record/inheritance.rb +30 -16
  140. data/lib/active_record/insert_all.rb +57 -10
  141. data/lib/active_record/integration.rb +10 -10
  142. data/lib/active_record/internal_metadata.rb +120 -30
  143. data/lib/active_record/locking/optimistic.rb +32 -18
  144. data/lib/active_record/locking/pessimistic.rb +8 -5
  145. data/lib/active_record/log_subscriber.rb +39 -17
  146. data/lib/active_record/marshalling.rb +56 -0
  147. data/lib/active_record/message_pack.rb +124 -0
  148. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  149. data/lib/active_record/middleware/database_selector.rb +18 -13
  150. data/lib/active_record/middleware/shard_selector.rb +7 -5
  151. data/lib/active_record/migration/command_recorder.rb +108 -10
  152. data/lib/active_record/migration/compatibility.rb +158 -64
  153. data/lib/active_record/migration/default_strategy.rb +23 -0
  154. data/lib/active_record/migration/execution_strategy.rb +19 -0
  155. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  156. data/lib/active_record/migration.rb +274 -117
  157. data/lib/active_record/model_schema.rb +86 -54
  158. data/lib/active_record/nested_attributes.rb +24 -6
  159. data/lib/active_record/normalization.rb +167 -0
  160. data/lib/active_record/persistence.rb +200 -47
  161. data/lib/active_record/promise.rb +84 -0
  162. data/lib/active_record/query_cache.rb +3 -21
  163. data/lib/active_record/query_logs.rb +87 -51
  164. data/lib/active_record/query_logs_formatter.rb +41 -0
  165. data/lib/active_record/querying.rb +16 -3
  166. data/lib/active_record/railtie.rb +128 -62
  167. data/lib/active_record/railties/controller_runtime.rb +12 -8
  168. data/lib/active_record/railties/databases.rake +145 -146
  169. data/lib/active_record/railties/job_runtime.rb +23 -0
  170. data/lib/active_record/readonly_attributes.rb +32 -5
  171. data/lib/active_record/reflection.rb +189 -45
  172. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  173. data/lib/active_record/relation/batches.rb +190 -61
  174. data/lib/active_record/relation/calculations.rb +208 -83
  175. data/lib/active_record/relation/delegation.rb +23 -9
  176. data/lib/active_record/relation/finder_methods.rb +77 -16
  177. data/lib/active_record/relation/merger.rb +2 -0
  178. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  180. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  181. data/lib/active_record/relation/predicate_builder.rb +26 -14
  182. data/lib/active_record/relation/query_attribute.rb +25 -1
  183. data/lib/active_record/relation/query_methods.rb +430 -77
  184. data/lib/active_record/relation/spawn_methods.rb +18 -1
  185. data/lib/active_record/relation.rb +98 -41
  186. data/lib/active_record/result.rb +25 -9
  187. data/lib/active_record/runtime_registry.rb +10 -1
  188. data/lib/active_record/sanitization.rb +57 -16
  189. data/lib/active_record/schema.rb +36 -22
  190. data/lib/active_record/schema_dumper.rb +65 -23
  191. data/lib/active_record/schema_migration.rb +68 -33
  192. data/lib/active_record/scoping/default.rb +20 -12
  193. data/lib/active_record/scoping/named.rb +2 -2
  194. data/lib/active_record/scoping.rb +2 -1
  195. data/lib/active_record/secure_password.rb +60 -0
  196. data/lib/active_record/secure_token.rb +21 -3
  197. data/lib/active_record/serialization.rb +5 -0
  198. data/lib/active_record/signed_id.rb +9 -7
  199. data/lib/active_record/store.rb +16 -11
  200. data/lib/active_record/suppressor.rb +3 -1
  201. data/lib/active_record/table_metadata.rb +16 -3
  202. data/lib/active_record/tasks/database_tasks.rb +138 -107
  203. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  204. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  205. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  206. data/lib/active_record/test_fixtures.rb +123 -99
  207. data/lib/active_record/timestamp.rb +27 -15
  208. data/lib/active_record/token_for.rb +113 -0
  209. data/lib/active_record/touch_later.rb +11 -6
  210. data/lib/active_record/transactions.rb +39 -13
  211. data/lib/active_record/translation.rb +1 -1
  212. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  213. data/lib/active_record/type/internal/timezone.rb +7 -2
  214. data/lib/active_record/type/serialized.rb +8 -4
  215. data/lib/active_record/type/time.rb +4 -0
  216. data/lib/active_record/validations/absence.rb +1 -1
  217. data/lib/active_record/validations/associated.rb +3 -3
  218. data/lib/active_record/validations/numericality.rb +5 -4
  219. data/lib/active_record/validations/presence.rb +5 -28
  220. data/lib/active_record/validations/uniqueness.rb +50 -5
  221. data/lib/active_record/validations.rb +8 -4
  222. data/lib/active_record/version.rb +1 -1
  223. data/lib/active_record.rb +143 -16
  224. data/lib/arel/errors.rb +10 -0
  225. data/lib/arel/factory_methods.rb +4 -0
  226. data/lib/arel/filter_predications.rb +1 -1
  227. data/lib/arel/nodes/and.rb +4 -0
  228. data/lib/arel/nodes/binary.rb +6 -1
  229. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  230. data/lib/arel/nodes/cte.rb +36 -0
  231. data/lib/arel/nodes/filter.rb +1 -1
  232. data/lib/arel/nodes/fragments.rb +35 -0
  233. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  234. data/lib/arel/nodes/leading_join.rb +8 -0
  235. data/lib/arel/nodes/node.rb +111 -2
  236. data/lib/arel/nodes/sql_literal.rb +6 -0
  237. data/lib/arel/nodes/table_alias.rb +4 -0
  238. data/lib/arel/nodes.rb +4 -0
  239. data/lib/arel/predications.rb +2 -0
  240. data/lib/arel/table.rb +9 -5
  241. data/lib/arel/visitors/mysql.rb +8 -1
  242. data/lib/arel/visitors/to_sql.rb +81 -17
  243. data/lib/arel/visitors/visitor.rb +2 -2
  244. data/lib/arel.rb +16 -2
  245. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  246. data/lib/rails/generators/active_record/migration.rb +3 -1
  247. data/lib/rails/generators/active_record/model/USAGE +113 -0
  248. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  249. metadata +51 -15
  250. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  251. data/lib/active_record/null_relation.rb +0 -63
@@ -8,8 +8,8 @@ module ActiveRecord
8
8
  included do
9
9
  ##
10
10
  # :singleton-method:
11
- # Set the secret used for the signed id verifier instance when using Active Record outside of Rails.
12
- # Within Rails, this is automatically set using the Rails application key generator.
11
+ # Set the secret used for the signed id verifier instance when using Active Record outside of \Rails.
12
+ # Within \Rails, this is automatically set using the \Rails application key generator.
13
13
  class_attribute :signed_id_verifier_secret, instance_writer: false
14
14
  end
15
15
 
@@ -47,7 +47,7 @@ module ActiveRecord
47
47
  end
48
48
  end
49
49
 
50
- # Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
50
+ # Works like find_signed, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
51
51
  # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
52
52
  # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
53
53
  # the valid signed id can't find a record.
@@ -66,7 +66,7 @@ module ActiveRecord
66
66
  end
67
67
 
68
68
  # 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
69
+ # with the class-level +signed_id_verifier_secret+, which within \Rails comes from the
70
70
  # Rails.application.key_generator. By default, it's SHA256 for the digest and JSON for the serialization.
71
71
  def signed_id_verifier
72
72
  @signed_id_verifier ||= begin
@@ -97,7 +97,7 @@ module ActiveRecord
97
97
 
98
98
  # Returns a signed id that's generated using a preconfigured +ActiveSupport::MessageVerifier+ instance.
99
99
  # This signed id is tamper proof, so it's safe to send in an email or otherwise share with the outside world.
100
- # It can further more be set to expire (the default is not to expire), and scoped down with a specific purpose.
100
+ # It can furthermore be set to expire (the default is not to expire), and scoped down with a specific purpose.
101
101
  # If the expiration date has been exceeded before +find_signed+ is called, the id won't find the designated
102
102
  # record. If a purpose is set, this too must match.
103
103
  #
@@ -109,8 +109,10 @@ module ActiveRecord
109
109
  #
110
110
  # And you then change your +find_signed+ calls to require this new purpose. Any old signed ids that were not
111
111
  # created with the purpose will no longer find the record.
112
- def signed_id(expires_in: nil, purpose: nil)
113
- self.class.signed_id_verifier.generate id, expires_in: expires_in, purpose: self.class.combine_signed_id_purposes(purpose)
112
+ def signed_id(expires_in: nil, expires_at: nil, purpose: nil)
113
+ raise ArgumentError, "Cannot get a signed_id for a new record" if new_record?
114
+
115
+ self.class.signed_id_verifier.generate id, expires_in: expires_in, expires_at: expires_at, purpose: self.class.combine_signed_id_purposes(purpose)
114
116
  end
115
117
  end
116
118
  end
@@ -3,6 +3,8 @@
3
3
  require "active_support/core_ext/hash/indifferent_access"
4
4
 
5
5
  module ActiveRecord
6
+ # = Active Record \Store
7
+ #
6
8
  # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
7
9
  # It's like a simple key/value store baked into your record when you don't care about being able to
8
10
  # query that store outside the context of a single record.
@@ -59,7 +61,7 @@ module ActiveRecord
59
61
  # u.color = 'green'
60
62
  # u.color_changed? # => true
61
63
  # u.color_was # => 'black'
62
- # u.color_change # => ['black', 'red']
64
+ # u.color_change # => ['black', 'green']
63
65
  #
64
66
  # # Add additional accessors to an existing store through store_accessor
65
67
  # class SuperUser < User
@@ -70,7 +72,7 @@ module ActiveRecord
70
72
  #
71
73
  # The stored attribute names can be retrieved using {.stored_attributes}[rdoc-ref:rdoc-ref:ClassMethods#stored_attributes].
72
74
  #
73
- # User.stored_attributes[:settings] # [:color, :homepage, :two_factor_auth, :login_retry]
75
+ # User.stored_attributes[:settings] # => [:color, :homepage, :two_factor_auth, :login_retry]
74
76
  #
75
77
  # == Overwriting default accessors
76
78
  #
@@ -102,7 +104,8 @@ module ActiveRecord
102
104
 
103
105
  module ClassMethods
104
106
  def store(store_attribute, options = {})
105
- serialize store_attribute, IndifferentCoder.new(store_attribute, options[:coder])
107
+ coder = build_column_serializer(store_attribute, options[:coder], Object, options[:yaml])
108
+ serialize store_attribute, coder: IndifferentCoder.new(store_attribute, coder)
106
109
  store_accessor(store_attribute, options[:accessors], **options.slice(:prefix, :suffix)) if options.has_key? :accessors
107
110
  end
108
111
 
@@ -160,19 +163,19 @@ module ActiveRecord
160
163
 
161
164
  define_method("saved_change_to_#{accessor_key}?") do
162
165
  return false unless saved_change_to_attribute?(store_attribute)
163
- prev_store, new_store = saved_change_to_attribute(store_attribute)
166
+ prev_store, new_store = saved_changes[store_attribute]
164
167
  prev_store&.dig(key) != new_store&.dig(key)
165
168
  end
166
169
 
167
170
  define_method("saved_change_to_#{accessor_key}") do
168
171
  return unless saved_change_to_attribute?(store_attribute)
169
- prev_store, new_store = saved_change_to_attribute(store_attribute)
172
+ prev_store, new_store = saved_changes[store_attribute]
170
173
  [prev_store&.dig(key), new_store&.dig(key)]
171
174
  end
172
175
 
173
176
  define_method("#{accessor_key}_before_last_save") do
174
177
  return unless saved_change_to_attribute?(store_attribute)
175
- prev_store, _new_store = saved_change_to_attribute(store_attribute)
178
+ prev_store, _new_store = saved_changes[store_attribute]
176
179
  prev_store&.dig(key)
177
180
  end
178
181
  end
@@ -225,10 +228,7 @@ module ActiveRecord
225
228
 
226
229
  def self.write(object, attribute, key, value)
227
230
  prepare(object, attribute)
228
- if value != read(object, attribute, key)
229
- object.public_send :"#{attribute}_will_change!"
230
- object.public_send(attribute)[key] = value
231
- end
231
+ object.public_send(attribute)[key] = value if value != read(object, attribute, key)
232
232
  end
233
233
 
234
234
  def self.prepare(object, attribute)
@@ -268,7 +268,7 @@ module ActiveRecord
268
268
  end
269
269
 
270
270
  def dump(obj)
271
- @coder.dump self.class.as_indifferent_hash(obj)
271
+ @coder.dump as_regular_hash(obj)
272
272
  end
273
273
 
274
274
  def load(yaml)
@@ -285,6 +285,11 @@ module ActiveRecord
285
285
  ActiveSupport::HashWithIndifferentAccess.new
286
286
  end
287
287
  end
288
+
289
+ private
290
+ def as_regular_hash(obj)
291
+ obj.respond_to?(:to_hash) ? obj.to_hash : {}
292
+ end
288
293
  end
289
294
  end
290
295
  end
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveRecord
4
+ # = Active Record \Suppressor
5
+ #
4
6
  # ActiveRecord::Suppressor prevents the receiver from being saved during
5
7
  # a given block.
6
8
  #
@@ -32,7 +34,7 @@ module ActiveRecord
32
34
 
33
35
  class << self
34
36
  def registry # :nodoc:
35
- ActiveSupport::IsolatedExecutionState[:active_record_suppresor_registry] ||= {}
37
+ ActiveSupport::IsolatedExecutionState[:active_record_suppressor_registry] ||= {}
36
38
  end
37
39
  end
38
40
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module ActiveRecord
4
4
  class TableMetadata # :nodoc:
5
- delegate :join_primary_key, :join_foreign_key, :join_foreign_type, to: :reflection
5
+ delegate :join_primary_key, :join_primary_type, :join_foreign_key, :join_foreign_type, to: :reflection
6
6
 
7
7
  def initialize(klass, arel_table, reflection = nil)
8
8
  @klass = klass
@@ -19,11 +19,20 @@ module ActiveRecord
19
19
  end
20
20
 
21
21
  def has_column?(column_name)
22
- klass&.columns_hash.key?(column_name)
22
+ klass&.columns_hash&.key?(column_name)
23
23
  end
24
24
 
25
25
  def associated_with?(table_name)
26
- klass&._reflect_on_association(table_name) || klass&._reflect_on_association(table_name.singularize)
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
27
36
  end
28
37
 
29
38
  def associated_table(table_name)
@@ -54,6 +63,10 @@ module ActiveRecord
54
63
  reflection&.polymorphic?
55
64
  end
56
65
 
66
+ def polymorphic_name_association
67
+ reflection&.polymorphic_name
68
+ end
69
+
57
70
  def through_association?
58
71
  reflection&.through_reflection?
59
72
  end
@@ -6,14 +6,16 @@ module ActiveRecord
6
6
  module Tasks # :nodoc:
7
7
  class DatabaseNotSupported < StandardError; end # :nodoc:
8
8
 
9
+ # = Active Record \DatabaseTasks
10
+ #
9
11
  # ActiveRecord::Tasks::DatabaseTasks is a utility class, which encapsulates
10
12
  # logic behind common tasks used to manage database and migrations.
11
13
  #
12
- # The tasks defined here are used with Rails commands provided by Active Record.
14
+ # The tasks defined here are used with \Rails commands provided by Active Record.
13
15
  #
14
16
  # In order to use DatabaseTasks, a few config values need to be set. All the needed
15
- # config values are set by Rails already, so it's necessary to do it only if you
16
- # want to change the defaults or when you want to use Active Record outside of Rails
17
+ # config values are set by \Rails already, so it's necessary to do it only if you
18
+ # want to change the defaults or when you want to use Active Record outside of \Rails
17
19
  # (in such case after configuring the database tasks, you can also use the rake tasks
18
20
  # defined in Active Record).
19
21
  #
@@ -27,7 +29,7 @@ module ActiveRecord
27
29
  # * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
28
30
  # * +root+: a path to the root of the application.
29
31
  #
30
- # Example usage of DatabaseTasks outside Rails could look as such:
32
+ # Example usage of DatabaseTasks outside \Rails could look as such:
31
33
  #
32
34
  # include ActiveRecord::Tasks
33
35
  # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
@@ -60,20 +62,12 @@ module ActiveRecord
60
62
 
61
63
  LOCAL_HOSTS = ["127.0.0.1", "localhost"]
62
64
 
63
- def check_protected_environments!
64
- unless ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
65
- current = ActiveRecord::Base.connection.migration_context.current_environment
66
- stored = ActiveRecord::Base.connection.migration_context.last_stored_environment
67
-
68
- if ActiveRecord::Base.connection.migration_context.protected_environment?
69
- raise ActiveRecord::ProtectedEnvironmentError.new(stored)
70
- end
65
+ def check_protected_environments!(environment = env)
66
+ return if ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"]
71
67
 
72
- if stored && stored != current
73
- raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
74
- end
68
+ configs_for(env_name: environment).each do |db_config|
69
+ check_current_protected_environment!(db_config)
75
70
  end
76
- rescue ActiveRecord::NoDatabaseError
77
71
  end
78
72
 
79
73
  def register_task(pattern, task)
@@ -82,6 +76,7 @@ module ActiveRecord
82
76
  end
83
77
 
84
78
  register_task(/mysql/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
79
+ register_task(/trilogy/, "ActiveRecord::Tasks::MySQLDatabaseTasks")
85
80
  register_task(/postgresql/, "ActiveRecord::Tasks::PostgreSQLDatabaseTasks")
86
81
  register_task(/sqlite/, "ActiveRecord::Tasks::SQLiteDatabaseTasks")
87
82
 
@@ -130,28 +125,20 @@ module ActiveRecord
130
125
  end
131
126
 
132
127
  def create_all
133
- old_pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(ActiveRecord::Base.connection_specification_name)
128
+ db_config = migration_connection.pool.db_config
129
+
134
130
  each_local_configuration { |db_config| create(db_config) }
135
- if old_pool
136
- ActiveRecord::Base.connection_handler.establish_connection(old_pool.db_config)
137
- end
131
+
132
+ migration_class.establish_connection(db_config)
138
133
  end
139
134
 
140
- def setup_initial_database_yaml
135
+ def setup_initial_database_yaml # :nodoc:
141
136
  return {} unless defined?(Rails)
142
137
 
143
- begin
144
- Rails.application.config.load_database_yaml
145
- rescue
146
- unless ActiveRecord.suppress_multiple_database_warning
147
- $stderr.puts "Rails couldn't infer whether you are using multiple databases from your database.yml and can't generate the tasks for the non-primary databases. If you'd like to use this feature, please simplify your ERB."
148
- end
149
-
150
- {}
151
- end
138
+ Rails.application.config.load_database_yaml
152
139
  end
153
140
 
154
- def for_each(databases)
141
+ def for_each(databases) # :nodoc:
155
142
  return {} unless defined?(Rails)
156
143
 
157
144
  database_configs = ActiveRecord::DatabaseConfigurations.new(databases).configs_for(env_name: Rails.env)
@@ -166,7 +153,7 @@ module ActiveRecord
166
153
  end
167
154
  end
168
155
 
169
- def raise_for_multi_db(environment = env, command:)
156
+ def raise_for_multi_db(environment = env, command:) # :nodoc:
170
157
  db_configs = configs_for(env_name: environment)
171
158
 
172
159
  if db_configs.count > 1
@@ -182,38 +169,35 @@ module ActiveRecord
182
169
 
183
170
  def create_current(environment = env, name = nil)
184
171
  each_current_configuration(environment, name) { |db_config| create(db_config) }
185
- ActiveRecord::Base.establish_connection(environment.to_sym)
172
+
173
+ migration_class.establish_connection(environment.to_sym)
186
174
  end
187
175
 
188
176
  def prepare_all
189
177
  seed = false
190
178
 
191
- configs_for(env_name: env).each do |db_config|
192
- ActiveRecord::Base.establish_connection(db_config)
179
+ each_current_configuration(env) do |db_config|
180
+ with_temporary_pool(db_config) do
181
+ begin
182
+ database_initialized = migration_connection.schema_migration.table_exists?
183
+ rescue ActiveRecord::NoDatabaseError
184
+ create(db_config)
185
+ retry
186
+ end
193
187
 
194
- # Skipped when no database
195
- migrate
188
+ unless database_initialized
189
+ if File.exist?(schema_dump_path(db_config))
190
+ load_schema(db_config, ActiveRecord.schema_format, nil)
191
+ end
192
+
193
+ seed = true
194
+ end
196
195
 
197
- if ActiveRecord.dump_schema_after_migration
198
- dump_schema(db_config, ActiveRecord.schema_format)
199
- end
200
- rescue ActiveRecord::NoDatabaseError
201
- create_current(db_config.env_name, db_config.name)
202
-
203
- if File.exist?(schema_dump_path(db_config))
204
- load_schema(
205
- db_config,
206
- ActiveRecord.schema_format,
207
- nil
208
- )
209
- else
210
196
  migrate
197
+ dump_schema(db_config) if ActiveRecord.dump_schema_after_migration
211
198
  end
212
-
213
- seed = true
214
199
  end
215
200
 
216
- ActiveRecord::Base.establish_connection
217
201
  load_seed if seed
218
202
  end
219
203
 
@@ -238,10 +222,9 @@ module ActiveRecord
238
222
  end
239
223
 
240
224
  def truncate_tables(db_config)
241
- ActiveRecord::Base.establish_connection(db_config)
242
-
243
- connection = ActiveRecord::Base.connection
244
- connection.truncate_tables(*connection.tables)
225
+ with_temporary_connection(db_config) do |conn|
226
+ conn.truncate_tables(*conn.tables)
227
+ end
245
228
  end
246
229
  private :truncate_tables
247
230
 
@@ -252,18 +235,22 @@ module ActiveRecord
252
235
  end
253
236
 
254
237
  def migrate(version = nil)
255
- check_target_version
256
-
257
238
  scope = ENV["SCOPE"]
258
239
  verbose_was, Migration.verbose = Migration.verbose, verbose?
259
240
 
260
- Base.connection.migration_context.migrate(target_version || version) do |migration|
261
- scope.blank? || scope == migration.scope
241
+ check_target_version
242
+
243
+ migration_connection.migration_context.migrate(target_version) do |migration|
244
+ if version.blank?
245
+ scope.blank? || scope == migration.scope
246
+ else
247
+ migration.version == version
248
+ end
262
249
  end.tap do |migrations_ran|
263
250
  Migration.write("No migrations ran. (using #{scope} scope)") if scope.present? && migrations_ran.empty?
264
251
  end
265
252
 
266
- ActiveRecord::Base.clear_cache!
253
+ migration_connection.schema_cache.clear!
267
254
  ensure
268
255
  Migration.verbose = verbose_was
269
256
  end
@@ -271,9 +258,9 @@ module ActiveRecord
271
258
  def db_configs_with_versions(db_configs) # :nodoc:
272
259
  db_configs_with_versions = Hash.new { |h, k| h[k] = [] }
273
260
 
274
- db_configs.each do |db_config|
275
- ActiveRecord::Base.establish_connection(db_config)
276
- versions_to_run = ActiveRecord::Base.connection.migration_context.pending_migration_versions
261
+ with_temporary_connection_for_each do |conn|
262
+ db_config = conn.pool.db_config
263
+ versions_to_run = conn.migration_context.pending_migration_versions
277
264
  target_version = ActiveRecord::Tasks::DatabaseTasks.target_version
278
265
 
279
266
  versions_to_run.each do |version|
@@ -286,22 +273,22 @@ module ActiveRecord
286
273
  end
287
274
 
288
275
  def migrate_status
289
- unless ActiveRecord::Base.connection.schema_migration.table_exists?
276
+ unless migration_connection.schema_migration.table_exists?
290
277
  Kernel.abort "Schema migrations table does not exist yet."
291
278
  end
292
279
 
293
280
  # output
294
- puts "\ndatabase: #{ActiveRecord::Base.connection_db_config.database}\n\n"
281
+ puts "\ndatabase: #{migration_connection.pool.db_config.database}\n\n"
295
282
  puts "#{'Status'.center(8)} #{'Migration ID'.ljust(14)} Migration Name"
296
283
  puts "-" * 50
297
- ActiveRecord::Base.connection.migration_context.migrations_status.each do |status, version, name|
284
+ migration_connection.migration_context.migrations_status.each do |status, version, name|
298
285
  puts "#{status.center(8)} #{version.ljust(14)} #{name}"
299
286
  end
300
287
  puts
301
288
  end
302
289
 
303
290
  def check_target_version
304
- if target_version && !(Migration::MigrationFilenameRegexp.match?(ENV["VERSION"]) || /\A\d+\z/.match?(ENV["VERSION"]))
291
+ if target_version && !Migration.valid_version_format?(ENV["VERSION"])
305
292
  raise "Invalid format of target version: `VERSION=#{ENV['VERSION']}`"
306
293
  end
307
294
  end
@@ -341,7 +328,8 @@ module ActiveRecord
341
328
 
342
329
  def purge_current(environment = env)
343
330
  each_current_configuration(environment) { |db_config| purge(db_config) }
344
- ActiveRecord::Base.establish_connection(environment.to_sym)
331
+
332
+ migration_class.establish_connection(environment.to_sym)
345
333
  end
346
334
 
347
335
  def structure_dump(configuration, *arguments)
@@ -360,10 +348,10 @@ module ActiveRecord
360
348
 
361
349
  def load_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
362
350
  file ||= schema_dump_path(db_config, format)
351
+ return unless file
363
352
 
364
353
  verbose_was, Migration.verbose = Migration.verbose, verbose? && ENV["VERBOSE"]
365
354
  check_schema_file(file)
366
- ActiveRecord::Base.establish_connection(db_config)
367
355
 
368
356
  case format
369
357
  when :ruby
@@ -373,9 +361,8 @@ module ActiveRecord
373
361
  else
374
362
  raise ArgumentError, "unknown format #{format.inspect}"
375
363
  end
376
- ActiveRecord::InternalMetadata.create_table
377
- ActiveRecord::InternalMetadata[:environment] = db_config.env_name
378
- ActiveRecord::InternalMetadata[:schema_sha1] = schema_sha1(file)
364
+
365
+ migration_connection.internal_metadata.create_table_and_set_flags(db_config.env_name, schema_sha1(file))
379
366
  ensure
380
367
  Migration.verbose = verbose_was
381
368
  end
@@ -385,66 +372,58 @@ module ActiveRecord
385
372
 
386
373
  file ||= schema_dump_path(db_config)
387
374
 
388
- return true unless File.exist?(file)
375
+ return true unless file && File.exist?(file)
389
376
 
390
- ActiveRecord::Base.establish_connection(db_config)
377
+ with_temporary_connection(db_config) do |connection|
378
+ return false unless connection.internal_metadata.enabled?
379
+ return false unless connection.internal_metadata.table_exists?
391
380
 
392
- return false unless ActiveRecord::InternalMetadata.enabled?
393
- return false unless ActiveRecord::InternalMetadata.table_exists?
394
-
395
- ActiveRecord::InternalMetadata[:schema_sha1] == schema_sha1(file)
381
+ connection.internal_metadata[:schema_sha1] == schema_sha1(file)
382
+ end
396
383
  end
397
384
 
398
385
  def reconstruct_from_schema(db_config, format = ActiveRecord.schema_format, file = nil) # :nodoc:
399
386
  file ||= schema_dump_path(db_config, format)
400
387
 
401
- check_schema_file(file)
402
-
403
- ActiveRecord::Base.establish_connection(db_config)
388
+ check_schema_file(file) if file
404
389
 
405
- if schema_up_to_date?(db_config, format, file)
406
- truncate_tables(db_config)
407
- else
408
- purge(db_config)
390
+ with_temporary_pool(db_config, clobber: true) do
391
+ if schema_up_to_date?(db_config, format, file)
392
+ truncate_tables(db_config)
393
+ else
394
+ purge(db_config)
395
+ load_schema(db_config, format, file)
396
+ end
397
+ rescue ActiveRecord::NoDatabaseError
398
+ create(db_config)
409
399
  load_schema(db_config, format, file)
410
400
  end
411
- rescue ActiveRecord::NoDatabaseError
412
- create(db_config)
413
- load_schema(db_config, format, file)
414
401
  end
415
402
 
416
403
  def dump_schema(db_config, format = ActiveRecord.schema_format) # :nodoc:
404
+ return unless db_config.schema_dump
405
+
417
406
  require "active_record/schema_dumper"
418
407
  filename = schema_dump_path(db_config, format)
419
- connection = ActiveRecord::Base.connection
408
+ return unless filename
420
409
 
421
410
  FileUtils.mkdir_p(db_dir)
422
411
  case format
423
412
  when :ruby
424
413
  File.open(filename, "w:utf-8") do |file|
425
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
414
+ ActiveRecord::SchemaDumper.dump(migration_connection, file)
426
415
  end
427
416
  when :sql
428
417
  structure_dump(db_config, filename)
429
- if connection.schema_migration.table_exists?
418
+ if migration_connection.schema_migration.table_exists?
430
419
  File.open(filename, "a") do |f|
431
- f.puts connection.dump_schema_information
420
+ f.puts migration_connection.dump_schema_information
432
421
  f.print "\n"
433
422
  end
434
423
  end
435
424
  end
436
425
  end
437
426
 
438
- def schema_file_type(format = ActiveRecord.schema_format)
439
- case format
440
- when :ruby
441
- "schema.rb"
442
- when :sql
443
- "structure.sql"
444
- end
445
- end
446
- deprecate :schema_file_type
447
-
448
427
  def schema_dump_path(db_config, format = ActiveRecord.schema_format)
449
428
  return ENV["SCHEMA"] if ENV["SCHEMA"]
450
429
 
@@ -470,9 +449,10 @@ module ActiveRecord
470
449
 
471
450
  def load_schema_current(format = ActiveRecord.schema_format, file = nil, environment = env)
472
451
  each_current_configuration(environment) do |db_config|
473
- load_schema(db_config, format, file)
452
+ with_temporary_connection(db_config) do
453
+ load_schema(db_config, format, file)
454
+ end
474
455
  end
475
- ActiveRecord::Base.establish_connection(environment.to_sym)
476
456
  end
477
457
 
478
458
  def check_schema_file(filename)
@@ -495,7 +475,7 @@ module ActiveRecord
495
475
 
496
476
  # Dumps the schema cache in YAML format for the connection into the file
497
477
  #
498
- # ==== Examples:
478
+ # ==== Examples
499
479
  # ActiveRecord::Tasks::DatabaseTasks.dump_schema_cache(ActiveRecord::Base.connection, "tmp/schema_dump.yaml")
500
480
  def dump_schema_cache(conn, filename)
501
481
  conn.schema_cache.dump_to(filename)
@@ -505,7 +485,41 @@ module ActiveRecord
505
485
  FileUtils.rm_f filename, verbose: false
506
486
  end
507
487
 
488
+ def with_temporary_connection_for_each(env: ActiveRecord::Tasks::DatabaseTasks.env, name: nil, clobber: false, &block) # :nodoc:
489
+ if name
490
+ db_config = ActiveRecord::Base.configurations.configs_for(env_name: env, name: name)
491
+ with_temporary_connection(db_config, clobber: clobber, &block)
492
+ else
493
+ ActiveRecord::Base.configurations.configs_for(env_name: env, name: name).each do |db_config|
494
+ with_temporary_connection(db_config, clobber: clobber, &block)
495
+ end
496
+ end
497
+ end
498
+
499
+ def with_temporary_connection(db_config, clobber: false) # :nodoc:
500
+ with_temporary_pool(db_config, clobber: clobber) do |pool|
501
+ yield pool.connection
502
+ end
503
+ end
504
+
505
+ def migration_class # :nodoc:
506
+ ActiveRecord::Base
507
+ end
508
+
509
+ def migration_connection # :nodoc:
510
+ migration_class.connection
511
+ end
512
+
508
513
  private
514
+ def with_temporary_pool(db_config, clobber: false)
515
+ original_db_config = migration_class.connection_db_config
516
+ pool = migration_class.connection_handler.establish_connection(db_config, clobber: clobber)
517
+
518
+ yield pool
519
+ ensure
520
+ migration_class.connection_handler.establish_connection(original_db_config, clobber: clobber)
521
+ end
522
+
509
523
  def configs_for(**options)
510
524
  Base.configurations.configs_for(**options)
511
525
  end
@@ -586,6 +600,23 @@ module ActiveRecord
586
600
  structure_load_flags
587
601
  end
588
602
  end
603
+
604
+ def check_current_protected_environment!(db_config)
605
+ with_temporary_pool(db_config) do |pool|
606
+ connection = pool.connection
607
+ current = connection.migration_context.current_environment
608
+ stored = connection.migration_context.last_stored_environment
609
+
610
+ if connection.migration_context.protected_environment?
611
+ raise ActiveRecord::ProtectedEnvironmentError.new(stored)
612
+ end
613
+
614
+ if stored && stored != current
615
+ raise ActiveRecord::EnvironmentMismatchError.new(current: current, stored: stored)
616
+ end
617
+ rescue ActiveRecord::NoDatabaseError
618
+ end
619
+ end
589
620
  end
590
621
  end
591
622
  end