activerecord 6.1.6 → 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 +1627 -983
- data/MIT-LICENSE +1 -1
- data/README.rdoc +18 -18
- data/lib/active_record/aggregations.rb +17 -14
- data/lib/active_record/association_relation.rb +1 -11
- data/lib/active_record/associations/association.rb +50 -19
- data/lib/active_record/associations/association_scope.rb +17 -12
- data/lib/active_record/associations/belongs_to_association.rb +28 -9
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +10 -2
- data/lib/active_record/associations/builder/association.rb +11 -5
- data/lib/active_record/associations/builder/belongs_to.rb +40 -14
- data/lib/active_record/associations/builder/collection_association.rb +10 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/has_many.rb +3 -2
- data/lib/active_record/associations/builder/has_one.rb +2 -1
- data/lib/active_record/associations/builder/singular_association.rb +6 -2
- data/lib/active_record/associations/collection_association.rb +35 -31
- data/lib/active_record/associations/collection_proxy.rb +30 -15
- data/lib/active_record/associations/disable_joins_association_scope.rb +59 -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 +12 -7
- data/lib/active_record/associations/has_one_association.rb +20 -10
- data/lib/active_record/associations/has_one_through_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +26 -16
- data/lib/active_record/associations/preloader/association.rb +207 -52
- data/lib/active_record/associations/preloader/batch.rb +48 -0
- data/lib/active_record/associations/preloader/branch.rb +147 -0
- data/lib/active_record/associations/preloader/through_association.rb +50 -14
- data/lib/active_record/associations/preloader.rb +50 -121
- data/lib/active_record/associations/singular_association.rb +9 -3
- data/lib/active_record/associations/through_association.rb +25 -14
- data/lib/active_record/associations.rb +439 -305
- data/lib/active_record/asynchronous_queries_tracker.rb +60 -0
- data/lib/active_record/attribute_assignment.rb +1 -3
- data/lib/active_record/attribute_methods/before_type_cast.rb +24 -2
- data/lib/active_record/attribute_methods/dirty.rb +73 -22
- data/lib/active_record/attribute_methods/primary_key.rb +78 -26
- data/lib/active_record/attribute_methods/query.rb +31 -19
- data/lib/active_record/attribute_methods/read.rb +25 -10
- data/lib/active_record/attribute_methods/serialization.rb +194 -37
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -3
- data/lib/active_record/attribute_methods/write.rb +10 -13
- data/lib/active_record/attribute_methods.rb +121 -40
- data/lib/active_record/attributes.rb +27 -38
- data/lib/active_record/autosave_association.rb +61 -30
- data/lib/active_record/base.rb +25 -2
- data/lib/active_record/callbacks.rb +18 -34
- 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 +367 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +211 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +78 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +96 -590
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -17
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +172 -50
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +77 -27
- data/lib/active_record/connection_adapters/abstract/quoting.rb +87 -73
- data/lib/active_record/connection_adapters/abstract/savepoints.rb +4 -3
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +21 -20
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +186 -31
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +360 -138
- data/lib/active_record/connection_adapters/abstract/transaction.rb +281 -59
- data/lib/active_record/connection_adapters/abstract_adapter.rb +631 -149
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +285 -156
- data/lib/active_record/connection_adapters/column.rb +13 -0
- data/lib/active_record/connection_adapters/mysql/column.rb +1 -0
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +25 -134
- data/lib/active_record/connection_adapters/mysql/quoting.rb +56 -25
- 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 +104 -53
- data/lib/active_record/connection_adapters/pool_config.rb +20 -11
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +30 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +89 -52
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/date.rb +8 -0
- data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +53 -14
- data/lib/active_record/connection_adapters/postgresql/oid/money.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +12 -3
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp.rb +15 -0
- data/lib/active_record/connection_adapters/postgresql/oid/timestamp_with_time_zone.rb +30 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +18 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +89 -56
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +28 -0
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +92 -2
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +153 -3
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +78 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +394 -74
- data/lib/active_record/connection_adapters/postgresql/utils.rb +9 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +509 -247
- data/lib/active_record/connection_adapters/schema_cache.rb +319 -90
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +72 -53
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +37 -21
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +7 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +43 -22
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +294 -102
- 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 +9 -6
- data/lib/active_record/connection_handling.rb +107 -136
- data/lib/active_record/core.rb +202 -223
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/connection_url_resolver.rb +2 -1
- data/lib/active_record/database_configurations/database_config.rb +21 -12
- data/lib/active_record/database_configurations/hash_config.rb +84 -16
- data/lib/active_record/database_configurations/url_config.rb +18 -12
- data/lib/active_record/database_configurations.rb +95 -59
- data/lib/active_record/delegated_type.rb +61 -15
- 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 +39 -0
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +101 -0
- data/lib/active_record/encryption/cipher.rb +53 -0
- data/lib/active_record/encryption/config.rb +68 -0
- data/lib/active_record/encryption/configurable.rb +60 -0
- data/lib/active_record/encryption/context.rb +42 -0
- data/lib/active_record/encryption/contexts.rb +76 -0
- data/lib/active_record/encryption/derived_secret_key_provider.rb +18 -0
- data/lib/active_record/encryption/deterministic_key_provider.rb +14 -0
- data/lib/active_record/encryption/encryptable_record.rb +224 -0
- data/lib/active_record/encryption/encrypted_attribute_type.rb +151 -0
- data/lib/active_record/encryption/encrypted_fixtures.rb +38 -0
- data/lib/active_record/encryption/encrypting_only_encryptor.rb +12 -0
- data/lib/active_record/encryption/encryptor.rb +155 -0
- data/lib/active_record/encryption/envelope_encryption_key_provider.rb +55 -0
- data/lib/active_record/encryption/errors.rb +15 -0
- data/lib/active_record/encryption/extended_deterministic_queries.rb +157 -0
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +28 -0
- data/lib/active_record/encryption/key.rb +28 -0
- data/lib/active_record/encryption/key_generator.rb +53 -0
- data/lib/active_record/encryption/key_provider.rb +46 -0
- data/lib/active_record/encryption/message.rb +33 -0
- data/lib/active_record/encryption/message_serializer.rb +92 -0
- data/lib/active_record/encryption/null_encryptor.rb +21 -0
- data/lib/active_record/encryption/properties.rb +76 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +24 -0
- data/lib/active_record/encryption/scheme.rb +96 -0
- data/lib/active_record/encryption.rb +56 -0
- data/lib/active_record/enum.rb +154 -63
- data/lib/active_record/errors.rb +171 -15
- data/lib/active_record/explain.rb +23 -3
- data/lib/active_record/explain_registry.rb +11 -6
- data/lib/active_record/explain_subscriber.rb +1 -1
- data/lib/active_record/fixture_set/file.rb +15 -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 +70 -14
- data/lib/active_record/fixture_set/table_rows.rb +4 -4
- data/lib/active_record/fixtures.rb +131 -86
- data/lib/active_record/future_result.rb +164 -0
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +81 -29
- data/lib/active_record/insert_all.rb +135 -22
- data/lib/active_record/integration.rb +11 -10
- data/lib/active_record/internal_metadata.rb +119 -33
- data/lib/active_record/legacy_yaml_adapter.rb +2 -39
- data/lib/active_record/locking/optimistic.rb +36 -21
- data/lib/active_record/locking/pessimistic.rb +15 -6
- data/lib/active_record/log_subscriber.rb +52 -19
- 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 +10 -10
- data/lib/active_record/middleware/database_selector.rb +23 -13
- data/lib/active_record/middleware/shard_selector.rb +62 -0
- data/lib/active_record/migration/command_recorder.rb +112 -14
- data/lib/active_record/migration/compatibility.rb +221 -48
- 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/join_table.rb +1 -1
- data/lib/active_record/migration/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +358 -171
- data/lib/active_record/model_schema.rb +120 -101
- data/lib/active_record/nested_attributes.rb +37 -18
- data/lib/active_record/no_touching.rb +3 -3
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +405 -85
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +174 -0
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +29 -6
- data/lib/active_record/railtie.rb +219 -43
- data/lib/active_record/railties/controller_runtime.rb +13 -9
- data/lib/active_record/railties/databases.rake +188 -252
- data/lib/active_record/railties/job_runtime.rb +23 -0
- data/lib/active_record/readonly_attributes.rb +41 -3
- data/lib/active_record/reflection.rb +241 -80
- data/lib/active_record/relation/batches/batch_enumerator.rb +23 -7
- data/lib/active_record/relation/batches.rb +192 -63
- data/lib/active_record/relation/calculations.rb +219 -90
- data/lib/active_record/relation/delegation.rb +27 -13
- data/lib/active_record/relation/finder_methods.rb +108 -51
- data/lib/active_record/relation/merger.rb +22 -13
- 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 +27 -20
- data/lib/active_record/relation/query_attribute.rb +30 -12
- data/lib/active_record/relation/query_methods.rb +654 -127
- data/lib/active_record/relation/record_fetch_warning.rb +7 -9
- data/lib/active_record/relation/spawn_methods.rb +20 -3
- data/lib/active_record/relation/where_clause.rb +10 -19
- data/lib/active_record/relation.rb +262 -120
- data/lib/active_record/result.rb +37 -11
- data/lib/active_record/runtime_registry.rb +18 -13
- data/lib/active_record/sanitization.rb +65 -20
- data/lib/active_record/schema.rb +36 -22
- data/lib/active_record/schema_dumper.rb +73 -24
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +72 -15
- data/lib/active_record/scoping/named.rb +5 -13
- data/lib/active_record/scoping.rb +65 -34
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/serialization.rb +6 -1
- data/lib/active_record/signed_id.rb +10 -8
- data/lib/active_record/store.rb +16 -11
- data/lib/active_record/suppressor.rb +13 -15
- data/lib/active_record/table_metadata.rb +16 -3
- data/lib/active_record/tasks/database_tasks.rb +225 -136
- data/lib/active_record/tasks/mysql_database_tasks.rb +16 -7
- data/lib/active_record/tasks/postgresql_database_tasks.rb +35 -26
- data/lib/active_record/tasks/sqlite_database_tasks.rb +15 -7
- data/lib/active_record/test_databases.rb +1 -1
- data/lib/active_record/test_fixtures.rb +123 -99
- data/lib/active_record/timestamp.rb +29 -18
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +48 -27
- data/lib/active_record/translation.rb +3 -3
- data/lib/active_record/type/adapter_specific_registry.rb +32 -14
- data/lib/active_record/type/hash_lookup_type_map.rb +34 -1
- data/lib/active_record/type/internal/timezone.rb +7 -2
- data/lib/active_record/type/serialized.rb +9 -5
- data/lib/active_record/type/time.rb +4 -0
- data/lib/active_record/type/type_map.rb +17 -20
- data/lib/active_record/type.rb +1 -2
- data/lib/active_record/validations/absence.rb +1 -1
- data/lib/active_record/validations/associated.rb +4 -4
- 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 +51 -6
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +335 -32
- data/lib/arel/attributes/attribute.rb +0 -8
- data/lib/arel/crud.rb +28 -22
- data/lib/arel/delete_manager.rb +18 -4
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/filter_predications.rb +9 -0
- data/lib/arel/insert_manager.rb +2 -3
- 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/casted.rb +1 -1
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/delete_statement.rb +12 -13
- data/lib/arel/nodes/filter.rb +10 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/function.rb +1 -0
- data/lib/arel/nodes/homogeneous_in.rb +1 -9
- data/lib/arel/nodes/insert_statement.rb +2 -2
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/select_core.rb +2 -2
- data/lib/arel/nodes/select_statement.rb +2 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes/update_statement.rb +8 -3
- data/lib/arel/nodes.rb +5 -0
- data/lib/arel/predications.rb +13 -3
- data/lib/arel/select_manager.rb +10 -4
- data/lib/arel/table.rb +9 -6
- data/lib/arel/tree_manager.rb +0 -12
- data/lib/arel/update_manager.rb +18 -4
- data/lib/arel/visitors/dot.rb +80 -90
- data/lib/arel/visitors/mysql.rb +16 -3
- data/lib/arel/visitors/postgresql.rb +0 -10
- data/lib/arel/visitors/to_sql.rb +139 -19
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +18 -3
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +1 -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
- data/lib/rails/generators/active_record/model/templates/abstract_base_class.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/model.rb.tt +1 -1
- data/lib/rails/generators/active_record/model/templates/module.rb.tt +2 -2
- data/lib/rails/generators/active_record/multi_db/multi_db_generator.rb +16 -0
- data/lib/rails/generators/active_record/multi_db/templates/multi_db.rb.tt +44 -0
- metadata +93 -13
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -67
@@ -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,9 +133,29 @@ 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
|
|
129
|
-
|
130
|
-
|
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.
|
131
156
|
|
157
|
+
included do
|
158
|
+
class_attribute :primary_key_prefix_type, instance_writer: false
|
132
159
|
class_attribute :table_name_prefix, instance_writer: false, default: ""
|
133
160
|
class_attribute :table_name_suffix, instance_writer: false, default: ""
|
134
161
|
class_attribute :schema_migrations_table_name, instance_accessor: false, default: "schema_migrations"
|
@@ -137,8 +164,15 @@ module ActiveRecord
|
|
137
164
|
class_attribute :implicit_order_column, instance_accessor: false
|
138
165
|
class_attribute :immutable_strings_by_default, instance_accessor: false
|
139
166
|
|
167
|
+
class_attribute :inheritance_column, instance_accessor: false, default: "type"
|
168
|
+
singleton_class.class_eval do
|
169
|
+
alias_method :_inheritance_column=, :inheritance_column=
|
170
|
+
private :_inheritance_column=
|
171
|
+
alias_method :inheritance_column=, :real_inheritance_column=
|
172
|
+
end
|
173
|
+
|
140
174
|
self.protected_environments = ["production"]
|
141
|
-
|
175
|
+
|
142
176
|
self.ignored_columns = [].freeze
|
143
177
|
|
144
178
|
delegate :type_for_attribute, :column_for_attribute, to: :class
|
@@ -153,8 +187,9 @@ module ActiveRecord
|
|
153
187
|
# artists, records => artists_records
|
154
188
|
# records, artists => artists_records
|
155
189
|
# music_artists, music_records => music_artists_records
|
190
|
+
# music.artists, music.records => music.artists_records
|
156
191
|
def self.derive_join_table_name(first_table, second_table) # :nodoc:
|
157
|
-
[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", "_")
|
158
193
|
end
|
159
194
|
|
160
195
|
module ClassMethods
|
@@ -197,6 +232,21 @@ module ActiveRecord
|
|
197
232
|
# the table name guess for an Invoice class becomes "myapp_invoices".
|
198
233
|
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
|
199
234
|
#
|
235
|
+
# Active Model Naming's +model_name+ is the base name used to guess the
|
236
|
+
# table name. In case a custom Active Model Name is defined, it will be
|
237
|
+
# used for the table name as well:
|
238
|
+
#
|
239
|
+
# class PostRecord < ActiveRecord::Base
|
240
|
+
# class << self
|
241
|
+
# def model_name
|
242
|
+
# ActiveModel::Name.new(self, nil, "Post")
|
243
|
+
# end
|
244
|
+
# end
|
245
|
+
# end
|
246
|
+
#
|
247
|
+
# PostRecord.table_name
|
248
|
+
# # => "posts"
|
249
|
+
#
|
200
250
|
# You can also set your own table name explicitly:
|
201
251
|
#
|
202
252
|
# class Mouse < ActiveRecord::Base
|
@@ -233,9 +283,11 @@ module ActiveRecord
|
|
233
283
|
end
|
234
284
|
|
235
285
|
# Computes the table name, (re)sets it internally, and returns it.
|
236
|
-
def reset_table_name
|
237
|
-
self.table_name = if
|
238
|
-
|
286
|
+
def reset_table_name # :nodoc:
|
287
|
+
self.table_name = if self == Base
|
288
|
+
nil
|
289
|
+
elsif abstract_class?
|
290
|
+
superclass.table_name
|
239
291
|
elsif superclass.abstract_class?
|
240
292
|
superclass.table_name || compute_table_name
|
241
293
|
else
|
@@ -243,11 +295,11 @@ module ActiveRecord
|
|
243
295
|
end
|
244
296
|
end
|
245
297
|
|
246
|
-
def full_table_name_prefix
|
298
|
+
def full_table_name_prefix # :nodoc:
|
247
299
|
(module_parents.detect { |p| p.respond_to?(:table_name_prefix) } || self).table_name_prefix
|
248
300
|
end
|
249
301
|
|
250
|
-
def full_table_name_suffix
|
302
|
+
def full_table_name_suffix # :nodoc:
|
251
303
|
(module_parents.detect { |p| p.respond_to?(:table_name_suffix) } || self).table_name_suffix
|
252
304
|
end
|
253
305
|
|
@@ -266,33 +318,14 @@ module ActiveRecord
|
|
266
318
|
@protected_environments = environments.map(&:to_s)
|
267
319
|
end
|
268
320
|
|
269
|
-
|
270
|
-
|
271
|
-
#
|
272
|
-
# The default inheritance column name is +type+, which means it's a
|
273
|
-
# reserved word inside Active Record. To be able to use single-table
|
274
|
-
# inheritance with another column name, or to use the column +type+ in
|
275
|
-
# your own model for something else, you can set +inheritance_column+:
|
276
|
-
#
|
277
|
-
# self.inheritance_column = 'zoink'
|
278
|
-
def inheritance_column
|
279
|
-
(@inheritance_column ||= nil) || superclass.inheritance_column
|
280
|
-
end
|
281
|
-
|
282
|
-
# Sets the value of inheritance_column
|
283
|
-
def inheritance_column=(value)
|
284
|
-
@inheritance_column = value.to_s
|
285
|
-
@explicit_inheritance_column = true
|
321
|
+
def real_inheritance_column=(value) # :nodoc:
|
322
|
+
self._inheritance_column = value.to_s
|
286
323
|
end
|
287
324
|
|
288
325
|
# The list of columns names the model should ignore. Ignored columns won't have attribute
|
289
326
|
# accessors defined, and won't be referenced in SQL queries.
|
290
327
|
def ignored_columns
|
291
|
-
|
292
|
-
@ignored_columns
|
293
|
-
else
|
294
|
-
superclass.ignored_columns
|
295
|
-
end
|
328
|
+
@ignored_columns || superclass.ignored_columns
|
296
329
|
end
|
297
330
|
|
298
331
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
@@ -313,7 +346,7 @@ module ActiveRecord
|
|
313
346
|
# # name :string, limit: 255
|
314
347
|
# # category :string, limit: 255
|
315
348
|
#
|
316
|
-
# self.ignored_columns
|
349
|
+
# self.ignored_columns += [:category]
|
317
350
|
# end
|
318
351
|
#
|
319
352
|
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
@@ -339,7 +372,7 @@ module ActiveRecord
|
|
339
372
|
end
|
340
373
|
end
|
341
374
|
|
342
|
-
def reset_sequence_name
|
375
|
+
def reset_sequence_name # :nodoc:
|
343
376
|
@explicit_sequence_name = false
|
344
377
|
@sequence_name = connection.default_sequence_name(table_name, primary_key)
|
345
378
|
end
|
@@ -398,6 +431,12 @@ module ActiveRecord
|
|
398
431
|
@columns ||= columns_hash.values.freeze
|
399
432
|
end
|
400
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
|
+
|
401
440
|
def attribute_types # :nodoc:
|
402
441
|
load_schema
|
403
442
|
@attribute_types ||= Hash.new(Type.default_value)
|
@@ -430,7 +469,7 @@ module ActiveRecord
|
|
430
469
|
end
|
431
470
|
|
432
471
|
# Returns the column object for the named attribute.
|
433
|
-
# Returns an
|
472
|
+
# Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
|
434
473
|
# named attribute does not exist.
|
435
474
|
#
|
436
475
|
# class Person < ActiveRecord::Base
|
@@ -486,9 +525,9 @@ module ActiveRecord
|
|
486
525
|
#
|
487
526
|
# The most common usage pattern for this method is probably in a migration,
|
488
527
|
# when just after creating a table you want to populate it with some default
|
489
|
-
# values,
|
528
|
+
# values, e.g.:
|
490
529
|
#
|
491
|
-
# class CreateJobLevels < ActiveRecord::Migration[
|
530
|
+
# class CreateJobLevels < ActiveRecord::Migration[7.1]
|
492
531
|
# def up
|
493
532
|
# create_table :job_levels do |t|
|
494
533
|
# t.integer :id
|
@@ -516,35 +555,61 @@ module ActiveRecord
|
|
516
555
|
initialize_find_by_cache
|
517
556
|
end
|
518
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
|
+
|
519
572
|
protected
|
520
573
|
def initialize_load_schema_monitor
|
521
574
|
@load_schema_monitor = Monitor.new
|
522
575
|
end
|
523
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
|
+
|
524
599
|
private
|
525
600
|
def inherited(child_class)
|
526
601
|
super
|
527
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
|
528
607
|
end
|
529
608
|
|
530
609
|
def schema_loaded?
|
531
610
|
defined?(@schema_loaded) && @schema_loaded
|
532
611
|
end
|
533
612
|
|
534
|
-
def load_schema
|
535
|
-
return if schema_loaded?
|
536
|
-
@load_schema_monitor.synchronize do
|
537
|
-
return if defined?(@columns_hash) && @columns_hash
|
538
|
-
|
539
|
-
load_schema!
|
540
|
-
|
541
|
-
@schema_loaded = true
|
542
|
-
rescue
|
543
|
-
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
|
544
|
-
raise
|
545
|
-
end
|
546
|
-
end
|
547
|
-
|
548
613
|
def load_schema!
|
549
614
|
unless table_name
|
550
615
|
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
|
@@ -556,39 +621,19 @@ module ActiveRecord
|
|
556
621
|
@columns_hash.each do |name, column|
|
557
622
|
type = connection.lookup_cast_type_from_column(column)
|
558
623
|
type = _convert_type_from_options(type)
|
559
|
-
warn_if_deprecated_type(column)
|
560
624
|
define_attribute(
|
561
625
|
name,
|
562
626
|
type,
|
563
627
|
default: column.default,
|
564
628
|
user_provided_default: false
|
565
629
|
)
|
566
|
-
|
567
|
-
end
|
568
|
-
|
569
|
-
def reload_schema_from_cache
|
570
|
-
@arel_table = nil
|
571
|
-
@column_names = nil
|
572
|
-
@symbol_column_to_string_name_hash = nil
|
573
|
-
@attribute_types = nil
|
574
|
-
@content_columns = nil
|
575
|
-
@default_attributes = nil
|
576
|
-
@column_defaults = nil
|
577
|
-
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
|
578
|
-
@attributes_builder = nil
|
579
|
-
@columns = nil
|
580
|
-
@columns_hash = nil
|
581
|
-
@schema_loaded = false
|
582
|
-
@attribute_names = nil
|
583
|
-
@yaml_encoder = nil
|
584
|
-
direct_descendants.each do |descendant|
|
585
|
-
descendant.send(:reload_schema_from_cache)
|
630
|
+
alias_attribute :id_value, :id if name == "id"
|
586
631
|
end
|
587
632
|
end
|
588
633
|
|
589
634
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
590
|
-
def undecorated_table_name(
|
591
|
-
table_name =
|
635
|
+
def undecorated_table_name(model_name)
|
636
|
+
table_name = model_name.to_s.demodulize.underscore
|
592
637
|
pluralize_table_names ? table_name.pluralize : table_name
|
593
638
|
end
|
594
639
|
|
@@ -602,9 +647,9 @@ module ActiveRecord
|
|
602
647
|
contained += "_"
|
603
648
|
end
|
604
649
|
|
605
|
-
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(
|
650
|
+
"#{full_table_name_prefix}#{contained}#{undecorated_table_name(model_name)}#{full_table_name_suffix}"
|
606
651
|
else
|
607
|
-
# STI subclasses always use their superclass' table.
|
652
|
+
# STI subclasses always use their superclass's table.
|
608
653
|
base_class.table_name
|
609
654
|
end
|
610
655
|
end
|
@@ -616,32 +661,6 @@ module ActiveRecord
|
|
616
661
|
type
|
617
662
|
end
|
618
663
|
end
|
619
|
-
|
620
|
-
def warn_if_deprecated_type(column)
|
621
|
-
return if attributes_to_define_after_schema_loads.key?(column.name)
|
622
|
-
return unless column.respond_to?(:oid)
|
623
|
-
|
624
|
-
if column.array?
|
625
|
-
array_arguments = ", array: true"
|
626
|
-
else
|
627
|
-
array_arguments = ""
|
628
|
-
end
|
629
|
-
|
630
|
-
if column.sql_type.start_with?("interval")
|
631
|
-
precision_arguments = column.precision.presence && ", precision: #{column.precision}"
|
632
|
-
ActiveSupport::Deprecation.warn(<<~WARNING)
|
633
|
-
The behavior of the `:interval` type will be changing in Rails 7.0
|
634
|
-
to return an `ActiveSupport::Duration` object. If you'd like to keep
|
635
|
-
the old behavior, you can add this line to #{self.name} model:
|
636
|
-
|
637
|
-
attribute :#{column.name}, :string#{precision_arguments}#{array_arguments}
|
638
|
-
|
639
|
-
If you'd like the new behavior today, you can add this line:
|
640
|
-
|
641
|
-
attribute :#{column.name}, :interval#{precision_arguments}#{array_arguments}
|
642
|
-
WARNING
|
643
|
-
end
|
644
|
-
end
|
645
664
|
end
|
646
665
|
end
|
647
666
|
end
|
@@ -5,7 +5,7 @@ require "active_support/core_ext/module/redefine_method"
|
|
5
5
|
require "active_support/core_ext/hash/indifferent_access"
|
6
6
|
|
7
7
|
module ActiveRecord
|
8
|
-
module NestedAttributes
|
8
|
+
module NestedAttributes # :nodoc:
|
9
9
|
class TooManyRecords < ActiveRecordError
|
10
10
|
end
|
11
11
|
|
@@ -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
|
@@ -180,7 +180,7 @@ module ActiveRecord
|
|
180
180
|
# member.posts.second.title # => '[UPDATED] other post'
|
181
181
|
#
|
182
182
|
# However, the above applies if the parent model is being updated as well.
|
183
|
-
# For example,
|
183
|
+
# For example, if you wanted to create a +member+ named _joe_ and wanted to
|
184
184
|
# update the +posts+ at the same time, that would give an
|
185
185
|
# ActiveRecord::RecordNotFound error.
|
186
186
|
#
|
@@ -245,18 +245,19 @@ module ActiveRecord
|
|
245
245
|
#
|
246
246
|
# === Validating the presence of a parent model
|
247
247
|
#
|
248
|
-
#
|
249
|
-
#
|
250
|
-
#
|
248
|
+
# The +belongs_to+ association validates the presence of the parent model
|
249
|
+
# by default. You can disable this behavior by specifying <code>optional: true</code>.
|
250
|
+
# This can be used, for example, when conditionally validating the presence
|
251
|
+
# of the parent model:
|
251
252
|
#
|
252
|
-
# class
|
253
|
-
# has_many :
|
254
|
-
# accepts_nested_attributes_for :
|
253
|
+
# class Veterinarian < ActiveRecord::Base
|
254
|
+
# has_many :patients, inverse_of: :veterinarian
|
255
|
+
# accepts_nested_attributes_for :patients
|
255
256
|
# end
|
256
257
|
#
|
257
|
-
# class
|
258
|
-
# belongs_to :
|
259
|
-
#
|
258
|
+
# class Patient < ActiveRecord::Base
|
259
|
+
# belongs_to :veterinarian, inverse_of: :patients, optional: true
|
260
|
+
# validates :veterinarian, presence: true, unless: -> { awaiting_intake }
|
260
261
|
# end
|
261
262
|
#
|
262
263
|
# Note that if you do not specify the +:inverse_of+ option, then
|
@@ -279,6 +280,24 @@ module ActiveRecord
|
|
279
280
|
# member = Member.new
|
280
281
|
# member.avatar_attributes = {icon: 'sad'}
|
281
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
|
+
# }
|
282
301
|
module ClassMethods
|
283
302
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
|
284
303
|
|
@@ -288,7 +307,7 @@ module ActiveRecord
|
|
288
307
|
# [:allow_destroy]
|
289
308
|
# If true, destroys any members from the attributes hash with a
|
290
309
|
# <tt>_destroy</tt> key and a value that evaluates to +true+
|
291
|
-
# (e.g. 1, '1', true, or 'true'). This option is
|
310
|
+
# (e.g. 1, '1', true, or 'true'). This option is false by default.
|
292
311
|
# [:reject_if]
|
293
312
|
# Allows you to specify a Proc or a Symbol pointing to a method
|
294
313
|
# that checks whether a record should be built for a certain attribute
|
@@ -313,11 +332,11 @@ module ActiveRecord
|
|
313
332
|
# nested attributes are going to be used when an associated record already
|
314
333
|
# exists. In general, an existing record may either be updated with the
|
315
334
|
# new set of attribute values or be replaced by a wholly new record
|
316
|
-
# containing those values. By default the +:update_only+ option is
|
335
|
+
# containing those values. By default the +:update_only+ option is false
|
317
336
|
# and the nested attributes are used to update the existing record only
|
318
337
|
# if they include the record's <tt>:id</tt> value. Otherwise a new
|
319
338
|
# record will be instantiated and used to replace the existing one.
|
320
|
-
# However if the +:update_only+ option is
|
339
|
+
# However if the +:update_only+ option is true, the nested attributes
|
321
340
|
# are used to update the record's attributes always, regardless of
|
322
341
|
# whether the <tt>:id</tt> is present. The option is ignored for collection
|
323
342
|
# associations.
|
@@ -374,11 +393,11 @@ module ActiveRecord
|
|
374
393
|
end
|
375
394
|
end
|
376
395
|
|
377
|
-
# Returns ActiveRecord::AutosaveAssociation
|
396
|
+
# Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
|
378
397
|
# used in conjunction with fields_for to build a form element for the
|
379
398
|
# destruction of this association.
|
380
399
|
#
|
381
|
-
# See ActionView::Helpers::FormHelper
|
400
|
+
# See ActionView::Helpers::FormHelper#fields_for for more info.
|
382
401
|
def _destroy
|
383
402
|
marked_for_destruction?
|
384
403
|
end
|
@@ -486,7 +505,7 @@ module ActiveRecord
|
|
486
505
|
existing_records = if association.loaded?
|
487
506
|
association.target
|
488
507
|
else
|
489
|
-
attribute_ids = attributes_collection.
|
508
|
+
attribute_ids = attributes_collection.filter_map { |a| a["id"] || a[:id] }
|
490
509
|
attribute_ids.empty? ? [] : association.scope.where(association.klass.primary_key => attribute_ids)
|
491
510
|
end
|
492
511
|
|
@@ -26,20 +26,20 @@ module ActiveRecord
|
|
26
26
|
end
|
27
27
|
|
28
28
|
class << self
|
29
|
-
def apply_to(klass)
|
29
|
+
def apply_to(klass) # :nodoc:
|
30
30
|
klasses.push(klass)
|
31
31
|
yield
|
32
32
|
ensure
|
33
33
|
klasses.pop
|
34
34
|
end
|
35
35
|
|
36
|
-
def applied_to?(klass)
|
36
|
+
def applied_to?(klass) # :nodoc:
|
37
37
|
klasses.any? { |k| k >= klass }
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
def klasses
|
42
|
-
|
42
|
+
ActiveSupport::IsolatedExecutionState[:active_record_no_touching_classes] ||= []
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -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
|