activerecord 7.0.8.6 → 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 -1939
  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 +56 -14
  278. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  279. data/lib/active_record/null_relation.rb +0 -63
@@ -4,7 +4,7 @@ require "active_support/inflector"
4
4
  require "active_support/core_ext/hash/indifferent_access"
5
5
 
6
6
  module ActiveRecord
7
- # == Single table inheritance
7
+ # = Single table inheritance
8
8
  #
9
9
  # Active Record allows inheritance by storing the name of the class in a column that by
10
10
  # default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
@@ -32,8 +32,9 @@ module ActiveRecord
32
32
  # be triggered. In that case, it'll work just like normal subclasses with no special magic
33
33
  # for differentiating between them or reloading the right type with find.
34
34
  #
35
- # Note, all the attributes for all the cases are kept in the same table. Read more:
36
- # https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
35
+ # Note, all the attributes for all the cases are kept in the same table.
36
+ # Read more:
37
+ # * https://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
37
38
  #
38
39
  module Inheritance
39
40
  extend ActiveSupport::Concern
@@ -93,14 +94,24 @@ module ActiveRecord
93
94
  :true == (@finder_needs_type_condition ||= descends_from_active_record? ? :false : :true)
94
95
  end
95
96
 
96
- # Returns the class descending directly from ActiveRecord::Base, or
97
- # an abstract class, if any, in the inheritance hierarchy.
97
+ # Returns the first class in the inheritance hierarchy that descends from either an
98
+ # abstract class or from <tt>ActiveRecord::Base</tt>.
98
99
  #
99
- # If A extends ActiveRecord::Base, A.base_class will return A. If B descends from A
100
- # through some arbitrarily deep hierarchy, B.base_class will return A.
100
+ # Consider the following behaviour:
101
101
  #
102
- # If B < A and C < B and if A is an abstract_class then both B.base_class
103
- # and C.base_class would return B as the answer since A is an abstract_class.
102
+ # class ApplicationRecord < ActiveRecord::Base
103
+ # self.abstract_class = true
104
+ # end
105
+ # class Shape < ApplicationRecord
106
+ # self.abstract_class = true
107
+ # end
108
+ # Polygon = Class.new(Shape)
109
+ # Square = Class.new(Polygon)
110
+ #
111
+ # ApplicationRecord.base_class # => ApplicationRecord
112
+ # Shape.base_class # => Shape
113
+ # Polygon.base_class # => Polygon
114
+ # Square.base_class # => Polygon
104
115
  attr_reader :base_class
105
116
 
106
117
  # Returns whether the class is a base class.
@@ -116,7 +127,7 @@ module ActiveRecord
116
127
  # true.
117
128
  # +ApplicationRecord+, for example, is generated as an abstract class.
118
129
  #
119
- # Consider the following default behaviour:
130
+ # Consider the following default behavior:
120
131
  #
121
132
  # Shape = Class.new(ActiveRecord::Base)
122
133
  # Polygon = Class.new(Shape)
@@ -154,7 +165,7 @@ module ActiveRecord
154
165
 
155
166
  # Returns whether this class is an abstract class or not.
156
167
  def abstract_class?
157
- defined?(@abstract_class) && @abstract_class == true
168
+ @abstract_class == true
158
169
  end
159
170
 
160
171
  # Sets the application record class for Active Record
@@ -191,7 +202,9 @@ module ActiveRecord
191
202
  "The single-table inheritance mechanism failed to locate the subclass: '#{type_name}'. " \
192
203
  "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " \
193
204
  "Please rename this column if you didn't intend it to be used for storing the inheritance class " \
194
- "or overwrite #{name}.inheritance_column to use another column for that information."
205
+ "or overwrite #{name}.inheritance_column to use another column for that information. " \
206
+ "If you wish to disable single-table inheritance for #{name} set " \
207
+ "#{name}.inheritance_column to nil"
195
208
  end
196
209
 
197
210
  # Returns the value to be stored in the polymorphic type column for Polymorphic Associations.
@@ -210,12 +223,6 @@ module ActiveRecord
210
223
  end
211
224
  end
212
225
 
213
- def inherited(subclass)
214
- subclass.set_base_class
215
- subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
216
- super
217
- end
218
-
219
226
  def dup # :nodoc:
220
227
  # `initialize_dup` / `initialize_copy` don't work when defined
221
228
  # in the `singleton_class`.
@@ -277,6 +284,15 @@ module ActiveRecord
277
284
  end
278
285
 
279
286
  private
287
+ def inherited(subclass)
288
+ super
289
+ subclass.set_base_class
290
+ subclass.instance_variable_set(:@_type_candidates_cache, Concurrent::Map.new)
291
+ subclass.class_eval do
292
+ @finder_needs_type_condition = nil
293
+ end
294
+ end
295
+
280
296
  # Called by +instantiate+ to decide which class to use for a new
281
297
  # record instance. For single-table inheritance, we check the record
282
298
  # for a +type+ column and return the corresponding class.
@@ -7,34 +7,47 @@ module ActiveRecord
7
7
  attr_reader :model, :connection, :inserts, :keys
8
8
  attr_reader :on_duplicate, :update_only, :returning, :unique_by, :update_sql
9
9
 
10
- def initialize(model, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
11
- raise ArgumentError, "Empty list of attributes passed" if inserts.blank?
10
+ class << self
11
+ def execute(relation, ...)
12
+ relation.model.with_connection do |c|
13
+ new(relation, c, ...).execute
14
+ end
15
+ end
16
+ end
12
17
 
13
- @model, @connection, @inserts, @keys = model, model.connection, inserts, inserts.first.keys.map(&:to_s)
18
+ def initialize(relation, connection, inserts, on_duplicate:, update_only: nil, returning: nil, unique_by: nil, record_timestamps: nil)
19
+ @relation = relation
20
+ @model, @connection, @inserts = relation.model, connection, inserts.map(&:stringify_keys)
14
21
  @on_duplicate, @update_only, @returning, @unique_by = on_duplicate, update_only, returning, unique_by
15
22
  @record_timestamps = record_timestamps.nil? ? model.record_timestamps : record_timestamps
16
23
 
17
24
  disallow_raw_sql!(on_duplicate)
18
25
  disallow_raw_sql!(returning)
19
26
 
20
- configure_on_duplicate_update_logic
21
-
22
- if model.scope_attributes?
23
- @scope_attributes = model.scope_attributes
24
- @keys |= @scope_attributes.keys
27
+ if @inserts.empty?
28
+ @keys = []
29
+ else
30
+ resolve_sti
31
+ resolve_attribute_aliases
32
+ @keys = @inserts.first.keys
25
33
  end
34
+
35
+ @scope_attributes = relation.scope_for_create.except(@model.inheritance_column)
36
+ @keys |= @scope_attributes.keys
26
37
  @keys = @keys.to_set
27
38
 
28
39
  @returning = (connection.supports_insert_returning? ? primary_keys : false) if @returning.nil?
29
40
  @returning = false if @returning == []
30
41
 
31
- @unique_by = find_unique_index_for(unique_by)
32
- @on_duplicate = :skip if @on_duplicate == :update && updatable_columns.empty?
42
+ @unique_by = find_unique_index_for(@unique_by)
33
43
 
44
+ configure_on_duplicate_update_logic
34
45
  ensure_valid_options_for_connection!
35
46
  end
36
47
 
37
48
  def execute
49
+ return ActiveRecord::Result.empty if inserts.empty?
50
+
38
51
  message = +"#{model} "
39
52
  message << "Bulk " if inserts.many?
40
53
  message << (on_duplicate == :update ? "Upsert" : "Insert")
@@ -46,10 +59,9 @@ module ActiveRecord
46
59
  end
47
60
 
48
61
  def primary_keys
49
- Array(connection.schema_cache.primary_keys(model.table_name))
62
+ Array(@model.schema_cache.primary_keys(model.table_name))
50
63
  end
51
64
 
52
-
53
65
  def skip_duplicates?
54
66
  on_duplicate == :skip
55
67
  end
@@ -61,7 +73,7 @@ module ActiveRecord
61
73
  def map_key_with_value
62
74
  inserts.map do |attributes|
63
75
  attributes = attributes.stringify_keys
64
- attributes.merge!(scope_attributes) if scope_attributes
76
+ attributes.merge!(@scope_attributes)
65
77
  attributes.reverse_merge!(timestamps_for_create) if record_timestamps?
66
78
 
67
79
  verify_attributes(attributes)
@@ -76,7 +88,7 @@ module ActiveRecord
76
88
  @record_timestamps
77
89
  end
78
90
 
79
- # TODO: Consider remaining this method, as it only conditionally extends keys, not always
91
+ # TODO: Consider renaming this method, as it only conditionally extends keys, not always
80
92
  def keys_including_timestamps
81
93
  @keys_including_timestamps ||= if record_timestamps?
82
94
  keys + model.all_timestamp_attributes_in_model
@@ -86,7 +98,33 @@ module ActiveRecord
86
98
  end
87
99
 
88
100
  private
89
- attr_reader :scope_attributes
101
+ def has_attribute_aliases?(attributes)
102
+ attributes.keys.any? { |attribute| model.attribute_alias?(attribute) }
103
+ end
104
+
105
+ def resolve_sti
106
+ return if model.descends_from_active_record?
107
+
108
+ sti_type = model.sti_name
109
+ @inserts = @inserts.map do |insert|
110
+ insert.reverse_merge(model.inheritance_column.to_s => sti_type)
111
+ end
112
+ end
113
+
114
+ def resolve_attribute_aliases
115
+ return unless has_attribute_aliases?(@inserts.first)
116
+
117
+ @inserts = @inserts.map do |insert|
118
+ insert.transform_keys { |attribute| resolve_attribute_alias(attribute) }
119
+ end
120
+
121
+ @update_only = Array(@update_only).map { |attribute| resolve_attribute_alias(attribute) } if @update_only
122
+ @unique_by = Array(@unique_by).map { |attribute| resolve_attribute_alias(attribute) } if @unique_by
123
+ end
124
+
125
+ def resolve_attribute_alias(attribute)
126
+ model.attribute_alias(attribute) || attribute
127
+ end
90
128
 
91
129
  def configure_on_duplicate_update_logic
92
130
  if custom_update_sql_provided? && update_only.present?
@@ -99,6 +137,8 @@ module ActiveRecord
99
137
  elsif custom_update_sql_provided?
100
138
  @update_sql = on_duplicate
101
139
  @on_duplicate = :update
140
+ elsif @on_duplicate == :update && updatable_columns.empty?
141
+ @on_duplicate = :skip
102
142
  end
103
143
  end
104
144
 
@@ -115,8 +155,9 @@ module ActiveRecord
115
155
 
116
156
  name_or_columns = unique_by || model.primary_key
117
157
  match = Array(name_or_columns).map(&:to_s)
158
+ sorted_match = match.sort
118
159
 
119
- if index = unique_indexes.find { |i| match.include?(i.name) || i.columns == match }
160
+ if index = unique_indexes.find { |i| match.include?(i.name) || Array(i.columns).sort == sorted_match }
120
161
  index
121
162
  elsif match == primary_keys
122
163
  unique_by.nil? ? nil : ActiveRecord::ConnectionAdapters::IndexDefinition.new(model.table_name, "#{model.table_name}_primary_key", true, match)
@@ -126,10 +167,9 @@ module ActiveRecord
126
167
  end
127
168
 
128
169
  def unique_indexes
129
- connection.schema_cache.indexes(model.table_name).select(&:unique)
170
+ @model.schema_cache.indexes(model.table_name).select(&:unique)
130
171
  end
131
172
 
132
-
133
173
  def ensure_valid_options_for_connection!
134
174
  if returning && !connection.supports_insert_returning?
135
175
  raise ArgumentError, "#{connection.class} does not support :returning"
@@ -155,7 +195,7 @@ module ActiveRecord
155
195
 
156
196
 
157
197
  def readonly_columns
158
- primary_keys + model.readonly_attributes.to_a
198
+ primary_keys + model.readonly_attributes
159
199
  end
160
200
 
161
201
  def unique_by_columns
@@ -212,7 +252,13 @@ module ActiveRecord
212
252
  if insert_all.returning.is_a?(String)
213
253
  insert_all.returning
214
254
  else
215
- format_columns(insert_all.returning)
255
+ Array(insert_all.returning).map do |attribute|
256
+ if model.attribute_alias?(attribute)
257
+ "#{quote_column(model.attribute_alias(attribute))} AS #{quote_column(attribute)}"
258
+ else
259
+ quote_column(attribute)
260
+ end
261
+ end.join(",")
216
262
  end
217
263
  end
218
264
 
@@ -258,7 +304,7 @@ module ActiveRecord
258
304
  end
259
305
 
260
306
  def extract_types_from_columns_on(table_name, keys:)
261
- columns = connection.schema_cache.columns_hash(table_name)
307
+ columns = @model.schema_cache.columns_hash(table_name)
262
308
 
263
309
  unknown_column = (keys - columns.keys).first
264
310
  raise UnknownAttributeError.new(model.new, unknown_column) if unknown_column
@@ -271,7 +317,11 @@ module ActiveRecord
271
317
  end
272
318
 
273
319
  def quote_columns(columns)
274
- columns.map(&connection.method(:quote_column_name))
320
+ columns.map { |column| quote_column(column) }
321
+ end
322
+
323
+ def quote_column(column)
324
+ connection.quote_column_name(column)
275
325
  end
276
326
  end
277
327
  end
@@ -10,7 +10,7 @@ module ActiveRecord
10
10
  ##
11
11
  # :singleton-method:
12
12
  # Indicates the format used to generate the timestamp in the cache key, if
13
- # versioning is off. Accepts any of the symbols in <tt>Time::DATE_FORMATS</tt>.
13
+ # versioning is off. Accepts any of the symbols in +Time::DATE_FORMATS+.
14
14
  #
15
15
  # This is +:usec+, by default.
16
16
  class_attribute :cache_timestamp_format, instance_writer: false, default: :usec
@@ -20,7 +20,7 @@ module ActiveRecord
20
20
  # Indicates whether to use a stable #cache_key method that is accompanied
21
21
  # by a changing version in the #cache_version method.
22
22
  #
23
- # This is +true+, by default on Rails 5.2 and above.
23
+ # This is +true+, by default on \Rails 5.2 and above.
24
24
  class_attribute :cache_versioning, instance_writer: false, default: false
25
25
 
26
26
  ##
@@ -28,7 +28,7 @@ module ActiveRecord
28
28
  # Indicates whether to use a stable #cache_key method that is accompanied
29
29
  # by a changing version in the #cache_version method on collections.
30
30
  #
31
- # This is +false+, by default until Rails 6.1.
31
+ # This is +false+, by default until \Rails 6.1.
32
32
  class_attribute :collection_cache_versioning, instance_writer: false, default: false
33
33
  end
34
34
 
@@ -55,8 +55,8 @@ module ActiveRecord
55
55
  # user = User.find_by(name: 'Phusion')
56
56
  # user_path(user) # => "/users/Phusion"
57
57
  def to_param
58
- # We can't use alias_method here, because method 'id' optimizes itself on the fly.
59
- id && id.to_s # Be sure to stringify the id for routes
58
+ return unless id
59
+ Array(id).join(self.class.param_delimiter)
60
60
  end
61
61
 
62
62
  # Returns a stable cache key that can be used to identify this record.
@@ -64,7 +64,7 @@ module ActiveRecord
64
64
  # Product.new.cache_key # => "products/new"
65
65
  # Product.find(5).cache_key # => "products/5"
66
66
  #
67
- # If ActiveRecord::Base.cache_versioning is turned off, as it was in Rails 5.1 and earlier,
67
+ # If ActiveRecord::Base.cache_versioning is turned off, as it was in \Rails 5.1 and earlier,
68
68
  # the cache key will also include a version.
69
69
  #
70
70
  # Product.cache_versioning = false
@@ -106,7 +106,7 @@ module ActiveRecord
106
106
  timestamp.utc.to_fs(cache_timestamp_format)
107
107
  end
108
108
  elsif self.class.has_attribute?("updated_at")
109
- raise ActiveModel::MissingAttributeError, "missing attribute: updated_at"
109
+ raise ActiveModel::MissingAttributeError, "missing attribute 'updated_at' for #{self.class}"
110
110
  end
111
111
  end
112
112
 
@@ -178,7 +178,10 @@ module ActiveRecord
178
178
  def can_use_fast_cache_version?(timestamp)
179
179
  timestamp.is_a?(String) &&
180
180
  cache_timestamp_format == :usec &&
181
- ActiveRecord.default_timezone == :utc &&
181
+ # FIXME: checking out a connection for this is wasteful
182
+ # we should store/cache this information in the schema cache
183
+ # or similar.
184
+ self.class.with_connection(&:default_timezone) == :utc &&
182
185
  !updated_at_came_from_user?
183
186
  end
184
187
 
@@ -9,38 +9,83 @@ module ActiveRecord
9
9
  #
10
10
  # This is enabled by default. To disable this functionality set
11
11
  # `use_metadata_table` to false in your database configuration.
12
- class InternalMetadata < ActiveRecord::Base # :nodoc:
13
- self.record_timestamps = true
12
+ class InternalMetadata # :nodoc:
13
+ class NullInternalMetadata # :nodoc:
14
+ end
15
+
16
+ attr_reader :arel_table
17
+
18
+ def initialize(pool)
19
+ @pool = pool
20
+ @arel_table = Arel::Table.new(table_name)
21
+ end
22
+
23
+ def primary_key
24
+ "key"
25
+ end
14
26
 
15
- class << self
16
- def enabled?
17
- ActiveRecord::Base.connection.use_metadata_table?
27
+ def value_key
28
+ "value"
29
+ end
30
+
31
+ def table_name
32
+ "#{ActiveRecord::Base.table_name_prefix}#{ActiveRecord::Base.internal_metadata_table_name}#{ActiveRecord::Base.table_name_suffix}"
33
+ end
34
+
35
+ def enabled?
36
+ @pool.db_config.use_metadata_table?
37
+ end
38
+
39
+ def []=(key, value)
40
+ return unless enabled?
41
+
42
+ @pool.with_connection do |connection|
43
+ update_or_create_entry(connection, key, value)
18
44
  end
45
+ end
19
46
 
20
- def primary_key
21
- "key"
47
+ def [](key)
48
+ return unless enabled?
49
+
50
+ @pool.with_connection do |connection|
51
+ if entry = select_entry(connection, key)
52
+ entry[value_key]
53
+ end
22
54
  end
55
+ end
56
+
57
+ def delete_all_entries
58
+ dm = Arel::DeleteManager.new(arel_table)
23
59
 
24
- def table_name
25
- "#{table_name_prefix}#{internal_metadata_table_name}#{table_name_suffix}"
60
+ @pool.with_connection do |connection|
61
+ connection.delete(dm, "#{self.class} Destroy")
26
62
  end
63
+ end
27
64
 
28
- def []=(key, value)
29
- return unless enabled?
65
+ def count
66
+ sm = Arel::SelectManager.new(arel_table)
67
+ sm.project(*Arel::Nodes::Count.new([Arel.star]))
30
68
 
31
- find_or_initialize_by(key: key).update!(value: value)
69
+ @pool.with_connection do |connection|
70
+ connection.select_values(sm, "#{self.class} Count").first
32
71
  end
72
+ end
33
73
 
34
- def [](key)
35
- return unless enabled?
74
+ def create_table_and_set_flags(environment, schema_sha1 = nil)
75
+ return unless enabled?
36
76
 
37
- where(key: key).pick(:value)
77
+ @pool.with_connection do |connection|
78
+ create_table
79
+ update_or_create_entry(connection, :environment, environment)
80
+ update_or_create_entry(connection, :schema_sha1, schema_sha1) if schema_sha1
38
81
  end
82
+ end
39
83
 
40
- # Creates an internal metadata table with columns +key+ and +value+
41
- def create_table
42
- return unless enabled?
84
+ # Creates an internal metadata table with columns +key+ and +value+
85
+ def create_table
86
+ return unless enabled?
43
87
 
88
+ @pool.with_connection do |connection|
44
89
  unless connection.table_exists?(table_name)
45
90
  connection.create_table(table_name, id: false) do |t|
46
91
  t.string :key, **connection.internal_string_options_for_primary_key
@@ -49,12 +94,71 @@ module ActiveRecord
49
94
  end
50
95
  end
51
96
  end
97
+ end
52
98
 
53
- def drop_table
54
- return unless enabled?
99
+ def drop_table
100
+ return unless enabled?
55
101
 
102
+ @pool.with_connection do |connection|
56
103
  connection.drop_table table_name, if_exists: true
57
104
  end
58
105
  end
106
+
107
+ def table_exists?
108
+ @pool.schema_cache.data_source_exists?(table_name)
109
+ end
110
+
111
+ private
112
+ def update_or_create_entry(connection, key, value)
113
+ entry = select_entry(connection, key)
114
+
115
+ if entry
116
+ if entry[value_key] != value
117
+ update_entry(connection, key, value)
118
+ else
119
+ entry[value_key]
120
+ end
121
+ else
122
+ create_entry(connection, key, value)
123
+ end
124
+ end
125
+
126
+ def current_time(connection)
127
+ connection.default_timezone == :utc ? Time.now.utc : Time.now
128
+ end
129
+
130
+ def create_entry(connection, key, value)
131
+ im = Arel::InsertManager.new(arel_table)
132
+ im.insert [
133
+ [arel_table[primary_key], key],
134
+ [arel_table[value_key], value],
135
+ [arel_table[:created_at], current_time(connection)],
136
+ [arel_table[:updated_at], current_time(connection)]
137
+ ]
138
+
139
+ connection.insert(im, "#{self.class} Create", primary_key, key)
140
+ end
141
+
142
+ def update_entry(connection, key, new_value)
143
+ um = Arel::UpdateManager.new(arel_table)
144
+ um.set [
145
+ [arel_table[value_key], new_value],
146
+ [arel_table[:updated_at], current_time(connection)]
147
+ ]
148
+
149
+ um.where(arel_table[primary_key].eq(key))
150
+
151
+ connection.update(um, "#{self.class} Update")
152
+ end
153
+
154
+ def select_entry(connection, key)
155
+ sm = Arel::SelectManager.new(arel_table)
156
+ sm.project(Arel::Nodes::SqlLiteral.new("*", retryable: true))
157
+ sm.where(arel_table[primary_key].eq(Arel::Nodes::BindParam.new(key)))
158
+ sm.order(arel_table[primary_key].asc)
159
+ sm.limit = 1
160
+
161
+ connection.select_all(sm, "#{self.class} Load").first
162
+ end
59
163
  end
60
164
  end
@@ -14,7 +14,7 @@ module ActiveRecord
14
14
  # == Usage
15
15
  #
16
16
  # Active Record supports optimistic locking if the +lock_version+ field is present. Each update to the
17
- # record increments the +lock_version+ column and the locking facilities ensure that records instantiated twice
17
+ # record increments the integer column +lock_version+ and the locking facilities ensure that records instantiated twice
18
18
  # will let the last one saved raise a +StaleObjectError+ if the first was also updated. Example:
19
19
  #
20
20
  # p1 = Person.find(1)
@@ -182,14 +182,15 @@ module ActiveRecord
182
182
  super
183
183
  end
184
184
 
185
- def define_attribute(name, cast_type, **) # :nodoc:
186
- if lock_optimistically && name == locking_column
187
- cast_type = LockingType.new(cast_type)
185
+ private
186
+ def hook_attribute_type(name, cast_type)
187
+ if lock_optimistically && name == locking_column
188
+ cast_type = LockingType.new(cast_type)
189
+ end
190
+
191
+ super
188
192
  end
189
- super
190
- end
191
193
 
192
- private
193
194
  def inherited(base)
194
195
  super
195
196
  base.class_eval do
@@ -2,6 +2,8 @@
2
2
 
3
3
  module ActiveRecord
4
4
  module Locking
5
+ # = \Pessimistic \Locking
6
+ #
5
7
  # Locking::Pessimistic provides support for row-level locking using
6
8
  # SELECT ... FOR UPDATE and other lock types.
7
9
  #
@@ -71,6 +73,7 @@ module ActiveRecord
71
73
  Locking a record with unpersisted changes is not supported. Use
72
74
  `save` to persist the changes, or `reload` to discard them
73
75
  explicitly.
76
+ Changed attributes: #{changed.map(&:inspect).join(', ')}.
74
77
  MSG
75
78
  end
76
79
 
@@ -79,8 +82,8 @@ module ActiveRecord
79
82
  self
80
83
  end
81
84
 
82
- # Wraps the passed block in a transaction, locking the object
83
- # before yielding. You can pass the SQL locking clause
85
+ # Wraps the passed block in a transaction, reloading the object with a
86
+ # lock before yielding. You can pass the SQL locking clause
84
87
  # as an optional argument (see #lock!).
85
88
  #
86
89
  # You can also pass options like <tt>requires_new:</tt>, <tt>isolation:</tt>,