activerecord 7.0.0 → 7.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 (289) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +515 -1268
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +31 -31
  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 +28 -17
  20. data/lib/active_record/associations/collection_proxy.rb +36 -13
  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 +28 -18
  24. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  25. data/lib/active_record/associations/has_one_association.rb +10 -3
  26. data/lib/active_record/associations/join_dependency/join_association.rb +27 -25
  27. data/lib/active_record/associations/join_dependency.rb +18 -14
  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 +2 -4
  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 +378 -491
  36. data/lib/active_record/attribute_assignment.rb +1 -13
  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 +153 -70
  44. data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
  45. data/lib/active_record/attribute_methods/write.rb +6 -6
  46. data/lib/active_record/attribute_methods.rb +153 -40
  47. data/lib/active_record/attributes.rb +63 -48
  48. data/lib/active_record/autosave_association.rb +70 -38
  49. data/lib/active_record/base.rb +12 -8
  50. data/lib/active_record/callbacks.rb +16 -32
  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 -34
  54. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +124 -132
  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 +297 -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 +215 -63
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
  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 +163 -29
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +319 -135
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
  70. data/lib/active_record/connection_adapters/column.rb +9 -0
  71. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  72. data/lib/active_record/connection_adapters/mysql/database_statements.rb +27 -140
  73. data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
  74. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  75. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  76. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  77. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +45 -14
  78. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
  79. data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
  80. data/lib/active_record/connection_adapters/pool_config.rb +20 -10
  81. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  82. data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
  83. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +101 -48
  84. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  85. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  87. data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
  88. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
  89. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  90. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
  92. data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
  93. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  94. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +151 -2
  96. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  97. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
  98. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  99. data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
  100. data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
  101. data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
  102. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
  103. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
  104. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
  105. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
  106. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
  107. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
  108. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
  109. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  110. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
  111. data/lib/active_record/connection_adapters/trilogy_adapter.rb +229 -0
  112. data/lib/active_record/connection_adapters.rb +124 -1
  113. data/lib/active_record/connection_handling.rb +98 -106
  114. data/lib/active_record/core.rb +220 -177
  115. data/lib/active_record/counter_cache.rb +68 -34
  116. data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
  117. data/lib/active_record/database_configurations/database_config.rb +26 -5
  118. data/lib/active_record/database_configurations/hash_config.rb +52 -34
  119. data/lib/active_record/database_configurations/url_config.rb +37 -12
  120. data/lib/active_record/database_configurations.rb +88 -35
  121. data/lib/active_record/delegated_type.rb +40 -11
  122. data/lib/active_record/deprecator.rb +7 -0
  123. data/lib/active_record/destroy_association_async_job.rb +3 -1
  124. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  125. data/lib/active_record/dynamic_matchers.rb +2 -2
  126. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  127. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  128. data/lib/active_record/encryption/config.rb +25 -1
  129. data/lib/active_record/encryption/configurable.rb +13 -14
  130. data/lib/active_record/encryption/context.rb +10 -3
  131. data/lib/active_record/encryption/contexts.rb +8 -4
  132. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  133. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  134. data/lib/active_record/encryption/encryptable_record.rb +47 -25
  135. data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
  136. data/lib/active_record/encryption/encryptor.rb +25 -10
  137. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  138. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
  139. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  140. data/lib/active_record/encryption/key_generator.rb +12 -1
  141. data/lib/active_record/encryption/message.rb +1 -1
  142. data/lib/active_record/encryption/message_pack_message_serializer.rb +76 -0
  143. data/lib/active_record/encryption/message_serializer.rb +6 -0
  144. data/lib/active_record/encryption/null_encryptor.rb +4 -0
  145. data/lib/active_record/encryption/properties.rb +4 -4
  146. data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
  147. data/lib/active_record/encryption/scheme.rb +23 -22
  148. data/lib/active_record/encryption.rb +1 -0
  149. data/lib/active_record/enum.rb +131 -27
  150. data/lib/active_record/errors.rb +151 -31
  151. data/lib/active_record/explain.rb +21 -12
  152. data/lib/active_record/explain_subscriber.rb +1 -1
  153. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  154. data/lib/active_record/fixture_set/render_context.rb +2 -0
  155. data/lib/active_record/fixture_set/table_row.rb +29 -8
  156. data/lib/active_record/fixtures.rb +169 -99
  157. data/lib/active_record/future_result.rb +47 -8
  158. data/lib/active_record/gem_version.rb +3 -3
  159. data/lib/active_record/inheritance.rb +34 -18
  160. data/lib/active_record/insert_all.rb +72 -22
  161. data/lib/active_record/integration.rb +13 -10
  162. data/lib/active_record/internal_metadata.rb +124 -20
  163. data/lib/active_record/locking/optimistic.rb +39 -24
  164. data/lib/active_record/locking/pessimistic.rb +8 -5
  165. data/lib/active_record/log_subscriber.rb +28 -27
  166. data/lib/active_record/marshalling.rb +56 -0
  167. data/lib/active_record/message_pack.rb +124 -0
  168. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  169. data/lib/active_record/middleware/database_selector.rb +18 -13
  170. data/lib/active_record/middleware/shard_selector.rb +7 -5
  171. data/lib/active_record/migration/command_recorder.rb +110 -13
  172. data/lib/active_record/migration/compatibility.rb +174 -64
  173. data/lib/active_record/migration/default_strategy.rb +22 -0
  174. data/lib/active_record/migration/execution_strategy.rb +19 -0
  175. data/lib/active_record/migration/pending_migration_connection.rb +21 -0
  176. data/lib/active_record/migration.rb +292 -125
  177. data/lib/active_record/model_schema.rb +113 -112
  178. data/lib/active_record/nested_attributes.rb +35 -9
  179. data/lib/active_record/normalization.rb +163 -0
  180. data/lib/active_record/persistence.rb +177 -345
  181. data/lib/active_record/promise.rb +84 -0
  182. data/lib/active_record/query_cache.rb +19 -25
  183. data/lib/active_record/query_logs.rb +102 -51
  184. data/lib/active_record/query_logs_formatter.rb +41 -0
  185. data/lib/active_record/querying.rb +34 -9
  186. data/lib/active_record/railtie.rb +153 -100
  187. data/lib/active_record/railties/controller_runtime.rb +24 -10
  188. data/lib/active_record/railties/databases.rake +148 -152
  189. data/lib/active_record/railties/job_runtime.rb +23 -0
  190. data/lib/active_record/readonly_attributes.rb +32 -5
  191. data/lib/active_record/reflection.rb +278 -69
  192. data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
  193. data/lib/active_record/relation/batches.rb +198 -63
  194. data/lib/active_record/relation/calculations.rb +293 -108
  195. data/lib/active_record/relation/delegation.rb +31 -20
  196. data/lib/active_record/relation/finder_methods.rb +93 -18
  197. data/lib/active_record/relation/merger.rb +6 -6
  198. data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
  199. data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
  200. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
  201. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  202. data/lib/active_record/relation/predicate_builder.rb +28 -16
  203. data/lib/active_record/relation/query_attribute.rb +25 -1
  204. data/lib/active_record/relation/query_methods.rb +625 -107
  205. data/lib/active_record/relation/record_fetch_warning.rb +3 -0
  206. data/lib/active_record/relation/spawn_methods.rb +5 -4
  207. data/lib/active_record/relation/where_clause.rb +7 -19
  208. data/lib/active_record/relation.rb +602 -96
  209. data/lib/active_record/result.rb +55 -52
  210. data/lib/active_record/runtime_registry.rb +63 -1
  211. data/lib/active_record/sanitization.rb +76 -30
  212. data/lib/active_record/schema.rb +39 -23
  213. data/lib/active_record/schema_dumper.rb +82 -30
  214. data/lib/active_record/schema_migration.rb +75 -24
  215. data/lib/active_record/scoping/default.rb +20 -12
  216. data/lib/active_record/scoping/named.rb +3 -2
  217. data/lib/active_record/scoping.rb +2 -1
  218. data/lib/active_record/secure_password.rb +60 -0
  219. data/lib/active_record/secure_token.rb +21 -3
  220. data/lib/active_record/serialization.rb +5 -0
  221. data/lib/active_record/signed_id.rb +29 -8
  222. data/lib/active_record/statement_cache.rb +7 -7
  223. data/lib/active_record/store.rb +16 -11
  224. data/lib/active_record/suppressor.rb +3 -1
  225. data/lib/active_record/table_metadata.rb +7 -3
  226. data/lib/active_record/tasks/database_tasks.rb +191 -121
  227. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  228. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  229. data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
  230. data/lib/active_record/test_fixtures.rb +174 -152
  231. data/lib/active_record/testing/query_assertions.rb +121 -0
  232. data/lib/active_record/timestamp.rb +31 -17
  233. data/lib/active_record/token_for.rb +123 -0
  234. data/lib/active_record/touch_later.rb +12 -7
  235. data/lib/active_record/transaction.rb +132 -0
  236. data/lib/active_record/transactions.rb +109 -27
  237. data/lib/active_record/translation.rb +1 -3
  238. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  239. data/lib/active_record/type/internal/timezone.rb +7 -2
  240. data/lib/active_record/type/serialized.rb +9 -7
  241. data/lib/active_record/type/time.rb +4 -0
  242. data/lib/active_record/type_caster/connection.rb +4 -4
  243. data/lib/active_record/validations/absence.rb +1 -1
  244. data/lib/active_record/validations/associated.rb +12 -6
  245. data/lib/active_record/validations/numericality.rb +5 -4
  246. data/lib/active_record/validations/presence.rb +5 -28
  247. data/lib/active_record/validations/uniqueness.rb +63 -14
  248. data/lib/active_record/validations.rb +12 -5
  249. data/lib/active_record/version.rb +1 -1
  250. data/lib/active_record.rb +266 -30
  251. data/lib/arel/alias_predication.rb +1 -1
  252. data/lib/arel/collectors/bind.rb +2 -0
  253. data/lib/arel/collectors/composite.rb +7 -0
  254. data/lib/arel/collectors/sql_string.rb +1 -1
  255. data/lib/arel/collectors/substitute_binds.rb +1 -1
  256. data/lib/arel/errors.rb +10 -0
  257. data/lib/arel/factory_methods.rb +4 -0
  258. data/lib/arel/filter_predications.rb +1 -1
  259. data/lib/arel/nodes/binary.rb +6 -7
  260. data/lib/arel/nodes/bound_sql_literal.rb +65 -0
  261. data/lib/arel/nodes/cte.rb +36 -0
  262. data/lib/arel/nodes/filter.rb +1 -1
  263. data/lib/arel/nodes/fragments.rb +35 -0
  264. data/lib/arel/nodes/homogeneous_in.rb +1 -9
  265. data/lib/arel/nodes/leading_join.rb +8 -0
  266. data/lib/arel/nodes/{and.rb → nary.rb} +9 -2
  267. data/lib/arel/nodes/node.rb +115 -5
  268. data/lib/arel/nodes/sql_literal.rb +13 -0
  269. data/lib/arel/nodes/table_alias.rb +4 -0
  270. data/lib/arel/nodes.rb +6 -2
  271. data/lib/arel/predications.rb +3 -1
  272. data/lib/arel/select_manager.rb +1 -1
  273. data/lib/arel/table.rb +9 -5
  274. data/lib/arel/tree_manager.rb +8 -3
  275. data/lib/arel/update_manager.rb +2 -1
  276. data/lib/arel/visitors/dot.rb +1 -0
  277. data/lib/arel/visitors/mysql.rb +17 -5
  278. data/lib/arel/visitors/postgresql.rb +1 -12
  279. data/lib/arel/visitors/to_sql.rb +112 -34
  280. data/lib/arel/visitors/visitor.rb +2 -2
  281. data/lib/arel.rb +21 -3
  282. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  283. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
  284. data/lib/rails/generators/active_record/migration.rb +3 -1
  285. data/lib/rails/generators/active_record/model/USAGE +113 -0
  286. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  287. metadata +59 -17
  288. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  289. data/lib/active_record/null_relation.rb +0 -63
@@ -6,6 +6,13 @@ module ActiveRecord
6
6
  module ModelSchema
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ ##
10
+ # :method: id_value
11
+ # :call-seq: id_value
12
+ #
13
+ # Returns the underlying column value for a column named "id". Useful when defining
14
+ # a composite primary key including an "id" column so that the value is readable.
15
+
9
16
  ##
10
17
  # :singleton-method: primary_key_prefix_type
11
18
  # :call-seq: primary_key_prefix_type
@@ -126,6 +133,32 @@ module ActiveRecord
126
133
  # +:immutable_string+. This setting does not affect the behavior of
127
134
  # <tt>attribute :foo, :string</tt>. Defaults to false.
128
135
 
136
+ ##
137
+ # :singleton-method: inheritance_column
138
+ # :call-seq: inheritance_column
139
+ #
140
+ # The name of the table column which stores the class name on single-table
141
+ # inheritance situations.
142
+ #
143
+ # The default inheritance column name is +type+, which means it's a
144
+ # reserved word inside Active Record. To be able to use single-table
145
+ # inheritance with another column name, or to use the column +type+ in
146
+ # your own model for something else, you can set +inheritance_column+:
147
+ #
148
+ # self.inheritance_column = 'zoink'
149
+ #
150
+ # If you wish to disable single-table inheritance altogether you can set
151
+ # +inheritance_column+ to +nil+
152
+ #
153
+ # self.inheritance_column = nil
154
+
155
+ ##
156
+ # :singleton-method: inheritance_column=
157
+ # :call-seq: inheritance_column=(column)
158
+ #
159
+ # Defines the name of the table column which will store the class name on single-table
160
+ # inheritance situations.
161
+
129
162
  included do
130
163
  class_attribute :primary_key_prefix_type, instance_writer: false
131
164
  class_attribute :table_name_prefix, instance_writer: false, default: ""
@@ -136,15 +169,6 @@ module ActiveRecord
136
169
  class_attribute :implicit_order_column, instance_accessor: false
137
170
  class_attribute :immutable_strings_by_default, instance_accessor: false
138
171
 
139
- # Defines the name of the table column which will store the class name on single-table
140
- # inheritance situations.
141
- #
142
- # The default inheritance column name is +type+, which means it's a
143
- # reserved word inside Active Record. To be able to use single-table
144
- # inheritance with another column name, or to use the column +type+ in
145
- # your own model for something else, you can set +inheritance_column+:
146
- #
147
- # self.inheritance_column = 'zoink'
148
172
  class_attribute :inheritance_column, instance_accessor: false, default: "type"
149
173
  singleton_class.class_eval do
150
174
  alias_method :_inheritance_column=, :inheritance_column=
@@ -168,8 +192,9 @@ module ActiveRecord
168
192
  # artists, records => artists_records
169
193
  # records, artists => artists_records
170
194
  # music_artists, music_records => music_artists_records
195
+ # music.artists, music.records => music.artists_records
171
196
  def self.derive_join_table_name(first_table, second_table) # :nodoc:
172
- [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*_)(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
197
+ [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*[_.])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
173
198
  end
174
199
 
175
200
  module ClassMethods
@@ -253,19 +278,21 @@ module ActiveRecord
253
278
  @table_name = value
254
279
  @quoted_table_name = nil
255
280
  @arel_table = nil
256
- @sequence_name = nil unless defined?(@explicit_sequence_name) && @explicit_sequence_name
281
+ @sequence_name = nil unless @explicit_sequence_name
257
282
  @predicate_builder = nil
258
283
  end
259
284
 
260
285
  # Returns a quoted version of the table name, used to construct SQL statements.
261
286
  def quoted_table_name
262
- @quoted_table_name ||= connection.quote_table_name(table_name)
287
+ @quoted_table_name ||= adapter_class.quote_table_name(table_name)
263
288
  end
264
289
 
265
290
  # Computes the table name, (re)sets it internally, and returns it.
266
291
  def reset_table_name # :nodoc:
267
- self.table_name = if abstract_class?
268
- superclass == Base ? nil : superclass.table_name
292
+ self.table_name = if self == Base
293
+ nil
294
+ elsif abstract_class?
295
+ superclass.table_name
269
296
  elsif superclass.abstract_class?
270
297
  superclass.table_name || compute_table_name
271
298
  else
@@ -303,11 +330,7 @@ module ActiveRecord
303
330
  # The list of columns names the model should ignore. Ignored columns won't have attribute
304
331
  # accessors defined, and won't be referenced in SQL queries.
305
332
  def ignored_columns
306
- if defined?(@ignored_columns)
307
- @ignored_columns
308
- else
309
- superclass.ignored_columns
310
- end
333
+ @ignored_columns || superclass.ignored_columns
311
334
  end
312
335
 
313
336
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
@@ -328,7 +351,7 @@ module ActiveRecord
328
351
  # # name :string, limit: 255
329
352
  # # category :string, limit: 255
330
353
  #
331
- # self.ignored_columns = [:category]
354
+ # self.ignored_columns += [:category]
332
355
  # end
333
356
  #
334
357
  # The schema still contains "category", but now the model omits it, so any meta-driven code or
@@ -356,7 +379,7 @@ module ActiveRecord
356
379
 
357
380
  def reset_sequence_name # :nodoc:
358
381
  @explicit_sequence_name = false
359
- @sequence_name = connection.default_sequence_name(table_name, primary_key)
382
+ @sequence_name = with_connection { |c| c.default_sequence_name(table_name, primary_key) }
360
383
  end
361
384
 
362
385
  # Sets the name of the sequence to use when generating ids to the given
@@ -381,71 +404,53 @@ module ActiveRecord
381
404
  # Determines if the primary key values should be selected from their
382
405
  # corresponding sequence before the insert statement.
383
406
  def prefetch_primary_key?
384
- connection.prefetch_primary_key?(table_name)
407
+ with_connection { |c| c.prefetch_primary_key?(table_name) }
385
408
  end
386
409
 
387
410
  # Returns the next value that will be used as the primary key on
388
411
  # an insert statement.
389
412
  def next_sequence_value
390
- connection.next_sequence_value(sequence_name)
413
+ with_connection { |c| c.next_sequence_value(sequence_name) }
391
414
  end
392
415
 
393
416
  # Indicates whether the table associated with this class exists
394
417
  def table_exists?
395
- connection.schema_cache.data_source_exists?(table_name)
418
+ schema_cache.data_source_exists?(table_name)
396
419
  end
397
420
 
398
421
  def attributes_builder # :nodoc:
399
- unless defined?(@attributes_builder) && @attributes_builder
422
+ @attributes_builder ||= begin
400
423
  defaults = _default_attributes.except(*(column_names - [primary_key]))
401
- @attributes_builder = ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
424
+ ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
402
425
  end
403
- @attributes_builder
404
426
  end
405
427
 
406
428
  def columns_hash # :nodoc:
407
- load_schema
429
+ load_schema unless @columns_hash
408
430
  @columns_hash
409
431
  end
410
432
 
411
433
  def columns
412
- load_schema
434
+ load_schema unless @columns
413
435
  @columns ||= columns_hash.values.freeze
414
436
  end
415
437
 
416
- def attribute_types # :nodoc:
417
- load_schema
418
- @attribute_types ||= Hash.new(Type.default_value)
438
+ def _returning_columns_for_insert(connection) # :nodoc:
439
+ @_returning_columns_for_insert ||= begin
440
+ auto_populated_columns = columns.filter_map do |c|
441
+ c.name if connection.return_value_after_insert?(c)
442
+ end
443
+
444
+ auto_populated_columns.empty? ? Array(primary_key) : auto_populated_columns
445
+ end
419
446
  end
420
447
 
421
448
  def yaml_encoder # :nodoc:
422
449
  @yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
423
450
  end
424
451
 
425
- # Returns the type of the attribute with the given name, after applying
426
- # all modifiers. This method is the only valid source of information for
427
- # anything related to the types of a model's attributes. This method will
428
- # access the database and load the model's schema if it is required.
429
- #
430
- # The return value of this method will implement the interface described
431
- # by ActiveModel::Type::Value (though the object itself may not subclass
432
- # it).
433
- #
434
- # +attr_name+ The name of the attribute to retrieve the type for. Must be
435
- # a string or a symbol.
436
- def type_for_attribute(attr_name, &block)
437
- attr_name = attr_name.to_s
438
- attr_name = attribute_aliases[attr_name] || attr_name
439
-
440
- if block
441
- attribute_types.fetch(attr_name, &block)
442
- else
443
- attribute_types[attr_name]
444
- end
445
- end
446
-
447
452
  # Returns the column object for the named attribute.
448
- # Returns an +ActiveRecord::ConnectionAdapters::NullColumn+ if the
453
+ # Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
449
454
  # named attribute does not exist.
450
455
  #
451
456
  # class Person < ActiveRecord::Base
@@ -471,11 +476,6 @@ module ActiveRecord
471
476
  @column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
472
477
  end
473
478
 
474
- def _default_attributes # :nodoc:
475
- load_schema
476
- @default_attributes ||= ActiveModel::AttributeSet.new({})
477
- end
478
-
479
479
  # Returns an array of column names as strings.
480
480
  def column_names
481
481
  @column_names ||= columns.map(&:name).freeze
@@ -503,7 +503,7 @@ module ActiveRecord
503
503
  # when just after creating a table you want to populate it with some default
504
504
  # values, e.g.:
505
505
  #
506
- # class CreateJobLevels < ActiveRecord::Migration[7.0]
506
+ # class CreateJobLevels < ActiveRecord::Migration[7.2]
507
507
  # def up
508
508
  # create_table :job_levels do |t|
509
509
  # t.integer :id
@@ -523,41 +523,67 @@ module ActiveRecord
523
523
  # end
524
524
  # end
525
525
  def reset_column_information
526
- connection.clear_cache!
526
+ connection_pool.active_connection&.clear_cache!
527
527
  ([self] + descendants).each(&:undefine_attribute_methods)
528
- connection.schema_cache.clear_data_source_cache!(table_name)
528
+ schema_cache.clear_data_source_cache!(table_name)
529
529
 
530
530
  reload_schema_from_cache
531
531
  initialize_find_by_cache
532
532
  end
533
533
 
534
+ # Load the model's schema information either from the schema cache
535
+ # or directly from the database.
536
+ def load_schema
537
+ return if schema_loaded?
538
+ @load_schema_monitor.synchronize do
539
+ return if schema_loaded?
540
+
541
+ load_schema!
542
+
543
+ @schema_loaded = true
544
+ rescue
545
+ reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
546
+ raise
547
+ end
548
+ end
549
+
534
550
  protected
535
551
  def initialize_load_schema_monitor
536
552
  @load_schema_monitor = Monitor.new
537
553
  end
538
554
 
555
+ def reload_schema_from_cache(recursive = true)
556
+ @_returning_columns_for_insert = nil
557
+ @arel_table = nil
558
+ @column_names = nil
559
+ @symbol_column_to_string_name_hash = nil
560
+ @content_columns = nil
561
+ @column_defaults = nil
562
+ @attributes_builder = nil
563
+ @columns = nil
564
+ @columns_hash = nil
565
+ @schema_loaded = false
566
+ @attribute_names = nil
567
+ @yaml_encoder = nil
568
+ if recursive
569
+ subclasses.each do |descendant|
570
+ descendant.send(:reload_schema_from_cache)
571
+ end
572
+ end
573
+ end
574
+
539
575
  private
540
576
  def inherited(child_class)
541
577
  super
542
578
  child_class.initialize_load_schema_monitor
579
+ child_class.reload_schema_from_cache(false)
580
+ child_class.class_eval do
581
+ @ignored_columns = nil
582
+ end
543
583
  end
544
584
 
545
585
  def schema_loaded?
546
- defined?(@schema_loaded) && @schema_loaded
547
- end
548
-
549
- def load_schema
550
- return if schema_loaded?
551
- @load_schema_monitor.synchronize do
552
- return if defined?(@columns_hash) && @columns_hash
553
-
554
- load_schema!
555
-
556
- @schema_loaded = true
557
- rescue
558
- reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
559
- raise
560
- end
586
+ @schema_loaded
561
587
  end
562
588
 
563
589
  def load_schema!
@@ -565,38 +591,11 @@ module ActiveRecord
565
591
  raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
566
592
  end
567
593
 
568
- columns_hash = connection.schema_cache.columns_hash(table_name)
594
+ columns_hash = schema_cache.columns_hash(table_name)
569
595
  columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
570
596
  @columns_hash = columns_hash.freeze
571
- @columns_hash.each do |name, column|
572
- type = connection.lookup_cast_type_from_column(column)
573
- type = _convert_type_from_options(type)
574
- define_attribute(
575
- name,
576
- type,
577
- default: column.default,
578
- user_provided_default: false
579
- )
580
- end
581
- end
582
597
 
583
- def reload_schema_from_cache
584
- @arel_table = nil
585
- @column_names = nil
586
- @symbol_column_to_string_name_hash = nil
587
- @attribute_types = nil
588
- @content_columns = nil
589
- @default_attributes = nil
590
- @column_defaults = nil
591
- @attributes_builder = nil
592
- @columns = nil
593
- @columns_hash = nil
594
- @schema_loaded = false
595
- @attribute_names = nil
596
- @yaml_encoder = nil
597
- subclasses.each do |descendant|
598
- descendant.send(:reload_schema_from_cache)
599
- end
598
+ super
600
599
  end
601
600
 
602
601
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -617,17 +616,19 @@ module ActiveRecord
617
616
 
618
617
  "#{full_table_name_prefix}#{contained}#{undecorated_table_name(model_name)}#{full_table_name_suffix}"
619
618
  else
620
- # STI subclasses always use their superclass' table.
619
+ # STI subclasses always use their superclass's table.
621
620
  base_class.table_name
622
621
  end
623
622
  end
624
623
 
625
- def _convert_type_from_options(type)
624
+ def type_for_column(connection, column)
625
+ type = connection.lookup_cast_type_from_column(column)
626
+
626
627
  if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
627
- type.to_immutable_string
628
- else
629
- type
628
+ type = type.to_immutable_string
630
629
  end
630
+
631
+ type
631
632
  end
632
633
  end
633
634
  end
@@ -15,7 +15,7 @@ module ActiveRecord
15
15
  class_attribute :nested_attributes_options, instance_writer: false, default: {}
16
16
  end
17
17
 
18
- # = Active Record Nested Attributes
18
+ # = Active Record Nested \Attributes
19
19
  #
20
20
  # Nested attributes allow you to save attributes on associated records
21
21
  # through the parent. By default nested attribute updating is turned off
@@ -280,6 +280,24 @@ module ActiveRecord
280
280
  # member = Member.new
281
281
  # member.avatar_attributes = {icon: 'sad'}
282
282
  # member.avatar.width # => 200
283
+ #
284
+ # === Creating forms with nested attributes
285
+ #
286
+ # Use ActionView::Helpers::FormHelper#fields_for to create form elements for
287
+ # nested attributes.
288
+ #
289
+ # Integration test params should reflect the structure of the form. For
290
+ # example:
291
+ #
292
+ # post members_path, params: {
293
+ # member: {
294
+ # name: 'joe',
295
+ # posts_attributes: {
296
+ # '0' => { title: 'Foo' },
297
+ # '1' => { title: 'Bar' }
298
+ # }
299
+ # }
300
+ # }
283
301
  module ClassMethods
284
302
  REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
285
303
 
@@ -289,7 +307,7 @@ module ActiveRecord
289
307
  # [:allow_destroy]
290
308
  # If true, destroys any members from the attributes hash with a
291
309
  # <tt>_destroy</tt> key and a value that evaluates to +true+
292
- # (e.g. 1, '1', true, or 'true'). This option is off by default.
310
+ # (e.g. 1, '1', true, or 'true'). This option is false by default.
293
311
  # [:reject_if]
294
312
  # Allows you to specify a Proc or a Symbol pointing to a method
295
313
  # that checks whether a record should be built for a certain attribute
@@ -314,11 +332,11 @@ module ActiveRecord
314
332
  # nested attributes are going to be used when an associated record already
315
333
  # exists. In general, an existing record may either be updated with the
316
334
  # new set of attribute values or be replaced by a wholly new record
317
- # containing those values. By default the +:update_only+ option is +false+
335
+ # containing those values. By default the +:update_only+ option is false
318
336
  # and the nested attributes are used to update the existing record only
319
337
  # if they include the record's <tt>:id</tt> value. Otherwise a new
320
338
  # record will be instantiated and used to replace the existing one.
321
- # However if the +:update_only+ option is +true+, the nested attributes
339
+ # However if the +:update_only+ option is true, the nested attributes
322
340
  # are used to update the record's attributes always, regardless of
323
341
  # whether the <tt>:id</tt> is present. The option is ignored for collection
324
342
  # associations.
@@ -375,11 +393,11 @@ module ActiveRecord
375
393
  end
376
394
  end
377
395
 
378
- # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
396
+ # Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
379
397
  # used in conjunction with fields_for to build a form element for the
380
398
  # destruction of this association.
381
399
  #
382
- # See ActionView::Helpers::FormHelper::fields_for for more info.
400
+ # See ActionView::Helpers::FormHelper#fields_for for more info.
383
401
  def _destroy
384
402
  marked_for_destruction?
385
403
  end
@@ -403,10 +421,15 @@ module ActiveRecord
403
421
  # update_only is true, and a <tt>:_destroy</tt> key set to a truthy value,
404
422
  # then the existing record will be marked for destruction.
405
423
  def assign_nested_attributes_for_one_to_one_association(association_name, attributes)
406
- options = nested_attributes_options[association_name]
407
424
  if attributes.respond_to?(:permitted?)
408
425
  attributes = attributes.to_h
409
426
  end
427
+
428
+ unless attributes.is_a?(Hash)
429
+ raise ArgumentError, "Hash expected for `#{association_name}` attributes, got #{attributes.class.name}"
430
+ end
431
+
432
+ options = nested_attributes_options[association_name]
410
433
  attributes = attributes.with_indifferent_access
411
434
  existing_record = send(association_name)
412
435
 
@@ -468,7 +491,7 @@ module ActiveRecord
468
491
  end
469
492
 
470
493
  unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
471
- raise ArgumentError, "Hash or Array expected for attribute `#{association_name}`, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
494
+ raise ArgumentError, "Hash or Array expected for `#{association_name}` attributes, got #{attributes_collection.class.name}"
472
495
  end
473
496
 
474
497
  check_record_limit!(options[:limit], attributes_collection)
@@ -491,7 +514,7 @@ module ActiveRecord
491
514
  attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
492
515
  end
493
516
 
494
- attributes_collection.each do |attributes|
517
+ records = attributes_collection.map do |attributes|
495
518
  if attributes.respond_to?(:permitted?)
496
519
  attributes = attributes.to_h
497
520
  end
@@ -514,11 +537,14 @@ module ActiveRecord
514
537
  end
515
538
 
516
539
  assign_to_or_mark_for_destruction(existing_record, attributes, options[:allow_destroy])
540
+ existing_record
517
541
  end
518
542
  else
519
543
  raise_nested_attributes_record_not_found!(association_name, attributes["id"])
520
544
  end
521
545
  end
546
+
547
+ association.nested_attributes_target = records
522
548
  end
523
549
 
524
550
  # Takes in a limit and checks if the attributes_collection has too many
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord # :nodoc:
4
+ module Normalization
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ class_attribute :normalized_attributes, default: Set.new
9
+
10
+ before_validation :normalize_changed_in_place_attributes
11
+ end
12
+
13
+ # Normalizes a specified attribute using its declared normalizations.
14
+ #
15
+ # ==== Examples
16
+ #
17
+ # class User < ActiveRecord::Base
18
+ # normalizes :email, with: -> email { email.strip.downcase }
19
+ # end
20
+ #
21
+ # legacy_user = User.find(1)
22
+ # legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n"
23
+ # legacy_user.normalize_attribute(:email)
24
+ # legacy_user.email # => "cruise-control@example.com"
25
+ # legacy_user.save
26
+ def normalize_attribute(name)
27
+ # Treat the value as a new, unnormalized value.
28
+ self[name] = self[name]
29
+ end
30
+
31
+ module ClassMethods
32
+ # Declares a normalization for one or more attributes. The normalization
33
+ # is applied when the attribute is assigned or updated, and the normalized
34
+ # value will be persisted to the database. The normalization is also
35
+ # applied to the corresponding keyword argument of query methods. This
36
+ # allows a record to be created and later queried using unnormalized
37
+ # values.
38
+ #
39
+ # However, to prevent confusion, the normalization will not be applied
40
+ # when the attribute is fetched from the database. This means that if a
41
+ # record was persisted before the normalization was declared, the record's
42
+ # attribute will not be normalized until either it is assigned a new
43
+ # value, or it is explicitly migrated via Normalization#normalize_attribute.
44
+ #
45
+ # Because the normalization may be applied multiple times, it should be
46
+ # _idempotent_. In other words, applying the normalization more than once
47
+ # should have the same result as applying it only once.
48
+ #
49
+ # By default, the normalization will not be applied to +nil+ values. This
50
+ # behavior can be changed with the +:apply_to_nil+ option.
51
+ #
52
+ # Be aware that if your app was created before Rails 7.1, and your app
53
+ # marshals instances of the targeted model (for example, when caching),
54
+ # then you should set ActiveRecord.marshalling_format_version to +7.1+ or
55
+ # higher via either <tt>config.load_defaults 7.1</tt> or
56
+ # <tt>config.active_record.marshalling_format_version = 7.1</tt>.
57
+ # Otherwise, +Marshal+ may attempt to serialize the normalization +Proc+
58
+ # and raise +TypeError+.
59
+ #
60
+ # ==== Options
61
+ #
62
+ # * +:with+ - Any callable object that accepts the attribute's value as
63
+ # its sole argument, and returns it normalized.
64
+ # * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
65
+ # Defaults to +false+.
66
+ #
67
+ # ==== Examples
68
+ #
69
+ # class User < ActiveRecord::Base
70
+ # normalizes :email, with: -> email { email.strip.downcase }
71
+ # normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
72
+ # end
73
+ #
74
+ # user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
75
+ # user.email # => "cruise-control@example.com"
76
+ #
77
+ # user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
78
+ # user.email # => "cruise-control@example.com"
79
+ # user.email_before_type_cast # => "cruise-control@example.com"
80
+ #
81
+ # User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
82
+ # User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
83
+ #
84
+ # User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
85
+ # User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
86
+ #
87
+ # User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
88
+ def normalizes(*names, with:, apply_to_nil: false)
89
+ decorate_attributes(names) do |name, cast_type|
90
+ NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
91
+ end
92
+
93
+ self.normalized_attributes += names.map(&:to_sym)
94
+ end
95
+
96
+ # Normalizes a given +value+ using normalizations declared for +name+.
97
+ #
98
+ # ==== Examples
99
+ #
100
+ # class User < ActiveRecord::Base
101
+ # normalizes :email, with: -> email { email.strip.downcase }
102
+ # end
103
+ #
104
+ # User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
105
+ # # => "cruise-control@example.com"
106
+ def normalize_value_for(name, value)
107
+ type_for_attribute(name).cast(value)
108
+ end
109
+ end
110
+
111
+ private
112
+ def normalize_changed_in_place_attributes
113
+ self.class.normalized_attributes.each do |name|
114
+ normalize_attribute(name) if attribute_changed_in_place?(name)
115
+ end
116
+ end
117
+
118
+ class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
119
+ include ActiveModel::Type::SerializeCastValue
120
+
121
+ attr_reader :cast_type, :normalizer, :normalize_nil
122
+ alias :normalize_nil? :normalize_nil
123
+
124
+ def initialize(cast_type:, normalizer:, normalize_nil:)
125
+ @cast_type = cast_type
126
+ @normalizer = normalizer
127
+ @normalize_nil = normalize_nil
128
+ super(cast_type)
129
+ end
130
+
131
+ def cast(value)
132
+ normalize(super(value))
133
+ end
134
+
135
+ def serialize(value)
136
+ serialize_cast_value(cast(value))
137
+ end
138
+
139
+ def serialize_cast_value(value)
140
+ ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
141
+ end
142
+
143
+ def ==(other)
144
+ self.class == other.class &&
145
+ normalize_nil? == other.normalize_nil? &&
146
+ normalizer == other.normalizer &&
147
+ cast_type == other.cast_type
148
+ end
149
+ alias eql? ==
150
+
151
+ def hash
152
+ [self.class, cast_type, normalizer, normalize_nil?].hash
153
+ end
154
+
155
+ define_method(:inspect, Kernel.instance_method(:inspect))
156
+
157
+ private
158
+ def normalize(value)
159
+ normalizer.call(value) unless value.nil? && !normalize_nil?
160
+ end
161
+ end
162
+ end
163
+ end