activerecord 5.2.4.3 → 6.0.2.2

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

Potentially problematic release.


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

Files changed (269) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +715 -571
  3. data/MIT-LICENSE +3 -1
  4. data/README.rdoc +4 -2
  5. data/examples/performance.rb +1 -1
  6. data/lib/active_record.rb +9 -2
  7. data/lib/active_record/aggregations.rb +4 -2
  8. data/lib/active_record/association_relation.rb +15 -6
  9. data/lib/active_record/associations.rb +20 -15
  10. data/lib/active_record/associations/association.rb +61 -20
  11. data/lib/active_record/associations/association_scope.rb +4 -6
  12. data/lib/active_record/associations/belongs_to_association.rb +36 -42
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +0 -4
  14. data/lib/active_record/associations/builder/association.rb +14 -18
  15. data/lib/active_record/associations/builder/belongs_to.rb +19 -52
  16. data/lib/active_record/associations/builder/collection_association.rb +3 -13
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +17 -38
  18. data/lib/active_record/associations/builder/has_many.rb +2 -0
  19. data/lib/active_record/associations/builder/has_one.rb +35 -1
  20. data/lib/active_record/associations/builder/singular_association.rb +2 -0
  21. data/lib/active_record/associations/collection_association.rb +12 -23
  22. data/lib/active_record/associations/collection_proxy.rb +12 -15
  23. data/lib/active_record/associations/foreign_association.rb +7 -0
  24. data/lib/active_record/associations/has_many_association.rb +2 -10
  25. data/lib/active_record/associations/has_many_through_association.rb +14 -14
  26. data/lib/active_record/associations/has_one_association.rb +28 -30
  27. data/lib/active_record/associations/has_one_through_association.rb +5 -5
  28. data/lib/active_record/associations/join_dependency.rb +28 -28
  29. data/lib/active_record/associations/join_dependency/join_association.rb +9 -10
  30. data/lib/active_record/associations/join_dependency/join_part.rb +2 -2
  31. data/lib/active_record/associations/preloader.rb +39 -31
  32. data/lib/active_record/associations/preloader/association.rb +38 -36
  33. data/lib/active_record/associations/preloader/through_association.rb +48 -39
  34. data/lib/active_record/associations/singular_association.rb +2 -16
  35. data/lib/active_record/attribute_assignment.rb +7 -10
  36. data/lib/active_record/attribute_methods.rb +28 -100
  37. data/lib/active_record/attribute_methods/before_type_cast.rb +4 -1
  38. data/lib/active_record/attribute_methods/dirty.rb +111 -40
  39. data/lib/active_record/attribute_methods/primary_key.rb +15 -22
  40. data/lib/active_record/attribute_methods/query.rb +2 -3
  41. data/lib/active_record/attribute_methods/read.rb +15 -53
  42. data/lib/active_record/attribute_methods/serialization.rb +1 -1
  43. data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
  44. data/lib/active_record/attribute_methods/write.rb +17 -24
  45. data/lib/active_record/attributes.rb +13 -0
  46. data/lib/active_record/autosave_association.rb +2 -2
  47. data/lib/active_record/base.rb +2 -3
  48. data/lib/active_record/callbacks.rb +5 -19
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +104 -16
  50. data/lib/active_record/connection_adapters/abstract/database_limits.rb +17 -4
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +99 -123
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +18 -8
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +68 -17
  54. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +19 -12
  55. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +76 -48
  56. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +1 -3
  57. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +132 -53
  58. data/lib/active_record/connection_adapters/abstract/transaction.rb +96 -56
  59. data/lib/active_record/connection_adapters/abstract_adapter.rb +187 -43
  60. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +138 -195
  61. data/lib/active_record/connection_adapters/column.rb +17 -13
  62. data/lib/active_record/connection_adapters/connection_specification.rb +53 -43
  63. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +6 -10
  64. data/lib/active_record/connection_adapters/mysql/database_statements.rb +75 -13
  65. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -7
  66. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +3 -4
  67. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +40 -32
  68. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +14 -6
  69. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +129 -13
  70. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +6 -10
  71. data/lib/active_record/connection_adapters/mysql2_adapter.rb +26 -9
  72. data/lib/active_record/connection_adapters/postgresql/column.rb +17 -31
  73. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +22 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  75. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +1 -4
  76. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +1 -1
  77. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +1 -1
  78. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +2 -2
  79. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +1 -1
  80. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +1 -1
  81. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +9 -7
  82. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +6 -3
  83. data/lib/active_record/connection_adapters/postgresql/quoting.rb +44 -7
  84. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +12 -1
  85. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +107 -91
  86. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +55 -53
  87. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +24 -27
  88. data/lib/active_record/connection_adapters/postgresql_adapter.rb +164 -74
  89. data/lib/active_record/connection_adapters/schema_cache.rb +37 -14
  90. data/lib/active_record/connection_adapters/sql_type_metadata.rb +11 -8
  91. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +120 -0
  92. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +42 -6
  93. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +42 -11
  94. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +129 -141
  95. data/lib/active_record/connection_handling.rb +155 -26
  96. data/lib/active_record/core.rb +103 -59
  97. data/lib/active_record/counter_cache.rb +4 -29
  98. data/lib/active_record/database_configurations.rb +233 -0
  99. data/lib/active_record/database_configurations/database_config.rb +37 -0
  100. data/lib/active_record/database_configurations/hash_config.rb +50 -0
  101. data/lib/active_record/database_configurations/url_config.rb +79 -0
  102. data/lib/active_record/dynamic_matchers.rb +1 -1
  103. data/lib/active_record/enum.rb +37 -7
  104. data/lib/active_record/errors.rb +15 -7
  105. data/lib/active_record/explain.rb +1 -1
  106. data/lib/active_record/fixture_set/model_metadata.rb +33 -0
  107. data/lib/active_record/fixture_set/render_context.rb +17 -0
  108. data/lib/active_record/fixture_set/table_row.rb +153 -0
  109. data/lib/active_record/fixture_set/table_rows.rb +47 -0
  110. data/lib/active_record/fixtures.rb +145 -472
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +13 -3
  113. data/lib/active_record/insert_all.rb +179 -0
  114. data/lib/active_record/integration.rb +68 -16
  115. data/lib/active_record/internal_metadata.rb +10 -2
  116. data/lib/active_record/locking/optimistic.rb +5 -6
  117. data/lib/active_record/locking/pessimistic.rb +3 -3
  118. data/lib/active_record/log_subscriber.rb +7 -26
  119. data/lib/active_record/middleware/database_selector.rb +75 -0
  120. data/lib/active_record/middleware/database_selector/resolver.rb +88 -0
  121. data/lib/active_record/middleware/database_selector/resolver/session.rb +45 -0
  122. data/lib/active_record/migration.rb +100 -81
  123. data/lib/active_record/migration/command_recorder.rb +50 -6
  124. data/lib/active_record/migration/compatibility.rb +76 -49
  125. data/lib/active_record/model_schema.rb +33 -9
  126. data/lib/active_record/nested_attributes.rb +2 -2
  127. data/lib/active_record/no_touching.rb +7 -0
  128. data/lib/active_record/persistence.rb +228 -24
  129. data/lib/active_record/query_cache.rb +11 -4
  130. data/lib/active_record/querying.rb +32 -20
  131. data/lib/active_record/railtie.rb +80 -43
  132. data/lib/active_record/railties/collection_cache_association_loading.rb +34 -0
  133. data/lib/active_record/railties/controller_runtime.rb +30 -35
  134. data/lib/active_record/railties/databases.rake +199 -46
  135. data/lib/active_record/reflection.rb +32 -30
  136. data/lib/active_record/relation.rb +311 -80
  137. data/lib/active_record/relation/batches.rb +13 -10
  138. data/lib/active_record/relation/calculations.rb +53 -47
  139. data/lib/active_record/relation/delegation.rb +26 -43
  140. data/lib/active_record/relation/finder_methods.rb +23 -27
  141. data/lib/active_record/relation/merger.rb +11 -20
  142. data/lib/active_record/relation/predicate_builder.rb +4 -6
  143. data/lib/active_record/relation/predicate_builder/array_handler.rb +5 -4
  144. data/lib/active_record/relation/predicate_builder/association_query_value.rb +1 -4
  145. data/lib/active_record/relation/predicate_builder/base_handler.rb +1 -2
  146. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +1 -2
  147. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +1 -4
  148. data/lib/active_record/relation/predicate_builder/range_handler.rb +3 -23
  149. data/lib/active_record/relation/query_attribute.rb +13 -8
  150. data/lib/active_record/relation/query_methods.rb +213 -64
  151. data/lib/active_record/relation/spawn_methods.rb +1 -1
  152. data/lib/active_record/relation/where_clause.rb +14 -10
  153. data/lib/active_record/relation/where_clause_factory.rb +1 -2
  154. data/lib/active_record/result.rb +30 -11
  155. data/lib/active_record/sanitization.rb +32 -40
  156. data/lib/active_record/schema.rb +2 -11
  157. data/lib/active_record/schema_dumper.rb +22 -7
  158. data/lib/active_record/schema_migration.rb +5 -1
  159. data/lib/active_record/scoping.rb +8 -8
  160. data/lib/active_record/scoping/default.rb +4 -5
  161. data/lib/active_record/scoping/named.rb +20 -15
  162. data/lib/active_record/statement_cache.rb +30 -3
  163. data/lib/active_record/store.rb +87 -8
  164. data/lib/active_record/table_metadata.rb +10 -17
  165. data/lib/active_record/tasks/database_tasks.rb +194 -25
  166. data/lib/active_record/tasks/mysql_database_tasks.rb +5 -5
  167. data/lib/active_record/tasks/postgresql_database_tasks.rb +5 -7
  168. data/lib/active_record/tasks/sqlite_database_tasks.rb +2 -8
  169. data/lib/active_record/test_databases.rb +23 -0
  170. data/lib/active_record/test_fixtures.rb +225 -0
  171. data/lib/active_record/timestamp.rb +39 -25
  172. data/lib/active_record/touch_later.rb +4 -2
  173. data/lib/active_record/transactions.rb +56 -65
  174. data/lib/active_record/translation.rb +1 -1
  175. data/lib/active_record/type.rb +3 -4
  176. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  177. data/lib/active_record/type_caster/connection.rb +15 -14
  178. data/lib/active_record/type_caster/map.rb +1 -4
  179. data/lib/active_record/validations.rb +1 -0
  180. data/lib/active_record/validations/uniqueness.rb +15 -27
  181. data/lib/arel.rb +58 -0
  182. data/lib/arel/alias_predication.rb +9 -0
  183. data/lib/arel/attributes.rb +22 -0
  184. data/lib/arel/attributes/attribute.rb +37 -0
  185. data/lib/arel/collectors/bind.rb +24 -0
  186. data/lib/arel/collectors/composite.rb +31 -0
  187. data/lib/arel/collectors/plain_string.rb +20 -0
  188. data/lib/arel/collectors/sql_string.rb +20 -0
  189. data/lib/arel/collectors/substitute_binds.rb +28 -0
  190. data/lib/arel/crud.rb +42 -0
  191. data/lib/arel/delete_manager.rb +18 -0
  192. data/lib/arel/errors.rb +9 -0
  193. data/lib/arel/expressions.rb +29 -0
  194. data/lib/arel/factory_methods.rb +49 -0
  195. data/lib/arel/insert_manager.rb +49 -0
  196. data/lib/arel/math.rb +45 -0
  197. data/lib/arel/nodes.rb +68 -0
  198. data/lib/arel/nodes/and.rb +32 -0
  199. data/lib/arel/nodes/ascending.rb +23 -0
  200. data/lib/arel/nodes/binary.rb +52 -0
  201. data/lib/arel/nodes/bind_param.rb +36 -0
  202. data/lib/arel/nodes/case.rb +55 -0
  203. data/lib/arel/nodes/casted.rb +50 -0
  204. data/lib/arel/nodes/comment.rb +29 -0
  205. data/lib/arel/nodes/count.rb +12 -0
  206. data/lib/arel/nodes/delete_statement.rb +45 -0
  207. data/lib/arel/nodes/descending.rb +23 -0
  208. data/lib/arel/nodes/equality.rb +18 -0
  209. data/lib/arel/nodes/extract.rb +24 -0
  210. data/lib/arel/nodes/false.rb +16 -0
  211. data/lib/arel/nodes/full_outer_join.rb +8 -0
  212. data/lib/arel/nodes/function.rb +44 -0
  213. data/lib/arel/nodes/grouping.rb +8 -0
  214. data/lib/arel/nodes/in.rb +8 -0
  215. data/lib/arel/nodes/infix_operation.rb +80 -0
  216. data/lib/arel/nodes/inner_join.rb +8 -0
  217. data/lib/arel/nodes/insert_statement.rb +37 -0
  218. data/lib/arel/nodes/join_source.rb +20 -0
  219. data/lib/arel/nodes/matches.rb +18 -0
  220. data/lib/arel/nodes/named_function.rb +23 -0
  221. data/lib/arel/nodes/node.rb +50 -0
  222. data/lib/arel/nodes/node_expression.rb +13 -0
  223. data/lib/arel/nodes/outer_join.rb +8 -0
  224. data/lib/arel/nodes/over.rb +15 -0
  225. data/lib/arel/nodes/regexp.rb +16 -0
  226. data/lib/arel/nodes/right_outer_join.rb +8 -0
  227. data/lib/arel/nodes/select_core.rb +67 -0
  228. data/lib/arel/nodes/select_statement.rb +41 -0
  229. data/lib/arel/nodes/sql_literal.rb +16 -0
  230. data/lib/arel/nodes/string_join.rb +11 -0
  231. data/lib/arel/nodes/table_alias.rb +27 -0
  232. data/lib/arel/nodes/terminal.rb +16 -0
  233. data/lib/arel/nodes/true.rb +16 -0
  234. data/lib/arel/nodes/unary.rb +45 -0
  235. data/lib/arel/nodes/unary_operation.rb +20 -0
  236. data/lib/arel/nodes/unqualified_column.rb +22 -0
  237. data/lib/arel/nodes/update_statement.rb +41 -0
  238. data/lib/arel/nodes/values_list.rb +9 -0
  239. data/lib/arel/nodes/window.rb +126 -0
  240. data/lib/arel/nodes/with.rb +11 -0
  241. data/lib/arel/order_predications.rb +13 -0
  242. data/lib/arel/predications.rb +257 -0
  243. data/lib/arel/select_manager.rb +271 -0
  244. data/lib/arel/table.rb +110 -0
  245. data/lib/arel/tree_manager.rb +72 -0
  246. data/lib/arel/update_manager.rb +34 -0
  247. data/lib/arel/visitors.rb +20 -0
  248. data/lib/arel/visitors/depth_first.rb +204 -0
  249. data/lib/arel/visitors/dot.rb +297 -0
  250. data/lib/arel/visitors/ibm_db.rb +34 -0
  251. data/lib/arel/visitors/informix.rb +62 -0
  252. data/lib/arel/visitors/mssql.rb +157 -0
  253. data/lib/arel/visitors/mysql.rb +83 -0
  254. data/lib/arel/visitors/oracle.rb +159 -0
  255. data/lib/arel/visitors/oracle12.rb +66 -0
  256. data/lib/arel/visitors/postgresql.rb +110 -0
  257. data/lib/arel/visitors/sqlite.rb +39 -0
  258. data/lib/arel/visitors/to_sql.rb +889 -0
  259. data/lib/arel/visitors/visitor.rb +46 -0
  260. data/lib/arel/visitors/where_sql.rb +23 -0
  261. data/lib/arel/window_predications.rb +9 -0
  262. data/lib/rails/generators/active_record/migration.rb +14 -1
  263. data/lib/rails/generators/active_record/migration/migration_generator.rb +2 -5
  264. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +1 -1
  265. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +4 -2
  266. data/lib/rails/generators/active_record/model/model_generator.rb +1 -0
  267. data/lib/rails/generators/active_record/model/templates/model.rb.tt +10 -1
  268. metadata +111 -26
  269. data/lib/active_record/collection_cache_key.rb +0 -53
@@ -3,6 +3,8 @@
3
3
  module ActiveRecord
4
4
  module Tasks # :nodoc:
5
5
  class MySQLDatabaseTasks # :nodoc:
6
+ ER_DB_CREATE_EXISTS = 1007
7
+
6
8
  delegate :connection, :establish_connection, to: ActiveRecord::Base
7
9
 
8
10
  def initialize(configuration)
@@ -14,7 +16,7 @@ module ActiveRecord
14
16
  connection.create_database configuration["database"], creation_options
15
17
  establish_connection configuration
16
18
  rescue ActiveRecord::StatementInvalid => error
17
- if error.message.include?("database exists")
19
+ if connection.error_number(error.cause) == ER_DB_CREATE_EXISTS
18
20
  raise DatabaseAlreadyExists
19
21
  else
20
22
  raise
@@ -68,9 +70,7 @@ module ActiveRecord
68
70
 
69
71
  private
70
72
 
71
- def configuration
72
- @configuration
73
- end
73
+ attr_reader :configuration
74
74
 
75
75
  def configuration_without_database
76
76
  configuration.merge("database" => nil)
@@ -106,7 +106,7 @@ module ActiveRecord
106
106
  end
107
107
 
108
108
  def run_cmd_error(cmd, args, action)
109
- msg = "failed to execute: `#{cmd}`\n".dup
109
+ msg = +"failed to execute: `#{cmd}`\n"
110
110
  msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
111
111
  msg
112
112
  end
@@ -6,8 +6,8 @@ module ActiveRecord
6
6
  module Tasks # :nodoc:
7
7
  class PostgreSQLDatabaseTasks # :nodoc:
8
8
  DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
9
- ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
10
- SQL_COMMENT_BEGIN = "--".freeze
9
+ ON_ERROR_STOP_1 = "ON_ERROR_STOP=1"
10
+ SQL_COMMENT_BEGIN = "--"
11
11
 
12
12
  delegate :connection, :establish_connection, :clear_active_connections!,
13
13
  to: ActiveRecord::Base
@@ -82,7 +82,7 @@ module ActiveRecord
82
82
 
83
83
  def structure_load(filename, extra_flags)
84
84
  set_psql_env
85
- args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename]
85
+ args = ["-v", ON_ERROR_STOP_1, "-q", "-X", "-f", filename]
86
86
  args.concat(Array(extra_flags)) if extra_flags
87
87
  args << configuration["database"]
88
88
  run_cmd("psql", args, "loading")
@@ -90,9 +90,7 @@ module ActiveRecord
90
90
 
91
91
  private
92
92
 
93
- def configuration
94
- @configuration
95
- end
93
+ attr_reader :configuration
96
94
 
97
95
  def encoding
98
96
  configuration["encoding"] || DEFAULT_ENCODING
@@ -117,7 +115,7 @@ module ActiveRecord
117
115
  end
118
116
 
119
117
  def run_cmd_error(cmd, args, action)
120
- msg = "failed to execute:\n".dup
118
+ msg = +"failed to execute:\n"
121
119
  msg << "#{cmd} #{args.join(' ')}\n\n"
122
120
  msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
123
121
  msg
@@ -60,20 +60,14 @@ module ActiveRecord
60
60
 
61
61
  private
62
62
 
63
- def configuration
64
- @configuration
65
- end
66
-
67
- def root
68
- @root
69
- end
63
+ attr_reader :configuration, :root
70
64
 
71
65
  def run_cmd(cmd, args, out)
72
66
  fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
73
67
  end
74
68
 
75
69
  def run_cmd_error(cmd, args)
76
- msg = "failed to execute:\n".dup
70
+ msg = +"failed to execute:\n"
77
71
  msg << "#{cmd} #{args.join(' ')}\n\n"
78
72
  msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
79
73
  msg
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/testing/parallelization"
4
+
5
+ module ActiveRecord
6
+ module TestDatabases # :nodoc:
7
+ ActiveSupport::Testing::Parallelization.after_fork_hook do |i|
8
+ create_and_load_schema(i, env_name: Rails.env)
9
+ end
10
+
11
+ def self.create_and_load_schema(i, env_name:)
12
+ old, ENV["VERBOSE"] = ENV["VERBOSE"], "false"
13
+
14
+ ActiveRecord::Base.configurations.configs_for(env_name: env_name).each do |db_config|
15
+ db_config.config["database"] += "-#{i}"
16
+ ActiveRecord::Tasks::DatabaseTasks.reconstruct_from_schema(db_config.config, ActiveRecord::Base.schema_format, nil, env_name, db_config.spec_name)
17
+ end
18
+ ensure
19
+ ActiveRecord::Base.establish_connection(Rails.env.to_sym)
20
+ ENV["VERBOSE"] = old
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,225 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module TestFixtures
5
+ extend ActiveSupport::Concern
6
+
7
+ def before_setup # :nodoc:
8
+ setup_fixtures
9
+ super
10
+ end
11
+
12
+ def after_teardown # :nodoc:
13
+ super
14
+ teardown_fixtures
15
+ end
16
+
17
+ included do
18
+ class_attribute :fixture_path, instance_writer: false
19
+ class_attribute :fixture_table_names, default: []
20
+ class_attribute :fixture_class_names, default: {}
21
+ class_attribute :use_transactional_tests, default: true
22
+ class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
23
+ class_attribute :pre_loaded_fixtures, default: false
24
+ class_attribute :config, default: ActiveRecord::Base
25
+ class_attribute :lock_threads, default: true
26
+ end
27
+
28
+ module ClassMethods
29
+ # Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
30
+ #
31
+ # Examples:
32
+ #
33
+ # set_fixture_class some_fixture: SomeModel,
34
+ # 'namespaced/fixture' => Another::Model
35
+ #
36
+ # The keys must be the fixture names, that coincide with the short paths to the fixture files.
37
+ def set_fixture_class(class_names = {})
38
+ self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
39
+ end
40
+
41
+ def fixtures(*fixture_set_names)
42
+ if fixture_set_names.first == :all
43
+ raise StandardError, "No fixture path found. Please set `#{self}.fixture_path`." if fixture_path.blank?
44
+ fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"].uniq
45
+ fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
46
+ else
47
+ fixture_set_names = fixture_set_names.flatten.map(&:to_s)
48
+ end
49
+
50
+ self.fixture_table_names |= fixture_set_names
51
+ setup_fixture_accessors(fixture_set_names)
52
+ end
53
+
54
+ def setup_fixture_accessors(fixture_set_names = nil)
55
+ fixture_set_names = Array(fixture_set_names || fixture_table_names)
56
+ methods = Module.new do
57
+ fixture_set_names.each do |fs_name|
58
+ fs_name = fs_name.to_s
59
+ accessor_name = fs_name.tr("/", "_").to_sym
60
+
61
+ define_method(accessor_name) do |*fixture_names|
62
+ force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
63
+ return_single_record = fixture_names.size == 1
64
+ fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
65
+
66
+ @fixture_cache[fs_name] ||= {}
67
+
68
+ instances = fixture_names.map do |f_name|
69
+ f_name = f_name.to_s if f_name.is_a?(Symbol)
70
+ @fixture_cache[fs_name].delete(f_name) if force_reload
71
+
72
+ if @loaded_fixtures[fs_name][f_name]
73
+ @fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
74
+ else
75
+ raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
76
+ end
77
+ end
78
+
79
+ return_single_record ? instances.first : instances
80
+ end
81
+ private accessor_name
82
+ end
83
+ end
84
+ include methods
85
+ end
86
+
87
+ def uses_transaction(*methods)
88
+ @uses_transaction = [] unless defined?(@uses_transaction)
89
+ @uses_transaction.concat methods.map(&:to_s)
90
+ end
91
+
92
+ def uses_transaction?(method)
93
+ @uses_transaction = [] unless defined?(@uses_transaction)
94
+ @uses_transaction.include?(method.to_s)
95
+ end
96
+ end
97
+
98
+ def run_in_transaction?
99
+ use_transactional_tests &&
100
+ !self.class.uses_transaction?(method_name)
101
+ end
102
+
103
+ def setup_fixtures(config = ActiveRecord::Base)
104
+ if pre_loaded_fixtures && !use_transactional_tests
105
+ raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
106
+ end
107
+
108
+ @fixture_cache = {}
109
+ @fixture_connections = []
110
+ @@already_loaded_fixtures ||= {}
111
+ @connection_subscriber = nil
112
+
113
+ # Load fixtures once and begin transaction.
114
+ if run_in_transaction?
115
+ if @@already_loaded_fixtures[self.class]
116
+ @loaded_fixtures = @@already_loaded_fixtures[self.class]
117
+ else
118
+ @loaded_fixtures = load_fixtures(config)
119
+ @@already_loaded_fixtures[self.class] = @loaded_fixtures
120
+ end
121
+
122
+ # Begin transactions for connections already established
123
+ @fixture_connections = enlist_fixture_connections
124
+ @fixture_connections.each do |connection|
125
+ connection.begin_transaction joinable: false, _lazy: false
126
+ connection.pool.lock_thread = true if lock_threads
127
+ end
128
+
129
+ # When connections are established in the future, begin a transaction too
130
+ @connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
131
+ spec_name = payload[:spec_name] if payload.key?(:spec_name)
132
+ setup_shared_connection_pool
133
+
134
+ if spec_name
135
+ begin
136
+ connection = ActiveRecord::Base.connection_handler.retrieve_connection(spec_name)
137
+ rescue ConnectionNotEstablished
138
+ connection = nil
139
+ end
140
+
141
+ if connection && !@fixture_connections.include?(connection)
142
+ connection.begin_transaction joinable: false, _lazy: false
143
+ connection.pool.lock_thread = true if lock_threads
144
+ @fixture_connections << connection
145
+ end
146
+ end
147
+ end
148
+
149
+ # Load fixtures for every test.
150
+ else
151
+ ActiveRecord::FixtureSet.reset_cache
152
+ @@already_loaded_fixtures[self.class] = nil
153
+ @loaded_fixtures = load_fixtures(config)
154
+ end
155
+
156
+ # Instantiate fixtures for every test if requested.
157
+ instantiate_fixtures if use_instantiated_fixtures
158
+ end
159
+
160
+ def teardown_fixtures
161
+ # Rollback changes if a transaction is active.
162
+ if run_in_transaction?
163
+ ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
164
+ @fixture_connections.each do |connection|
165
+ connection.rollback_transaction if connection.transaction_open?
166
+ connection.pool.lock_thread = false
167
+ end
168
+ @fixture_connections.clear
169
+ else
170
+ ActiveRecord::FixtureSet.reset_cache
171
+ end
172
+
173
+ ActiveRecord::Base.clear_active_connections!
174
+ end
175
+
176
+ def enlist_fixture_connections
177
+ setup_shared_connection_pool
178
+
179
+ ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
180
+ end
181
+
182
+ private
183
+
184
+ # Shares the writing connection pool with connections on
185
+ # other handlers.
186
+ #
187
+ # In an application with a primary and replica the test fixtures
188
+ # need to share a connection pool so that the reading connection
189
+ # can see data in the open transaction on the writing connection.
190
+ def setup_shared_connection_pool
191
+ writing_handler = ActiveRecord::Base.connection_handler
192
+
193
+ ActiveRecord::Base.connection_handlers.values.each do |handler|
194
+ if handler != writing_handler
195
+ handler.connection_pool_list.each do |pool|
196
+ name = pool.spec.name
197
+ writing_connection = writing_handler.retrieve_connection_pool(name)
198
+ handler.send(:owner_to_pool)[name] = writing_connection
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ def load_fixtures(config)
205
+ fixtures = ActiveRecord::FixtureSet.create_fixtures(fixture_path, fixture_table_names, fixture_class_names, config)
206
+ Hash[fixtures.map { |f| [f.name, f] }]
207
+ end
208
+
209
+ def instantiate_fixtures
210
+ if pre_loaded_fixtures
211
+ raise RuntimeError, "Load fixtures before instantiating them." if ActiveRecord::FixtureSet.all_loaded_fixtures.empty?
212
+ ActiveRecord::FixtureSet.instantiate_all_loaded_fixtures(self, load_instances?)
213
+ else
214
+ raise RuntimeError, "Load fixtures before instantiating them." if @loaded_fixtures.nil?
215
+ @loaded_fixtures.each_value do |fixture_set|
216
+ ActiveRecord::FixtureSet.instantiate_fixtures(self, fixture_set, load_instances?)
217
+ end
218
+ end
219
+ end
220
+
221
+ def load_instances?
222
+ use_instantiated_fixtures != :no_instances
223
+ end
224
+ end
225
+ end
@@ -56,23 +56,29 @@ module ActiveRecord
56
56
  def touch_attributes_with_time(*names, time: nil)
57
57
  attribute_names = timestamp_attributes_for_update_in_model
58
58
  attribute_names |= names.map(&:to_s)
59
- time ||= current_time_from_proper_timezone
60
- attribute_names.each_with_object({}) { |attr_name, result| result[attr_name] = time }
59
+ attribute_names.index_with(time || current_time_from_proper_timezone)
61
60
  end
62
61
 
63
- private
64
- def timestamp_attributes_for_create_in_model
65
- timestamp_attributes_for_create.select { |c| column_names.include?(c) }
66
- end
62
+ def timestamp_attributes_for_create_in_model
63
+ @timestamp_attributes_for_create_in_model ||=
64
+ (timestamp_attributes_for_create & column_names).freeze
65
+ end
67
66
 
68
- def timestamp_attributes_for_update_in_model
69
- timestamp_attributes_for_update.select { |c| column_names.include?(c) }
70
- end
67
+ def timestamp_attributes_for_update_in_model
68
+ @timestamp_attributes_for_update_in_model ||=
69
+ (timestamp_attributes_for_update & column_names).freeze
70
+ end
71
71
 
72
- def all_timestamp_attributes_in_model
73
- timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
74
- end
72
+ def all_timestamp_attributes_in_model
73
+ @all_timestamp_attributes_in_model ||=
74
+ (timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
75
+ end
75
76
 
77
+ def current_time_from_proper_timezone
78
+ default_timezone == :utc ? Time.now.utc : Time.now
79
+ end
80
+
81
+ private
76
82
  def timestamp_attributes_for_create
77
83
  ["created_at", "created_on"]
78
84
  end
@@ -81,8 +87,11 @@ module ActiveRecord
81
87
  ["updated_at", "updated_on"]
82
88
  end
83
89
 
84
- def current_time_from_proper_timezone
85
- default_timezone == :utc ? Time.now.utc : Time.now
90
+ def reload_schema_from_cache
91
+ @timestamp_attributes_for_create_in_model = nil
92
+ @timestamp_attributes_for_update_in_model = nil
93
+ @all_timestamp_attributes_in_model = nil
94
+ super
86
95
  end
87
96
  end
88
97
 
@@ -102,8 +111,8 @@ module ActiveRecord
102
111
  super
103
112
  end
104
113
 
105
- def _update_record(*args, touch: true, **options)
106
- if touch && should_record_timestamps?
114
+ def _update_record
115
+ if @_touch_record && should_record_timestamps?
107
116
  current_time = current_time_from_proper_timezone
108
117
 
109
118
  timestamp_attributes_for_update_in_model.each do |column|
@@ -111,7 +120,13 @@ module ActiveRecord
111
120
  _write_attribute(column, current_time)
112
121
  end
113
122
  end
114
- super(*args)
123
+
124
+ super
125
+ end
126
+
127
+ def create_or_update(touch: true, **)
128
+ @_touch_record = touch
129
+ super
115
130
  end
116
131
 
117
132
  def should_record_timestamps?
@@ -119,26 +134,25 @@ module ActiveRecord
119
134
  end
120
135
 
121
136
  def timestamp_attributes_for_create_in_model
122
- self.class.send(:timestamp_attributes_for_create_in_model)
137
+ self.class.timestamp_attributes_for_create_in_model
123
138
  end
124
139
 
125
140
  def timestamp_attributes_for_update_in_model
126
- self.class.send(:timestamp_attributes_for_update_in_model)
141
+ self.class.timestamp_attributes_for_update_in_model
127
142
  end
128
143
 
129
144
  def all_timestamp_attributes_in_model
130
- self.class.send(:all_timestamp_attributes_in_model)
145
+ self.class.all_timestamp_attributes_in_model
131
146
  end
132
147
 
133
148
  def current_time_from_proper_timezone
134
- self.class.send(:current_time_from_proper_timezone)
149
+ self.class.current_time_from_proper_timezone
135
150
  end
136
151
 
137
- def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
138
- timestamp_names
139
- .map { |attr| self[attr] }
152
+ def max_updated_column_timestamp
153
+ timestamp_attributes_for_update_in_model
154
+ .map { |attr| self[attr]&.to_time }
140
155
  .compact
141
- .map(&:to_time)
142
156
  .max
143
157
  end
144
158