activerecord 7.0.8.7 → 7.2.2.1

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 (279) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +631 -1944
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +29 -29
  5. data/examples/performance.rb +2 -2
  6. data/lib/active_record/aggregations.rb +16 -13
  7. data/lib/active_record/association_relation.rb +2 -2
  8. data/lib/active_record/associations/alias_tracker.rb +25 -19
  9. data/lib/active_record/associations/association.rb +35 -12
  10. data/lib/active_record/associations/association_scope.rb +16 -9
  11. data/lib/active_record/associations/belongs_to_association.rb +23 -8
  12. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
  13. data/lib/active_record/associations/builder/association.rb +3 -3
  14. data/lib/active_record/associations/builder/belongs_to.rb +22 -8
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
  16. data/lib/active_record/associations/builder/has_many.rb +3 -4
  17. data/lib/active_record/associations/builder/has_one.rb +3 -4
  18. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  19. data/lib/active_record/associations/collection_association.rb +26 -14
  20. data/lib/active_record/associations/collection_proxy.rb +29 -11
  21. data/lib/active_record/associations/errors.rb +265 -0
  22. data/lib/active_record/associations/foreign_association.rb +10 -3
  23. data/lib/active_record/associations/has_many_association.rb +21 -14
  24. data/lib/active_record/associations/has_many_through_association.rb +17 -7
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
  27. data/lib/active_record/associations/join_dependency.rb +10 -10
  28. data/lib/active_record/associations/nested_error.rb +47 -0
  29. data/lib/active_record/associations/preloader/association.rb +33 -8
  30. data/lib/active_record/associations/preloader/branch.rb +7 -1
  31. data/lib/active_record/associations/preloader/through_association.rb +1 -3
  32. data/lib/active_record/associations/preloader.rb +13 -10
  33. data/lib/active_record/associations/singular_association.rb +7 -1
  34. data/lib/active_record/associations/through_association.rb +22 -11
  35. data/lib/active_record/associations.rb +354 -485
  36. data/lib/active_record/attribute_assignment.rb +0 -4
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  38. data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
  39. data/lib/active_record/attribute_methods/dirty.rb +53 -35
  40. data/lib/active_record/attribute_methods/primary_key.rb +45 -25
  41. data/lib/active_record/attribute_methods/query.rb +28 -16
  42. data/lib/active_record/attribute_methods/read.rb +8 -7
  43. data/lib/active_record/attribute_methods/serialization.rb +131 -32
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +148 -33
  47. data/lib/active_record/attributes.rb +64 -50
  48. data/lib/active_record/autosave_association.rb +69 -37
  49. data/lib/active_record/base.rb +9 -5
  50. data/lib/active_record/callbacks.rb +11 -25
  51. data/lib/active_record/coders/column_serializer.rb +61 -0
  52. data/lib/active_record/coders/json.rb +1 -1
  53. data/lib/active_record/coders/yaml_column.rb +70 -42
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
  55. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  56. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +323 -88
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +217 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +137 -11
  65. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
  66. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  67. data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
  68. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
  69. data/lib/active_record/connection_adapters/column.rb +9 -0
  70. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  71. data/lib/active_record/connection_adapters/mysql/database_statements.rb +26 -139
  72. data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
  73. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  74. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
  77. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  78. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  79. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  80. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  81. data/lib/active_record/connection_adapters/postgresql/column.rb +14 -3
  82. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
  83. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  86. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  89. data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
  90. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
  91. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  92. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  93. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  94. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +370 -63
  95. data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
  96. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  97. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  98. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  99. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +45 -46
  100. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  101. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
  102. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  103. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
  104. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -110
  105. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  106. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  107. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  108. data/lib/active_record/connection_adapters.rb +124 -1
  109. data/lib/active_record/connection_handling.rb +96 -104
  110. data/lib/active_record/core.rb +251 -176
  111. data/lib/active_record/counter_cache.rb +68 -34
  112. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
  113. data/lib/active_record/database_configurations/database_config.rb +26 -5
  114. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  115. data/lib/active_record/database_configurations/url_config.rb +37 -12
  116. data/lib/active_record/database_configurations.rb +87 -34
  117. data/lib/active_record/delegated_type.rb +39 -10
  118. data/lib/active_record/deprecator.rb +7 -0
  119. data/lib/active_record/destroy_association_async_job.rb +3 -1
  120. data/lib/active_record/dynamic_matchers.rb +2 -2
  121. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  122. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  123. data/lib/active_record/encryption/config.rb +25 -1
  124. data/lib/active_record/encryption/configurable.rb +12 -19
  125. data/lib/active_record/encryption/context.rb +10 -3
  126. data/lib/active_record/encryption/contexts.rb +5 -1
  127. data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
  128. data/lib/active_record/encryption/encryptable_record.rb +45 -21
  129. data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
  130. data/lib/active_record/encryption/encryptor.rb +18 -3
  131. data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
  132. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  133. data/lib/active_record/encryption/key_generator.rb +12 -1
  134. data/lib/active_record/encryption/key_provider.rb +1 -1
  135. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  136. data/lib/active_record/encryption/message_serializer.rb +6 -0
  137. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  138. data/lib/active_record/encryption/properties.rb +3 -3
  139. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  140. data/lib/active_record/encryption/scheme.rb +22 -21
  141. data/lib/active_record/encryption.rb +3 -0
  142. data/lib/active_record/enum.rb +129 -28
  143. data/lib/active_record/errors.rb +151 -31
  144. data/lib/active_record/explain.rb +21 -12
  145. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  146. data/lib/active_record/fixture_set/render_context.rb +2 -0
  147. data/lib/active_record/fixture_set/table_row.rb +29 -8
  148. data/lib/active_record/fixtures.rb +167 -97
  149. data/lib/active_record/future_result.rb +47 -8
  150. data/lib/active_record/gem_version.rb +4 -4
  151. data/lib/active_record/inheritance.rb +34 -18
  152. data/lib/active_record/insert_all.rb +72 -22
  153. data/lib/active_record/integration.rb +11 -8
  154. data/lib/active_record/internal_metadata.rb +124 -20
  155. data/lib/active_record/locking/optimistic.rb +8 -7
  156. data/lib/active_record/locking/pessimistic.rb +5 -2
  157. data/lib/active_record/log_subscriber.rb +18 -22
  158. data/lib/active_record/marshalling.rb +59 -0
  159. data/lib/active_record/message_pack.rb +124 -0
  160. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  161. data/lib/active_record/middleware/database_selector.rb +6 -8
  162. data/lib/active_record/middleware/shard_selector.rb +3 -1
  163. data/lib/active_record/migration/command_recorder.rb +106 -8
  164. data/lib/active_record/migration/compatibility.rb +147 -5
  165. data/lib/active_record/migration/default_strategy.rb +22 -0
  166. data/lib/active_record/migration/execution_strategy.rb +19 -0
  167. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  168. data/lib/active_record/migration.rb +234 -117
  169. data/lib/active_record/model_schema.rb +90 -102
  170. data/lib/active_record/nested_attributes.rb +48 -11
  171. data/lib/active_record/normalization.rb +163 -0
  172. data/lib/active_record/persistence.rb +168 -339
  173. data/lib/active_record/promise.rb +84 -0
  174. data/lib/active_record/query_cache.rb +18 -25
  175. data/lib/active_record/query_logs.rb +92 -52
  176. data/lib/active_record/query_logs_formatter.rb +41 -0
  177. data/lib/active_record/querying.rb +33 -8
  178. data/lib/active_record/railtie.rb +129 -85
  179. data/lib/active_record/railties/controller_runtime.rb +22 -7
  180. data/lib/active_record/railties/databases.rake +145 -154
  181. data/lib/active_record/railties/job_runtime.rb +23 -0
  182. data/lib/active_record/readonly_attributes.rb +32 -5
  183. data/lib/active_record/reflection.rb +267 -69
  184. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  185. data/lib/active_record/relation/batches.rb +198 -63
  186. data/lib/active_record/relation/calculations.rb +250 -93
  187. data/lib/active_record/relation/delegation.rb +30 -19
  188. data/lib/active_record/relation/finder_methods.rb +93 -18
  189. data/lib/active_record/relation/merger.rb +6 -6
  190. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  191. data/lib/active_record/relation/predicate_builder/association_query_value.rb +18 -3
  192. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  193. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  194. data/lib/active_record/relation/predicate_builder.rb +28 -16
  195. data/lib/active_record/relation/query_attribute.rb +2 -1
  196. data/lib/active_record/relation/query_methods.rb +576 -107
  197. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  198. data/lib/active_record/relation/spawn_methods.rb +5 -4
  199. data/lib/active_record/relation/where_clause.rb +7 -19
  200. data/lib/active_record/relation.rb +580 -90
  201. data/lib/active_record/result.rb +49 -48
  202. data/lib/active_record/runtime_registry.rb +63 -1
  203. data/lib/active_record/sanitization.rb +70 -25
  204. data/lib/active_record/schema.rb +8 -7
  205. data/lib/active_record/schema_dumper.rb +63 -14
  206. data/lib/active_record/schema_migration.rb +75 -24
  207. data/lib/active_record/scoping/default.rb +15 -5
  208. data/lib/active_record/scoping/named.rb +3 -2
  209. data/lib/active_record/scoping.rb +2 -1
  210. data/lib/active_record/secure_password.rb +60 -0
  211. data/lib/active_record/secure_token.rb +21 -3
  212. data/lib/active_record/signed_id.rb +27 -6
  213. data/lib/active_record/statement_cache.rb +7 -7
  214. data/lib/active_record/store.rb +8 -8
  215. data/lib/active_record/suppressor.rb +3 -1
  216. data/lib/active_record/table_metadata.rb +1 -1
  217. data/lib/active_record/tasks/database_tasks.rb +190 -118
  218. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  219. data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
  220. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  221. data/lib/active_record/test_fixtures.rb +170 -155
  222. data/lib/active_record/testing/query_assertions.rb +121 -0
  223. data/lib/active_record/timestamp.rb +31 -17
  224. data/lib/active_record/token_for.rb +123 -0
  225. data/lib/active_record/touch_later.rb +12 -7
  226. data/lib/active_record/transaction.rb +132 -0
  227. data/lib/active_record/transactions.rb +106 -24
  228. data/lib/active_record/translation.rb +0 -2
  229. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  230. data/lib/active_record/type/internal/timezone.rb +7 -2
  231. data/lib/active_record/type/serialized.rb +1 -3
  232. data/lib/active_record/type/time.rb +4 -0
  233. data/lib/active_record/type_caster/connection.rb +4 -4
  234. data/lib/active_record/validations/absence.rb +1 -1
  235. data/lib/active_record/validations/associated.rb +9 -3
  236. data/lib/active_record/validations/numericality.rb +5 -4
  237. data/lib/active_record/validations/presence.rb +5 -28
  238. data/lib/active_record/validations/uniqueness.rb +61 -11
  239. data/lib/active_record/validations.rb +12 -5
  240. data/lib/active_record/version.rb +1 -1
  241. data/lib/active_record.rb +247 -33
  242. data/lib/arel/alias_predication.rb +1 -1
  243. data/lib/arel/collectors/bind.rb +2 -0
  244. data/lib/arel/collectors/composite.rb +7 -0
  245. data/lib/arel/collectors/sql_string.rb +1 -1
  246. data/lib/arel/collectors/substitute_binds.rb +1 -1
  247. data/lib/arel/errors.rb +10 -0
  248. data/lib/arel/factory_methods.rb +4 -0
  249. data/lib/arel/nodes/binary.rb +6 -7
  250. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  251. data/lib/arel/nodes/cte.rb +36 -0
  252. data/lib/arel/nodes/fragments.rb +35 -0
  253. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  254. data/lib/arel/nodes/leading_join.rb +8 -0
  255. data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
  256. data/lib/arel/nodes/node.rb +115 -5
  257. data/lib/arel/nodes/sql_literal.rb +13 -0
  258. data/lib/arel/nodes/table_alias.rb +4 -0
  259. data/lib/arel/nodes.rb +6 -2
  260. data/lib/arel/predications.rb +3 -1
  261. data/lib/arel/select_manager.rb +1 -1
  262. data/lib/arel/table.rb +9 -5
  263. data/lib/arel/tree_manager.rb +8 -3
  264. data/lib/arel/update_manager.rb +2 -1
  265. data/lib/arel/visitors/dot.rb +1 -0
  266. data/lib/arel/visitors/mysql.rb +17 -5
  267. data/lib/arel/visitors/postgresql.rb +1 -12
  268. data/lib/arel/visitors/sqlite.rb +25 -0
  269. data/lib/arel/visitors/to_sql.rb +112 -34
  270. data/lib/arel/visitors/visitor.rb +2 -2
  271. data/lib/arel.rb +21 -3
  272. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  273. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  274. data/lib/rails/generators/active_record/migration.rb +3 -1
  275. data/lib/rails/generators/active_record/model/USAGE +113 -0
  276. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  277. metadata +54 -12
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -3,8 +3,6 @@
3
3
  module ActiveRecord
4
4
  module Tasks # :nodoc:
5
5
  class SQLiteDatabaseTasks # :nodoc:
6
- delegate :connection, :establish_connection, to: ActiveRecord::Base
7
-
8
6
  def self.using_database_configurations?
9
7
  true
10
8
  end
@@ -17,25 +15,26 @@ module ActiveRecord
17
15
  def create
18
16
  raise DatabaseAlreadyExists if File.exist?(db_config.database)
19
17
 
20
- establish_connection(db_config)
18
+ establish_connection
21
19
  connection
22
20
  end
23
21
 
24
22
  def drop
25
- require "pathname"
26
- path = Pathname.new(db_config.database)
27
- file = path.absolute? ? path.to_s : File.join(root, path)
28
-
23
+ db_path = db_config.database
24
+ file = File.absolute_path?(db_path) ? db_path : File.join(root, db_path)
29
25
  FileUtils.rm(file)
26
+ FileUtils.rm_f(["#{file}-shm", "#{file}-wal"])
30
27
  rescue Errno::ENOENT => error
31
28
  raise NoDatabaseError.new(error.message)
32
29
  end
33
30
 
34
31
  def purge
32
+ connection.disconnect!
35
33
  drop
36
34
  rescue NoDatabaseError
37
35
  ensure
38
36
  create
37
+ connection.reconnect!
39
38
  end
40
39
 
41
40
  def charset
@@ -49,6 +48,7 @@ module ActiveRecord
49
48
 
50
49
  ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
51
50
  if ignore_tables.any?
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
53
  args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
54
54
  else
@@ -65,6 +65,15 @@ module ActiveRecord
65
65
  private
66
66
  attr_reader :db_config, :root
67
67
 
68
+ def connection
69
+ ActiveRecord::Base.lease_connection
70
+ end
71
+
72
+ def establish_connection(config = db_config)
73
+ ActiveRecord::Base.establish_connection(config)
74
+ connection.connect!
75
+ end
76
+
68
77
  def run_cmd(cmd, args, out)
69
78
  fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
70
79
  end
@@ -13,17 +13,31 @@ module ActiveRecord
13
13
 
14
14
  def after_teardown # :nodoc:
15
15
  super
16
+ ensure
16
17
  teardown_fixtures
17
18
  end
18
19
 
19
20
  included do
20
- class_attribute :fixture_path, instance_writer: false
21
+ ##
22
+ # :singleton-method: fixture_paths
23
+ #
24
+ # Returns the ActiveRecord::FixtureSet collection
25
+
26
+ ##
27
+ # :singleton-method: fixture_paths=
28
+ #
29
+ # :call-seq:
30
+ # fixture_paths=(fixture_paths)
31
+ class_attribute :fixture_paths, instance_writer: false, default: []
21
32
  class_attribute :fixture_table_names, default: []
22
33
  class_attribute :fixture_class_names, default: {}
23
34
  class_attribute :use_transactional_tests, default: true
24
35
  class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
25
36
  class_attribute :pre_loaded_fixtures, default: false
26
37
  class_attribute :lock_threads, default: true
38
+ class_attribute :fixture_sets, default: {}
39
+
40
+ ActiveSupport.run_load_hooks(:active_record_fixtures, self)
27
41
  end
28
42
 
29
43
  module ClassMethods
@@ -41,49 +55,31 @@ module ActiveRecord
41
55
 
42
56
  def fixtures(*fixture_set_names)
43
57
  if fixture_set_names.first == :all
44
- raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
45
- fixture_set_names = Dir[::File.join(fixture_path, "{**,*}/*.{yml}")].uniq
46
- fixture_set_names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
47
- fixture_set_names.map! { |f| f[fixture_path.to_s.size..-5].delete_prefix("/") }
58
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
59
+ fixture_set_names = fixture_paths.flat_map do |path|
60
+ names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq
61
+ names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
62
+ names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") }
63
+ end.uniq
48
64
  else
49
65
  fixture_set_names = fixture_set_names.flatten.map(&:to_s)
50
66
  end
51
67
 
52
- self.fixture_table_names |= fixture_set_names
68
+ self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
53
69
  setup_fixture_accessors(fixture_set_names)
54
70
  end
55
71
 
56
72
  def setup_fixture_accessors(fixture_set_names = nil)
57
73
  fixture_set_names = Array(fixture_set_names || fixture_table_names)
58
- methods = Module.new do
74
+ unless fixture_set_names.empty?
75
+ self.fixture_sets = fixture_sets.dup
59
76
  fixture_set_names.each do |fs_name|
60
- fs_name = fs_name.to_s
61
- accessor_name = fs_name.tr("/", "_").to_sym
62
-
63
- define_method(accessor_name) do |*fixture_names|
64
- force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
65
- return_single_record = fixture_names.size == 1
66
- fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
67
-
68
- @fixture_cache[fs_name] ||= {}
69
-
70
- instances = fixture_names.map do |f_name|
71
- f_name = f_name.to_s if f_name.is_a?(Symbol)
72
- @fixture_cache[fs_name].delete(f_name) if force_reload
73
-
74
- if @loaded_fixtures[fs_name][f_name]
75
- @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
76
- else
77
- raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
78
- end
79
- end
80
-
81
- return_single_record ? instances.first : instances
82
- end
83
- private accessor_name
77
+ key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name
78
+ key = -key.to_s if key.is_a?(Symbol)
79
+ fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
80
+ fixture_sets[key] = fs_name
84
81
  end
85
82
  end
86
- include methods
87
83
  end
88
84
 
89
85
  # Prevents automatically wrapping each specified test in a transaction,
@@ -100,99 +96,109 @@ module ActiveRecord
100
96
  end
101
97
  end
102
98
 
103
- def run_in_transaction?
104
- use_transactional_tests &&
105
- !self.class.uses_transaction?(name)
99
+ # Generic fixture accessor for fixture names that may conflict with other methods.
100
+ #
101
+ # assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
102
+ # assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
103
+ def fixture(fixture_set_name, *fixture_names)
104
+ active_record_fixture(fixture_set_name, *fixture_names)
106
105
  end
107
106
 
108
- def setup_fixtures(config = ActiveRecord::Base)
109
- if pre_loaded_fixtures && !use_transactional_tests
110
- raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
107
+ private
108
+ def run_in_transaction?
109
+ use_transactional_tests &&
110
+ !self.class.uses_transaction?(name)
111
111
  end
112
112
 
113
- @fixture_cache = {}
114
- @fixture_connections = []
115
- @@already_loaded_fixtures ||= {}
116
- @connection_subscriber = nil
117
- @legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
118
- @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
119
-
120
- # Load fixtures once and begin transaction.
121
- if run_in_transaction?
122
- if @@already_loaded_fixtures[self.class]
123
- @loaded_fixtures = @@already_loaded_fixtures[self.class]
113
+ def setup_fixtures(config = ActiveRecord::Base)
114
+ if pre_loaded_fixtures && !use_transactional_tests
115
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
116
+ end
117
+
118
+ @fixture_cache = {}
119
+ @fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
120
+ @fixture_connection_pools = []
121
+ @@already_loaded_fixtures ||= {}
122
+ @connection_subscriber = nil
123
+ @saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
124
+
125
+ if run_in_transaction?
126
+ # Load fixtures once and begin transaction.
127
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
128
+ unless @loaded_fixtures
129
+ @@already_loaded_fixtures.clear
130
+ @loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
131
+ end
132
+
133
+ setup_transactional_fixtures
124
134
  else
135
+ # Load fixtures for every test.
136
+ ActiveRecord::FixtureSet.reset_cache
137
+ invalidate_already_loaded_fixtures
125
138
  @loaded_fixtures = load_fixtures(config)
126
- @@already_loaded_fixtures[self.class] = @loaded_fixtures
127
139
  end
128
140
 
141
+ # Instantiate fixtures for every test if requested.
142
+ instantiate_fixtures if use_instantiated_fixtures
143
+ end
144
+
145
+ def teardown_fixtures
146
+ # Rollback changes if a transaction is active.
147
+ if run_in_transaction?
148
+ teardown_transactional_fixtures
149
+ else
150
+ ActiveRecord::FixtureSet.reset_cache
151
+ invalidate_already_loaded_fixtures
152
+ end
153
+
154
+ ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
155
+ end
156
+
157
+ def invalidate_already_loaded_fixtures
158
+ @@already_loaded_fixtures.clear
159
+ end
160
+
161
+ def setup_transactional_fixtures
162
+ setup_shared_connection_pool
163
+
129
164
  # Begin transactions for connections already established
130
- @fixture_connections = enlist_fixture_connections
131
- @fixture_connections.each do |connection|
132
- connection.begin_transaction joinable: false, _lazy: false
133
- connection.pool.lock_thread = true if lock_threads
165
+ @fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
166
+ @fixture_connection_pools.each do |pool|
167
+ pool.pin_connection!(lock_threads)
168
+ pool.lease_connection
134
169
  end
135
170
 
136
171
  # When connections are established in the future, begin a transaction too
137
172
  @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
138
- spec_name = payload[:spec_name] if payload.key?(:spec_name)
173
+ connection_name = payload[:connection_name] if payload.key?(:connection_name)
139
174
  shard = payload[:shard] if payload.key?(:shard)
140
- setup_shared_connection_pool if ActiveRecord.legacy_connection_handling
141
175
 
142
- if spec_name
143
- begin
144
- connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name, shard: shard)
145
- rescue ConnectionNotEstablished
146
- connection = nil
147
- end
148
-
149
- if connection
150
- setup_shared_connection_pool unless ActiveRecord.legacy_connection_handling
176
+ if connection_name
177
+ pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
178
+ if pool
179
+ setup_shared_connection_pool
151
180
 
152
- if !@fixture_connections.include?(connection)
153
- connection.begin_transaction joinable: false, _lazy: false
154
- connection.pool.lock_thread = true if lock_threads
155
- @fixture_connections << connection
181
+ unless @fixture_connection_pools.include?(pool)
182
+ pool.pin_connection!(lock_threads)
183
+ pool.lease_connection
184
+ @fixture_connection_pools << pool
156
185
  end
157
186
  end
158
187
  end
159
188
  end
160
-
161
- # Load fixtures for every test.
162
- else
163
- ActiveRecord::FixtureSet.reset_cache
164
- @@already_loaded_fixtures[self.class] = nil
165
- @loaded_fixtures = load_fixtures(config)
166
189
  end
167
190
 
168
- # Instantiate fixtures for every test if requested.
169
- instantiate_fixtures if use_instantiated_fixtures
170
- end
171
-
172
- def teardown_fixtures
173
- # Rollback changes if a transaction is active.
174
- if run_in_transaction?
191
+ def teardown_transactional_fixtures
175
192
  ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
176
- @fixture_connections.each do |connection|
177
- connection.rollback_transaction if connection.transaction_open?
178
- connection.pool.lock_thread = false
193
+ unless @fixture_connection_pools.map(&:unpin_connection!).all?
194
+ # Something caused the transaction to be committed or rolled back
195
+ # We can no longer trust the database is in a clean state.
196
+ @@already_loaded_fixtures.clear
179
197
  end
180
- @fixture_connections.clear
198
+ @fixture_connection_pools.clear
181
199
  teardown_shared_connection_pool
182
- else
183
- ActiveRecord::FixtureSet.reset_cache
184
200
  end
185
201
 
186
- ActiveRecord::Base.clear_active_connections!
187
- end
188
-
189
- def enlist_fixture_connections
190
- setup_shared_connection_pool
191
-
192
- ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
193
- end
194
-
195
- private
196
202
  # Shares the writing connection pool with connections on
197
203
  # other handlers.
198
204
  #
@@ -200,79 +206,43 @@ module ActiveRecord
200
206
  # need to share a connection pool so that the reading connection
201
207
  # can see data in the open transaction on the writing connection.
202
208
  def setup_shared_connection_pool
203
- if ActiveRecord.legacy_connection_handling
204
- writing_handler = ActiveRecord::Base.connection_handlers[ActiveRecord.writing_role]
205
-
206
- ActiveRecord::Base.connection_handlers.values.each do |handler|
207
- if handler != writing_handler
208
- handler.connection_pool_names.each do |name|
209
- writing_pool_manager = writing_handler.send(:owner_to_pool_manager)[name]
210
- return unless writing_pool_manager
211
-
212
- pool_manager = handler.send(:owner_to_pool_manager)[name]
213
- @legacy_saved_pool_configs[handler][name] ||= {}
214
- pool_manager.shard_names.each do |shard_name|
215
- writing_pool_config = writing_pool_manager.get_pool_config(nil, shard_name)
216
- pool_config = pool_manager.get_pool_config(nil, shard_name)
217
- next if pool_config == writing_pool_config
218
-
219
- @legacy_saved_pool_configs[handler][name][shard_name] = pool_config
220
- pool_manager.set_pool_config(nil, shard_name, writing_pool_config)
221
- end
222
- end
223
- end
224
- end
225
- else
226
- handler = ActiveRecord::Base.connection_handler
227
-
228
- handler.connection_pool_names.each do |name|
229
- pool_manager = handler.send(:owner_to_pool_manager)[name]
230
- pool_manager.shard_names.each do |shard_name|
231
- writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
232
- @saved_pool_configs[name][shard_name] ||= {}
233
- pool_manager.role_names.each do |role|
234
- next unless pool_config = pool_manager.get_pool_config(role, shard_name)
235
- next if pool_config == writing_pool_config
236
-
237
- @saved_pool_configs[name][shard_name][role] = pool_config
238
- pool_manager.set_pool_config(role, shard_name, writing_pool_config)
239
- end
209
+ handler = ActiveRecord::Base.connection_handler
210
+
211
+ handler.connection_pool_names.each do |name|
212
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
213
+ pool_manager.shard_names.each do |shard_name|
214
+ writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
215
+ @saved_pool_configs[name][shard_name] ||= {}
216
+ pool_manager.role_names.each do |role|
217
+ next unless pool_config = pool_manager.get_pool_config(role, shard_name)
218
+ next if pool_config == writing_pool_config
219
+
220
+ @saved_pool_configs[name][shard_name][role] = pool_config
221
+ pool_manager.set_pool_config(role, shard_name, writing_pool_config)
240
222
  end
241
223
  end
242
224
  end
243
225
  end
244
226
 
245
227
  def teardown_shared_connection_pool
246
- if ActiveRecord.legacy_connection_handling
247
- @legacy_saved_pool_configs.each_pair do |handler, names|
248
- names.each_pair do |name, shards|
249
- shards.each_pair do |shard_name, pool_config|
250
- pool_manager = handler.send(:owner_to_pool_manager)[name]
251
- pool_manager.set_pool_config(nil, shard_name, pool_config)
252
- end
253
- end
254
- end
255
- else
256
- handler = ActiveRecord::Base.connection_handler
228
+ handler = ActiveRecord::Base.connection_handler
257
229
 
258
- @saved_pool_configs.each_pair do |name, shards|
259
- pool_manager = handler.send(:owner_to_pool_manager)[name]
260
- shards.each_pair do |shard_name, roles|
261
- roles.each_pair do |role, pool_config|
262
- next unless pool_manager.get_pool_config(role, shard_name)
230
+ @saved_pool_configs.each_pair do |name, shards|
231
+ pool_manager = handler.send(:connection_name_to_pool_manager)[name]
232
+ shards.each_pair do |shard_name, roles|
233
+ roles.each_pair do |role, pool_config|
234
+ next unless pool_manager.get_pool_config(role, shard_name)
263
235
 
264
- pool_manager.set_pool_config(role, shard_name, pool_config)
265
- end
236
+ pool_manager.set_pool_config(role, shard_name, pool_config)
266
237
  end
267
238
  end
268
239
  end
269
240
 
270
- @legacy_saved_pool_configs.clear
271
241
  @saved_pool_configs.clear
272
242
  end
273
243
 
274
244
  def load_fixtures(config)
275
- ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config).index_by(&:name)
245
+ ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
276
246
  end
277
247
 
278
248
  def instantiate_fixtures
@@ -290,5 +260,50 @@ module ActiveRecord
290
260
  def load_instances?
291
261
  use_instantiated_fixtures != :no_instances
292
262
  end
263
+
264
+ def method_missing(method, ...)
265
+ if fixture_sets.key?(method.name)
266
+ active_record_fixture(method, ...)
267
+ else
268
+ super
269
+ end
270
+ end
271
+
272
+ def respond_to_missing?(method, include_private = false)
273
+ if include_private && fixture_sets.key?(method.name)
274
+ true
275
+ else
276
+ super
277
+ end
278
+ end
279
+
280
+ def active_record_fixture(fixture_set_name, *fixture_names)
281
+ if fs_name = fixture_sets[fixture_set_name.name]
282
+ access_fixture(fs_name, *fixture_names)
283
+ else
284
+ raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
285
+ end
286
+ end
287
+
288
+ def access_fixture(fs_name, *fixture_names)
289
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
290
+ return_single_record = fixture_names.size == 1
291
+
292
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
293
+ @fixture_cache[fs_name] ||= {}
294
+
295
+ instances = fixture_names.map do |f_name|
296
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
297
+ @fixture_cache[fs_name].delete(f_name) if force_reload
298
+
299
+ if @loaded_fixtures[fs_name][f_name]
300
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
301
+ else
302
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
303
+ end
304
+ end
305
+
306
+ return_single_record ? instances.first : instances
307
+ end
293
308
  end
294
309
  end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Assertions
5
+ module QueryAssertions
6
+ # Asserts that the number of SQL queries executed in the given block matches the expected count.
7
+ #
8
+ # # Check for exact number of queries
9
+ # assert_queries_count(1) { Post.first }
10
+ #
11
+ # # Check for any number of queries
12
+ # assert_queries_count { Post.first }
13
+ #
14
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
15
+ #
16
+ # assert_queries_count(1, include_schema: true) { Post.columns }
17
+ #
18
+ def assert_queries_count(count = nil, include_schema: false, &block)
19
+ ActiveRecord::Base.lease_connection.materialize_transactions
20
+
21
+ counter = SQLCounter.new
22
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
23
+ result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
24
+ queries = include_schema ? counter.log_all : counter.log
25
+ if count
26
+ assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
27
+ else
28
+ assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
29
+ end
30
+ result
31
+ end
32
+ end
33
+
34
+ # Asserts that no SQL queries are executed in the given block.
35
+ #
36
+ # assert_no_queries { post.comments }
37
+ #
38
+ # If the +:include_schema+ option is provided, any queries (including schema related) are counted.
39
+ #
40
+ # assert_no_queries(include_schema: true) { Post.columns }
41
+ #
42
+ def assert_no_queries(include_schema: false, &block)
43
+ assert_queries_count(0, include_schema: include_schema, &block)
44
+ end
45
+
46
+ # Asserts that the SQL queries executed in the given block match expected pattern.
47
+ #
48
+ # # Check for exact number of queries
49
+ # assert_queries_match(/LIMIT \?/, count: 1) { Post.first }
50
+ #
51
+ # # Check for any number of queries
52
+ # assert_queries_match(/LIMIT \?/) { Post.first }
53
+ #
54
+ # If the +:include_schema+ option is provided, any queries (including schema related)
55
+ # that match the matcher are considered.
56
+ #
57
+ # assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
58
+ #
59
+ def assert_queries_match(match, count: nil, include_schema: false, &block)
60
+ ActiveRecord::Base.lease_connection.materialize_transactions
61
+
62
+ counter = SQLCounter.new
63
+ ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
64
+ result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
65
+ queries = include_schema ? counter.log_all : counter.log
66
+ matched_queries = queries.select { |query| match === query }
67
+
68
+ if count
69
+ assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
70
+ else
71
+ assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
72
+ end
73
+
74
+ result
75
+ end
76
+ end
77
+
78
+ # Asserts that no SQL queries matching the pattern are executed in the given block.
79
+ #
80
+ # assert_no_queries_match(/SELECT/i) { post.comments }
81
+ #
82
+ # If the +:include_schema+ option is provided, any queries (including schema related)
83
+ # that match the matcher are counted.
84
+ #
85
+ # assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
86
+ #
87
+ def assert_no_queries_match(match, include_schema: false, &block)
88
+ assert_queries_match(match, count: 0, include_schema: include_schema, &block)
89
+ end
90
+
91
+ class SQLCounter # :nodoc:
92
+ attr_reader :log_full, :log_all
93
+
94
+ def initialize
95
+ @log_full = []
96
+ @log_all = []
97
+ end
98
+
99
+ def log
100
+ @log_full.map(&:first)
101
+ end
102
+
103
+ def call(*, payload)
104
+ return if payload[:cached]
105
+
106
+ sql = payload[:sql]
107
+ @log_all << sql
108
+
109
+ unless payload[:name] == "SCHEMA"
110
+ bound_values = (payload[:binds] || []).map do |value|
111
+ value = value.value_for_database if value.respond_to?(:value_for_database)
112
+ value
113
+ end
114
+
115
+ @log_full << [sql, bound_values]
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end