activerecord 7.0.0 → 7.1.0

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 (249) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +1607 -1040
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +17 -18
  5. data/lib/active_record/aggregations.rb +16 -13
  6. data/lib/active_record/association_relation.rb +1 -1
  7. data/lib/active_record/associations/association.rb +18 -3
  8. data/lib/active_record/associations/association_scope.rb +16 -9
  9. data/lib/active_record/associations/belongs_to_association.rb +14 -6
  10. data/lib/active_record/associations/builder/association.rb +3 -3
  11. data/lib/active_record/associations/builder/belongs_to.rb +21 -8
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
  13. data/lib/active_record/associations/builder/singular_association.rb +4 -0
  14. data/lib/active_record/associations/collection_association.rb +17 -12
  15. data/lib/active_record/associations/collection_proxy.rb +22 -12
  16. data/lib/active_record/associations/foreign_association.rb +10 -3
  17. data/lib/active_record/associations/has_many_association.rb +27 -17
  18. data/lib/active_record/associations/has_many_through_association.rb +10 -6
  19. data/lib/active_record/associations/has_one_association.rb +10 -3
  20. data/lib/active_record/associations/join_dependency.rb +20 -14
  21. data/lib/active_record/associations/preloader/association.rb +27 -6
  22. data/lib/active_record/associations/preloader/through_association.rb +1 -1
  23. data/lib/active_record/associations/preloader.rb +13 -10
  24. data/lib/active_record/associations/singular_association.rb +1 -1
  25. data/lib/active_record/associations/through_association.rb +22 -11
  26. data/lib/active_record/associations.rb +345 -219
  27. data/lib/active_record/attribute_assignment.rb +0 -2
  28. data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
  29. data/lib/active_record/attribute_methods/dirty.rb +40 -26
  30. data/lib/active_record/attribute_methods/primary_key.rb +76 -24
  31. data/lib/active_record/attribute_methods/query.rb +28 -16
  32. data/lib/active_record/attribute_methods/read.rb +18 -5
  33. data/lib/active_record/attribute_methods/serialization.rb +172 -69
  34. data/lib/active_record/attribute_methods/write.rb +3 -3
  35. data/lib/active_record/attribute_methods.rb +110 -28
  36. data/lib/active_record/attributes.rb +3 -3
  37. data/lib/active_record/autosave_association.rb +56 -10
  38. data/lib/active_record/base.rb +10 -5
  39. data/lib/active_record/callbacks.rb +16 -32
  40. data/lib/active_record/coders/column_serializer.rb +61 -0
  41. data/lib/active_record/coders/json.rb +1 -1
  42. data/lib/active_record/coders/yaml_column.rb +70 -34
  43. data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
  44. data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
  45. data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
  46. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
  47. data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
  48. data/lib/active_record/connection_adapters/abstract/database_statements.rb +128 -32
  49. data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
  50. data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
  51. data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
  52. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
  53. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
  54. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -129
  56. data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
  57. data/lib/active_record/connection_adapters/abstract_adapter.rb +504 -106
  58. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
  59. data/lib/active_record/connection_adapters/column.rb +9 -0
  60. data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
  61. data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
  62. data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
  63. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
  64. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
  65. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
  66. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
  67. data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
  68. data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
  69. data/lib/active_record/connection_adapters/pool_config.rb +14 -5
  70. data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
  71. data/lib/active_record/connection_adapters/postgresql/column.rb +3 -2
  72. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +72 -45
  73. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
  74. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
  75. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
  76. data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +3 -1
  77. data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
  78. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
  79. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
  80. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
  81. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
  82. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +358 -57
  83. data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
  84. data/lib/active_record/connection_adapters/postgresql_adapter.rb +343 -181
  85. data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
  86. data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
  87. data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +45 -39
  88. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
  89. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
  90. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
  91. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
  92. data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
  93. data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
  94. data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
  95. data/lib/active_record/connection_adapters.rb +3 -1
  96. data/lib/active_record/connection_handling.rb +73 -96
  97. data/lib/active_record/core.rb +136 -148
  98. data/lib/active_record/counter_cache.rb +46 -25
  99. data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
  100. data/lib/active_record/database_configurations/database_config.rb +9 -3
  101. data/lib/active_record/database_configurations/hash_config.rb +22 -12
  102. data/lib/active_record/database_configurations/url_config.rb +17 -11
  103. data/lib/active_record/database_configurations.rb +87 -34
  104. data/lib/active_record/delegated_type.rb +9 -4
  105. data/lib/active_record/deprecator.rb +7 -0
  106. data/lib/active_record/destroy_association_async_job.rb +2 -0
  107. data/lib/active_record/disable_joins_association_relation.rb +1 -1
  108. data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
  109. data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
  110. data/lib/active_record/encryption/config.rb +25 -1
  111. data/lib/active_record/encryption/configurable.rb +13 -14
  112. data/lib/active_record/encryption/context.rb +10 -3
  113. data/lib/active_record/encryption/contexts.rb +8 -4
  114. data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
  115. data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
  116. data/lib/active_record/encryption/encryptable_record.rb +38 -22
  117. data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
  118. data/lib/active_record/encryption/encryptor.rb +7 -7
  119. data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
  120. data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -71
  121. data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
  122. data/lib/active_record/encryption/key_generator.rb +12 -1
  123. data/lib/active_record/encryption/message.rb +1 -1
  124. data/lib/active_record/encryption/message_serializer.rb +2 -0
  125. data/lib/active_record/encryption/properties.rb +4 -4
  126. data/lib/active_record/encryption/scheme.rb +20 -23
  127. data/lib/active_record/encryption.rb +1 -0
  128. data/lib/active_record/enum.rb +114 -27
  129. data/lib/active_record/errors.rb +108 -15
  130. data/lib/active_record/explain.rb +23 -3
  131. data/lib/active_record/explain_subscriber.rb +1 -1
  132. data/lib/active_record/fixture_set/model_metadata.rb +14 -4
  133. data/lib/active_record/fixture_set/render_context.rb +2 -0
  134. data/lib/active_record/fixture_set/table_row.rb +29 -8
  135. data/lib/active_record/fixtures.rb +121 -73
  136. data/lib/active_record/future_result.rb +30 -5
  137. data/lib/active_record/gem_version.rb +2 -2
  138. data/lib/active_record/inheritance.rb +30 -16
  139. data/lib/active_record/insert_all.rb +55 -8
  140. data/lib/active_record/integration.rb +10 -10
  141. data/lib/active_record/internal_metadata.rb +118 -30
  142. data/lib/active_record/locking/optimistic.rb +32 -18
  143. data/lib/active_record/locking/pessimistic.rb +8 -5
  144. data/lib/active_record/log_subscriber.rb +39 -17
  145. data/lib/active_record/marshalling.rb +56 -0
  146. data/lib/active_record/message_pack.rb +124 -0
  147. data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
  148. data/lib/active_record/middleware/database_selector.rb +18 -13
  149. data/lib/active_record/middleware/shard_selector.rb +7 -5
  150. data/lib/active_record/migration/command_recorder.rb +104 -9
  151. data/lib/active_record/migration/compatibility.rb +158 -64
  152. data/lib/active_record/migration/default_strategy.rb +23 -0
  153. data/lib/active_record/migration/execution_strategy.rb +19 -0
  154. data/lib/active_record/migration.rb +271 -117
  155. data/lib/active_record/model_schema.rb +82 -50
  156. data/lib/active_record/nested_attributes.rb +23 -3
  157. data/lib/active_record/normalization.rb +159 -0
  158. data/lib/active_record/persistence.rb +200 -47
  159. data/lib/active_record/promise.rb +84 -0
  160. data/lib/active_record/query_cache.rb +3 -21
  161. data/lib/active_record/query_logs.rb +87 -51
  162. data/lib/active_record/query_logs_formatter.rb +41 -0
  163. data/lib/active_record/querying.rb +16 -3
  164. data/lib/active_record/railtie.rb +127 -61
  165. data/lib/active_record/railties/controller_runtime.rb +12 -8
  166. data/lib/active_record/railties/databases.rake +142 -143
  167. data/lib/active_record/railties/job_runtime.rb +23 -0
  168. data/lib/active_record/readonly_attributes.rb +32 -5
  169. data/lib/active_record/reflection.rb +177 -45
  170. data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
  171. data/lib/active_record/relation/batches.rb +190 -61
  172. data/lib/active_record/relation/calculations.rb +200 -83
  173. data/lib/active_record/relation/delegation.rb +23 -9
  174. data/lib/active_record/relation/finder_methods.rb +77 -16
  175. data/lib/active_record/relation/merger.rb +2 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
  177. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
  178. data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
  179. data/lib/active_record/relation/predicate_builder.rb +26 -14
  180. data/lib/active_record/relation/query_attribute.rb +25 -1
  181. data/lib/active_record/relation/query_methods.rb +429 -76
  182. data/lib/active_record/relation/spawn_methods.rb +18 -1
  183. data/lib/active_record/relation.rb +98 -41
  184. data/lib/active_record/result.rb +25 -9
  185. data/lib/active_record/runtime_registry.rb +10 -1
  186. data/lib/active_record/sanitization.rb +57 -16
  187. data/lib/active_record/schema.rb +36 -22
  188. data/lib/active_record/schema_dumper.rb +65 -23
  189. data/lib/active_record/schema_migration.rb +68 -33
  190. data/lib/active_record/scoping/default.rb +20 -12
  191. data/lib/active_record/scoping/named.rb +2 -2
  192. data/lib/active_record/scoping.rb +2 -1
  193. data/lib/active_record/secure_password.rb +60 -0
  194. data/lib/active_record/secure_token.rb +21 -3
  195. data/lib/active_record/serialization.rb +5 -0
  196. data/lib/active_record/signed_id.rb +9 -7
  197. data/lib/active_record/store.rb +16 -11
  198. data/lib/active_record/suppressor.rb +3 -1
  199. data/lib/active_record/table_metadata.rb +16 -3
  200. data/lib/active_record/tasks/database_tasks.rb +138 -107
  201. data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
  202. data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
  203. data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
  204. data/lib/active_record/test_fixtures.rb +123 -99
  205. data/lib/active_record/timestamp.rb +26 -14
  206. data/lib/active_record/token_for.rb +113 -0
  207. data/lib/active_record/touch_later.rb +11 -6
  208. data/lib/active_record/transactions.rb +39 -13
  209. data/lib/active_record/translation.rb +1 -1
  210. data/lib/active_record/type/adapter_specific_registry.rb +1 -8
  211. data/lib/active_record/type/internal/timezone.rb +7 -2
  212. data/lib/active_record/type/serialized.rb +8 -4
  213. data/lib/active_record/type/time.rb +4 -0
  214. data/lib/active_record/validations/absence.rb +1 -1
  215. data/lib/active_record/validations/associated.rb +3 -3
  216. data/lib/active_record/validations/numericality.rb +5 -4
  217. data/lib/active_record/validations/presence.rb +5 -28
  218. data/lib/active_record/validations/uniqueness.rb +50 -5
  219. data/lib/active_record/validations.rb +8 -4
  220. data/lib/active_record/version.rb +1 -1
  221. data/lib/active_record.rb +143 -16
  222. data/lib/arel/errors.rb +10 -0
  223. data/lib/arel/factory_methods.rb +4 -0
  224. data/lib/arel/filter_predications.rb +1 -1
  225. data/lib/arel/nodes/and.rb +4 -0
  226. data/lib/arel/nodes/binary.rb +6 -1
  227. data/lib/arel/nodes/bound_sql_literal.rb +61 -0
  228. data/lib/arel/nodes/cte.rb +36 -0
  229. data/lib/arel/nodes/filter.rb +1 -1
  230. data/lib/arel/nodes/fragments.rb +35 -0
  231. data/lib/arel/nodes/homogeneous_in.rb +0 -8
  232. data/lib/arel/nodes/leading_join.rb +8 -0
  233. data/lib/arel/nodes/node.rb +111 -2
  234. data/lib/arel/nodes/sql_literal.rb +6 -0
  235. data/lib/arel/nodes/table_alias.rb +4 -0
  236. data/lib/arel/nodes.rb +4 -0
  237. data/lib/arel/predications.rb +2 -0
  238. data/lib/arel/table.rb +9 -5
  239. data/lib/arel/visitors/mysql.rb +8 -1
  240. data/lib/arel/visitors/to_sql.rb +81 -17
  241. data/lib/arel/visitors/visitor.rb +2 -2
  242. data/lib/arel.rb +16 -2
  243. data/lib/rails/generators/active_record/application_record/USAGE +8 -0
  244. data/lib/rails/generators/active_record/migration.rb +3 -1
  245. data/lib/rails/generators/active_record/model/USAGE +113 -0
  246. data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
  247. metadata +50 -15
  248. data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
  249. 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,27 @@ 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
+ ##
151
+ # :singleton-method: inheritance_column=
152
+ # :call-seq: inheritance_column=(column)
153
+ #
154
+ # Defines the name of the table column which will store the class name on single-table
155
+ # inheritance situations.
156
+
129
157
  included do
130
158
  class_attribute :primary_key_prefix_type, instance_writer: false
131
159
  class_attribute :table_name_prefix, instance_writer: false, default: ""
@@ -136,15 +164,6 @@ module ActiveRecord
136
164
  class_attribute :implicit_order_column, instance_accessor: false
137
165
  class_attribute :immutable_strings_by_default, instance_accessor: false
138
166
 
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
167
  class_attribute :inheritance_column, instance_accessor: false, default: "type"
149
168
  singleton_class.class_eval do
150
169
  alias_method :_inheritance_column=, :inheritance_column=
@@ -168,8 +187,9 @@ module ActiveRecord
168
187
  # artists, records => artists_records
169
188
  # records, artists => artists_records
170
189
  # music_artists, music_records => music_artists_records
190
+ # music.artists, music.records => music.artists_records
171
191
  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", "_")
192
+ [first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*[_.])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
173
193
  end
174
194
 
175
195
  module ClassMethods
@@ -303,11 +323,7 @@ module ActiveRecord
303
323
  # The list of columns names the model should ignore. Ignored columns won't have attribute
304
324
  # accessors defined, and won't be referenced in SQL queries.
305
325
  def ignored_columns
306
- if defined?(@ignored_columns)
307
- @ignored_columns
308
- else
309
- superclass.ignored_columns
310
- end
326
+ @ignored_columns || superclass.ignored_columns
311
327
  end
312
328
 
313
329
  # Sets the columns names the model should ignore. Ignored columns won't have attribute
@@ -328,7 +344,7 @@ module ActiveRecord
328
344
  # # name :string, limit: 255
329
345
  # # category :string, limit: 255
330
346
  #
331
- # self.ignored_columns = [:category]
347
+ # self.ignored_columns += [:category]
332
348
  # end
333
349
  #
334
350
  # The schema still contains "category", but now the model omits it, so any meta-driven code or
@@ -413,6 +429,12 @@ module ActiveRecord
413
429
  @columns ||= columns_hash.values.freeze
414
430
  end
415
431
 
432
+ def _returning_columns_for_insert # :nodoc:
433
+ @_returning_columns_for_insert ||= columns.filter_map do |c|
434
+ c.name if connection.return_value_after_insert?(c)
435
+ end
436
+ end
437
+
416
438
  def attribute_types # :nodoc:
417
439
  load_schema
418
440
  @attribute_types ||= Hash.new(Type.default_value)
@@ -503,7 +525,7 @@ module ActiveRecord
503
525
  # when just after creating a table you want to populate it with some default
504
526
  # values, e.g.:
505
527
  #
506
- # class CreateJobLevels < ActiveRecord::Migration[7.0]
528
+ # class CreateJobLevels < ActiveRecord::Migration[7.1]
507
529
  # def up
508
530
  # create_table :job_levels do |t|
509
531
  # t.integer :id
@@ -531,35 +553,61 @@ module ActiveRecord
531
553
  initialize_find_by_cache
532
554
  end
533
555
 
556
+ def load_schema # :nodoc:
557
+ return if schema_loaded?
558
+ @load_schema_monitor.synchronize do
559
+ return if @columns_hash
560
+
561
+ load_schema!
562
+
563
+ @schema_loaded = true
564
+ rescue
565
+ reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
566
+ raise
567
+ end
568
+ end
569
+
534
570
  protected
535
571
  def initialize_load_schema_monitor
536
572
  @load_schema_monitor = Monitor.new
537
573
  end
538
574
 
575
+ def reload_schema_from_cache(recursive = true)
576
+ @_returning_columns_for_insert = nil
577
+ @arel_table = nil
578
+ @column_names = nil
579
+ @symbol_column_to_string_name_hash = nil
580
+ @attribute_types = nil
581
+ @content_columns = nil
582
+ @default_attributes = nil
583
+ @column_defaults = nil
584
+ @attributes_builder = nil
585
+ @columns = nil
586
+ @columns_hash = nil
587
+ @schema_loaded = false
588
+ @attribute_names = nil
589
+ @yaml_encoder = nil
590
+ if recursive
591
+ subclasses.each do |descendant|
592
+ descendant.send(:reload_schema_from_cache)
593
+ end
594
+ end
595
+ end
596
+
539
597
  private
540
598
  def inherited(child_class)
541
599
  super
542
600
  child_class.initialize_load_schema_monitor
601
+ child_class.reload_schema_from_cache(false)
602
+ child_class.class_eval do
603
+ @ignored_columns = nil
604
+ end
543
605
  end
544
606
 
545
607
  def schema_loaded?
546
608
  defined?(@schema_loaded) && @schema_loaded
547
609
  end
548
610
 
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
561
- end
562
-
563
611
  def load_schema!
564
612
  unless table_name
565
613
  raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
@@ -577,26 +625,10 @@ module ActiveRecord
577
625
  default: column.default,
578
626
  user_provided_default: false
579
627
  )
628
+ alias_attribute :id_value, :id if name == "id"
580
629
  end
581
- end
582
630
 
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
631
+ super
600
632
  end
601
633
 
602
634
  # Guesses the table name, but does not decorate it with prefix and suffix information.
@@ -617,7 +649,7 @@ module ActiveRecord
617
649
 
618
650
  "#{full_table_name_prefix}#{contained}#{undecorated_table_name(model_name)}#{full_table_name_suffix}"
619
651
  else
620
- # STI subclasses always use their superclass' table.
652
+ # STI subclasses always use their superclass's table.
621
653
  base_class.table_name
622
654
  end
623
655
  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,26 @@ 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
287
+ # for updating or destroying nested attributes.
288
+ #
289
+ # === Testing
290
+ #
291
+ # If you are using ActionView::Helpers::FormHelper#fields_for, your integration
292
+ # tests should replicate the HTML structure it provides. For example;
293
+ #
294
+ # post members_path, params: {
295
+ # member: {
296
+ # name: 'joe',
297
+ # posts_attributes: {
298
+ # '0' => { title: 'Foo' },
299
+ # '1' => { title: 'Bar' }
300
+ # }
301
+ # }
302
+ # }
283
303
  module ClassMethods
284
304
  REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
285
305
 
@@ -375,11 +395,11 @@ module ActiveRecord
375
395
  end
376
396
  end
377
397
 
378
- # Returns ActiveRecord::AutosaveAssociation::marked_for_destruction? It's
398
+ # Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
379
399
  # used in conjunction with fields_for to build a form element for the
380
400
  # destruction of this association.
381
401
  #
382
- # See ActionView::Helpers::FormHelper::fields_for for more info.
402
+ # See ActionView::Helpers::FormHelper#fields_for for more info.
383
403
  def _destroy
384
404
  marked_for_destruction?
385
405
  end
@@ -0,0 +1,159 @@
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
+ # ==== Options
53
+ #
54
+ # * +:with+ - Any callable object that accepts the attribute's value as
55
+ # its sole argument, and returns it normalized.
56
+ # * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
57
+ # Defaults to +false+.
58
+ #
59
+ # ==== Examples
60
+ #
61
+ # class User < ActiveRecord::Base
62
+ # normalizes :email, with: -> email { email.strip.downcase }
63
+ # normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
64
+ # end
65
+ #
66
+ # user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
67
+ # user.email # => "cruise-control@example.com"
68
+ #
69
+ # user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
70
+ # user.email # => "cruise-control@example.com"
71
+ # user.email_before_type_cast # => "cruise-control@example.com"
72
+ #
73
+ # User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
74
+ # User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
75
+ #
76
+ # User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
77
+ # User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
78
+ #
79
+ # User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
80
+ def normalizes(*names, with:, apply_to_nil: false)
81
+ names.each do |name|
82
+ attribute(name) do |cast_type|
83
+ NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
84
+ end
85
+ end
86
+
87
+ self.normalized_attributes += names.map(&:to_sym)
88
+ end
89
+
90
+ # Normalizes a given +value+ using normalizations declared for +name+.
91
+ #
92
+ # ==== Examples
93
+ #
94
+ # class User < ActiveRecord::Base
95
+ # normalizes :email, with: -> email { email.strip.downcase }
96
+ # end
97
+ #
98
+ # User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
99
+ # # => "cruise-control@example.com"
100
+ def normalize_value_for(name, value)
101
+ type_for_attribute(name).cast(value)
102
+ end
103
+ end
104
+
105
+ private
106
+ def normalize_changed_in_place_attributes
107
+ self.class.normalized_attributes.each do |name|
108
+ normalize_attribute(name) if attribute_changed_in_place?(name)
109
+ end
110
+ end
111
+
112
+ class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
113
+ include ActiveModel::Type::SerializeCastValue
114
+
115
+ attr_reader :cast_type, :normalizer, :normalize_nil
116
+ alias :normalize_nil? :normalize_nil
117
+
118
+ def initialize(cast_type:, normalizer:, normalize_nil:)
119
+ @cast_type = cast_type
120
+ @normalizer = normalizer
121
+ @normalize_nil = normalize_nil
122
+ super(cast_type)
123
+ end
124
+
125
+ def cast(value)
126
+ normalize(super(value))
127
+ end
128
+
129
+ def serialize(value)
130
+ serialize_cast_value(cast(value))
131
+ end
132
+
133
+ def serialize_cast_value(value)
134
+ ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
135
+ end
136
+
137
+ def ==(other)
138
+ self.class == other.class &&
139
+ normalize_nil? == other.normalize_nil? &&
140
+ normalizer == other.normalizer &&
141
+ cast_type == other.cast_type
142
+ end
143
+ alias eql? ==
144
+
145
+ def hash
146
+ [self.class, cast_type, normalizer, normalize_nil?].hash
147
+ end
148
+
149
+ def inspect
150
+ Kernel.instance_method(:inspect).bind_call(self)
151
+ end
152
+
153
+ private
154
+ def normalize(value)
155
+ normalizer.call(value) unless value.nil? && !normalize_nil?
156
+ end
157
+ end
158
+ end
159
+ end