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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +515 -1268
- data/MIT-LICENSE +1 -1
- data/README.rdoc +31 -31
- data/examples/performance.rb +2 -2
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +2 -2
- data/lib/active_record/associations/alias_tracker.rb +25 -19
- data/lib/active_record/associations/association.rb +35 -12
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +23 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +22 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +3 -7
- data/lib/active_record/associations/builder/has_many.rb +3 -4
- data/lib/active_record/associations/builder/has_one.rb +3 -4
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +28 -17
- data/lib/active_record/associations/collection_proxy.rb +36 -13
- data/lib/active_record/associations/errors.rb +265 -0
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +28 -18
- 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/join_association.rb +27 -25
- data/lib/active_record/associations/join_dependency.rb +18 -14
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +33 -8
- data/lib/active_record/associations/preloader/branch.rb +7 -1
- data/lib/active_record/associations/preloader/through_association.rb +2 -4
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +7 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +378 -491
- data/lib/active_record/attribute_assignment.rb +1 -13
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/dirty.rb +53 -35
- data/lib/active_record/attribute_methods/primary_key.rb +45 -25
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +8 -7
- data/lib/active_record/attribute_methods/serialization.rb +153 -70
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +7 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +153 -40
- data/lib/active_record/attributes.rb +63 -48
- data/lib/active_record/autosave_association.rb +70 -38
- data/lib/active_record/base.rb +12 -8
- 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 +124 -132
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +297 -88
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +160 -45
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +215 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +83 -65
- 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 +319 -135
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +512 -126
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +282 -119
- 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 +27 -140
- data/lib/active_record/connection_adapters/mysql/quoting.rb +64 -52
- 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 +45 -14
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +152 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +101 -68
- data/lib/active_record/connection_adapters/pool_config.rb +20 -10
- 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 +101 -48
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- 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/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +94 -61
- 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 +151 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +379 -66
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +370 -203
- data/lib/active_record/connection_adapters/schema_cache.rb +302 -79
- data/lib/active_record/connection_adapters/sqlite3/column.rb +62 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +60 -43
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +61 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +20 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +64 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +321 -110
- 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 +229 -0
- data/lib/active_record/connection_adapters.rb +124 -1
- data/lib/active_record/connection_handling.rb +98 -106
- data/lib/active_record/core.rb +220 -177
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -2
- data/lib/active_record/database_configurations/database_config.rb +26 -5
- data/lib/active_record/database_configurations/hash_config.rb +52 -34
- data/lib/active_record/database_configurations/url_config.rb +37 -12
- data/lib/active_record/database_configurations.rb +88 -35
- data/lib/active_record/delegated_type.rb +40 -11
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/disable_joins_association_relation.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +2 -2
- 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 +47 -25
- data/lib/active_record/encryption/encrypted_attribute_type.rb +49 -14
- data/lib/active_record/encryption/encryptor.rb +25 -10
- 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_pack_message_serializer.rb +76 -0
- data/lib/active_record/encryption/message_serializer.rb +6 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/properties.rb +4 -4
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +23 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +131 -27
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +169 -99
- data/lib/active_record/future_result.rb +47 -8
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +34 -18
- data/lib/active_record/insert_all.rb +72 -22
- data/lib/active_record/integration.rb +13 -10
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +39 -24
- data/lib/active_record/locking/pessimistic.rb +8 -5
- data/lib/active_record/log_subscriber.rb +28 -27
- 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 +110 -13
- data/lib/active_record/migration/compatibility.rb +174 -64
- data/lib/active_record/migration/default_strategy.rb +22 -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 +292 -125
- data/lib/active_record/model_schema.rb +113 -112
- data/lib/active_record/nested_attributes.rb +35 -9
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +177 -345
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +19 -25
- data/lib/active_record/query_logs.rb +102 -51
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +34 -9
- data/lib/active_record/railtie.rb +153 -100
- data/lib/active_record/railties/controller_runtime.rb +24 -10
- data/lib/active_record/railties/databases.rake +148 -152
- 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 +278 -69
- data/lib/active_record/relation/batches/batch_enumerator.rb +20 -5
- data/lib/active_record/relation/batches.rb +198 -63
- data/lib/active_record/relation/calculations.rb +293 -108
- data/lib/active_record/relation/delegation.rb +31 -20
- data/lib/active_record/relation/finder_methods.rb +93 -18
- data/lib/active_record/relation/merger.rb +6 -6
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +38 -4
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +10 -7
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +28 -16
- data/lib/active_record/relation/query_attribute.rb +25 -1
- data/lib/active_record/relation/query_methods.rb +625 -107
- data/lib/active_record/relation/record_fetch_warning.rb +3 -0
- data/lib/active_record/relation/spawn_methods.rb +5 -4
- data/lib/active_record/relation/where_clause.rb +7 -19
- data/lib/active_record/relation.rb +602 -96
- data/lib/active_record/result.rb +55 -52
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +76 -30
- data/lib/active_record/schema.rb +39 -23
- data/lib/active_record/schema_dumper.rb +82 -30
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +20 -12
- data/lib/active_record/scoping/named.rb +3 -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 +29 -8
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +7 -3
- data/lib/active_record/tasks/database_tasks.rb +191 -121
- 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 +16 -7
- data/lib/active_record/test_fixtures.rb +174 -152
- data/lib/active_record/testing/query_assertions.rb +121 -0
- data/lib/active_record/timestamp.rb +31 -17
- data/lib/active_record/token_for.rb +123 -0
- data/lib/active_record/touch_later.rb +12 -7
- data/lib/active_record/transaction.rb +132 -0
- data/lib/active_record/transactions.rb +109 -27
- data/lib/active_record/translation.rb +1 -3
- 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 +9 -7
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type_caster/connection.rb +4 -4
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +12 -6
- 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 +63 -14
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +266 -30
- data/lib/arel/alias_predication.rb +1 -1
- data/lib/arel/collectors/bind.rb +2 -0
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +1 -1
- data/lib/arel/collectors/substitute_binds.rb +1 -1
- 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/binary.rb +6 -7
- data/lib/arel/nodes/bound_sql_literal.rb +65 -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/{and.rb → nary.rb} +9 -2
- data/lib/arel/nodes/node.rb +115 -5
- data/lib/arel/nodes/sql_literal.rb +13 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +6 -2
- data/lib/arel/predications.rb +3 -1
- data/lib/arel/select_manager.rb +1 -1
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +8 -3
- data/lib/arel/update_manager.rb +2 -1
- data/lib/arel/visitors/dot.rb +1 -0
- data/lib/arel/visitors/mysql.rb +17 -5
- data/lib/arel/visitors/postgresql.rb +1 -12
- data/lib/arel/visitors/to_sql.rb +112 -34
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +21 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- 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 +59 -17
- 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,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
|
|
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 ||=
|
|
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
|
|
268
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
418
|
+
schema_cache.data_source_exists?(table_name)
|
|
396
419
|
end
|
|
397
420
|
|
|
398
421
|
def attributes_builder # :nodoc:
|
|
399
|
-
|
|
422
|
+
@attributes_builder ||= begin
|
|
400
423
|
defaults = _default_attributes.except(*(column_names - [primary_key]))
|
|
401
|
-
|
|
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
|
|
417
|
-
|
|
418
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
526
|
+
connection_pool.active_connection&.clear_cache!
|
|
527
527
|
([self] + descendants).each(&:undefine_attribute_methods)
|
|
528
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
@@ -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
|
|
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.
|
|
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
|