activerecord 7.0.0 → 7.1.2
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +1701 -1039
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -12
- data/lib/active_record/associations/collection_proxy.rb +22 -12
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +27 -17
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +20 -14
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader/through_association.rb +1 -1
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +362 -236
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +52 -34
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +172 -69
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +110 -28
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +56 -10
- data/lib/active_record/base.rb +10 -5
- data/lib/active_record/callbacks.rb +16 -32
- data/lib/active_record/coders/column_serializer.rb +61 -0
- data/lib/active_record/coders/json.rb +1 -1
- data/lib/active_record/coders/yaml_column.rb +70 -34
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +164 -89
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +52 -8
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +18 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +163 -29
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +302 -131
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +513 -106
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +217 -104
- data/lib/active_record/connection_adapters/column.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +23 -144
- data/lib/active_record/connection_adapters/mysql/quoting.rb +29 -12
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +10 -1
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +8 -2
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +38 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +16 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +75 -45
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +4 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +41 -8
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +6 -10
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +76 -6
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +372 -63
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +359 -197
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +22 -5
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +41 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +242 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +73 -96
- data/lib/active_record/core.rb +142 -153
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -0
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +87 -34
- data/lib/active_record/delegated_type.rb +9 -4
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +13 -14
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +8 -4
- data/lib/active_record/encryption/derived_secret_key_provider.rb +9 -3
- data/lib/active_record/encryption/deterministic_key_provider.rb +1 -1
- data/lib/active_record/encryption/encryptable_record.rb +38 -22
- data/lib/active_record/encryption/encrypted_attribute_type.rb +19 -8
- data/lib/active_record/encryption/encryptor.rb +7 -7
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +3 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +83 -86
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +3 -3
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message.rb +1 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/scheme.rb +20 -23
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -29
- data/lib/active_record/errors.rb +108 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/model_metadata.rb +14 -4
- data/lib/active_record/fixture_set/render_context.rb +2 -0
- data/lib/active_record/fixture_set/table_row.rb +29 -8
- data/lib/active_record/fixtures.rb +121 -73
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +57 -10
- data/lib/active_record/integration.rb +10 -10
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +32 -18
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +39 -17
- data/lib/active_record/marshalling.rb +56 -0
- data/lib/active_record/message_pack.rb +124 -0
- data/lib/active_record/middleware/database_selector/resolver.rb +4 -0
- data/lib/active_record/middleware/database_selector.rb +18 -13
- data/lib/active_record/middleware/shard_selector.rb +7 -5
- data/lib/active_record/migration/command_recorder.rb +108 -10
- data/lib/active_record/migration/compatibility.rb +158 -64
- data/lib/active_record/migration/default_strategy.rb +23 -0
- data/lib/active_record/migration/execution_strategy.rb +19 -0
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +274 -117
- data/lib/active_record/model_schema.rb +86 -54
- data/lib/active_record/nested_attributes.rb +24 -6
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +200 -47
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +87 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +16 -3
- data/lib/active_record/railtie.rb +128 -62
- data/lib/active_record/railties/controller_runtime.rb +12 -8
- data/lib/active_record/railties/databases.rake +145 -146
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +32 -5
- data/lib/active_record/reflection.rb +189 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +208 -83
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +31 -3
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +4 -6
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +430 -77
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +98 -41
- data/lib/active_record/result.rb +25 -9
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +57 -16
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +65 -23
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +5 -0
- data/lib/active_record/signed_id.rb +9 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +138 -107
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +17 -15
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +27 -15
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +39 -13
- data/lib/active_record/translation.rb +1 -1
- data/lib/active_record/type/adapter_specific_registry.rb +1 -8
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +8 -4
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +3 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +50 -5
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +143 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +1 -1
- data/lib/arel/nodes/and.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/filter.rb +1 -1
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +51 -15
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- 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
|
@@ -264,8 +284,10 @@ module ActiveRecord
|
|
264
284
|
|
265
285
|
# Computes the table name, (re)sets it internally, and returns it.
|
266
286
|
def reset_table_name # :nodoc:
|
267
|
-
self.table_name = if
|
268
|
-
|
287
|
+
self.table_name = if self == Base
|
288
|
+
nil
|
289
|
+
elsif abstract_class?
|
290
|
+
superclass.table_name
|
269
291
|
elsif superclass.abstract_class?
|
270
292
|
superclass.table_name || compute_table_name
|
271
293
|
else
|
@@ -303,11 +325,7 @@ module ActiveRecord
|
|
303
325
|
# The list of columns names the model should ignore. Ignored columns won't have attribute
|
304
326
|
# accessors defined, and won't be referenced in SQL queries.
|
305
327
|
def ignored_columns
|
306
|
-
|
307
|
-
@ignored_columns
|
308
|
-
else
|
309
|
-
superclass.ignored_columns
|
310
|
-
end
|
328
|
+
@ignored_columns || superclass.ignored_columns
|
311
329
|
end
|
312
330
|
|
313
331
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
@@ -328,7 +346,7 @@ module ActiveRecord
|
|
328
346
|
# # name :string, limit: 255
|
329
347
|
# # category :string, limit: 255
|
330
348
|
#
|
331
|
-
# self.ignored_columns
|
349
|
+
# self.ignored_columns += [:category]
|
332
350
|
# end
|
333
351
|
#
|
334
352
|
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
@@ -413,6 +431,12 @@ module ActiveRecord
|
|
413
431
|
@columns ||= columns_hash.values.freeze
|
414
432
|
end
|
415
433
|
|
434
|
+
def _returning_columns_for_insert # :nodoc:
|
435
|
+
@_returning_columns_for_insert ||= columns.filter_map do |c|
|
436
|
+
c.name if connection.return_value_after_insert?(c)
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
416
440
|
def attribute_types # :nodoc:
|
417
441
|
load_schema
|
418
442
|
@attribute_types ||= Hash.new(Type.default_value)
|
@@ -445,7 +469,7 @@ module ActiveRecord
|
|
445
469
|
end
|
446
470
|
|
447
471
|
# Returns the column object for the named attribute.
|
448
|
-
# Returns an
|
472
|
+
# Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
|
449
473
|
# named attribute does not exist.
|
450
474
|
#
|
451
475
|
# class Person < ActiveRecord::Base
|
@@ -503,7 +527,7 @@ module ActiveRecord
|
|
503
527
|
# when just after creating a table you want to populate it with some default
|
504
528
|
# values, e.g.:
|
505
529
|
#
|
506
|
-
# class CreateJobLevels < ActiveRecord::Migration[7.
|
530
|
+
# class CreateJobLevels < ActiveRecord::Migration[7.1]
|
507
531
|
# def up
|
508
532
|
# create_table :job_levels do |t|
|
509
533
|
# t.integer :id
|
@@ -531,35 +555,61 @@ module ActiveRecord
|
|
531
555
|
initialize_find_by_cache
|
532
556
|
end
|
533
557
|
|
558
|
+
def load_schema # :nodoc:
|
559
|
+
return if schema_loaded?
|
560
|
+
@load_schema_monitor.synchronize do
|
561
|
+
return if @columns_hash
|
562
|
+
|
563
|
+
load_schema!
|
564
|
+
|
565
|
+
@schema_loaded = true
|
566
|
+
rescue
|
567
|
+
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
|
568
|
+
raise
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
534
572
|
protected
|
535
573
|
def initialize_load_schema_monitor
|
536
574
|
@load_schema_monitor = Monitor.new
|
537
575
|
end
|
538
576
|
|
577
|
+
def reload_schema_from_cache(recursive = true)
|
578
|
+
@_returning_columns_for_insert = nil
|
579
|
+
@arel_table = nil
|
580
|
+
@column_names = nil
|
581
|
+
@symbol_column_to_string_name_hash = nil
|
582
|
+
@attribute_types = nil
|
583
|
+
@content_columns = nil
|
584
|
+
@default_attributes = nil
|
585
|
+
@column_defaults = nil
|
586
|
+
@attributes_builder = nil
|
587
|
+
@columns = nil
|
588
|
+
@columns_hash = nil
|
589
|
+
@schema_loaded = false
|
590
|
+
@attribute_names = nil
|
591
|
+
@yaml_encoder = nil
|
592
|
+
if recursive
|
593
|
+
subclasses.each do |descendant|
|
594
|
+
descendant.send(:reload_schema_from_cache)
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
539
599
|
private
|
540
600
|
def inherited(child_class)
|
541
601
|
super
|
542
602
|
child_class.initialize_load_schema_monitor
|
603
|
+
child_class.reload_schema_from_cache(false)
|
604
|
+
child_class.class_eval do
|
605
|
+
@ignored_columns = nil
|
606
|
+
end
|
543
607
|
end
|
544
608
|
|
545
609
|
def schema_loaded?
|
546
610
|
defined?(@schema_loaded) && @schema_loaded
|
547
611
|
end
|
548
612
|
|
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
613
|
def load_schema!
|
564
614
|
unless table_name
|
565
615
|
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
|
@@ -577,25 +627,7 @@ module ActiveRecord
|
|
577
627
|
default: column.default,
|
578
628
|
user_provided_default: false
|
579
629
|
)
|
580
|
-
|
581
|
-
end
|
582
|
-
|
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)
|
630
|
+
alias_attribute :id_value, :id if name == "id"
|
599
631
|
end
|
600
632
|
end
|
601
633
|
|
@@ -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,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
|
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
|
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
|
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
|
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
|
400
|
+
# See ActionView::Helpers::FormHelper#fields_for for more info.
|
383
401
|
def _destroy
|
384
402
|
marked_for_destruction?
|
385
403
|
end
|
@@ -0,0 +1,167 @@
|
|
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
|
+
names.each do |name|
|
90
|
+
attribute(name) do |cast_type|
|
91
|
+
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
self.normalized_attributes += names.map(&:to_sym)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Normalizes a given +value+ using normalizations declared for +name+.
|
99
|
+
#
|
100
|
+
# ==== Examples
|
101
|
+
#
|
102
|
+
# class User < ActiveRecord::Base
|
103
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
|
107
|
+
# # => "cruise-control@example.com"
|
108
|
+
def normalize_value_for(name, value)
|
109
|
+
type_for_attribute(name).cast(value)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
def normalize_changed_in_place_attributes
|
115
|
+
self.class.normalized_attributes.each do |name|
|
116
|
+
normalize_attribute(name) if attribute_changed_in_place?(name)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
|
121
|
+
include ActiveModel::Type::SerializeCastValue
|
122
|
+
|
123
|
+
attr_reader :cast_type, :normalizer, :normalize_nil
|
124
|
+
alias :normalize_nil? :normalize_nil
|
125
|
+
|
126
|
+
def initialize(cast_type:, normalizer:, normalize_nil:)
|
127
|
+
@cast_type = cast_type
|
128
|
+
@normalizer = normalizer
|
129
|
+
@normalize_nil = normalize_nil
|
130
|
+
super(cast_type)
|
131
|
+
end
|
132
|
+
|
133
|
+
def cast(value)
|
134
|
+
normalize(super(value))
|
135
|
+
end
|
136
|
+
|
137
|
+
def serialize(value)
|
138
|
+
serialize_cast_value(cast(value))
|
139
|
+
end
|
140
|
+
|
141
|
+
def serialize_cast_value(value)
|
142
|
+
ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
|
143
|
+
end
|
144
|
+
|
145
|
+
def ==(other)
|
146
|
+
self.class == other.class &&
|
147
|
+
normalize_nil? == other.normalize_nil? &&
|
148
|
+
normalizer == other.normalizer &&
|
149
|
+
cast_type == other.cast_type
|
150
|
+
end
|
151
|
+
alias eql? ==
|
152
|
+
|
153
|
+
def hash
|
154
|
+
[self.class, cast_type, normalizer, normalize_nil?].hash
|
155
|
+
end
|
156
|
+
|
157
|
+
def inspect
|
158
|
+
Kernel.instance_method(:inspect).bind_call(self)
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
def normalize(value)
|
163
|
+
normalizer.call(value) unless value.nil? && !normalize_nil?
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|