activerecord 7.0.8.1 → 7.2.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 +642 -1925
- data/MIT-LICENSE +1 -1
- data/README.rdoc +29 -29
- 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 +26 -14
- data/lib/active_record/associations/collection_proxy.rb +29 -11
- 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 +21 -14
- data/lib/active_record/associations/has_many_through_association.rb +17 -7
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +30 -27
- data/lib/active_record/associations/join_dependency.rb +10 -10
- 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 +1 -3
- 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 +354 -485
- data/lib/active_record/attribute_assignment.rb +0 -4
- 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 +131 -32
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +11 -6
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +148 -33
- data/lib/active_record/attributes.rb +64 -50
- data/lib/active_record/autosave_association.rb +69 -37
- data/lib/active_record/base.rb +9 -5
- data/lib/active_record/callbacks.rb +11 -25
- 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 -42
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +123 -131
- 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 +323 -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 +217 -63
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -63
- 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 +137 -11
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +307 -129
- data/lib/active_record/connection_adapters/abstract/transaction.rb +367 -75
- data/lib/active_record/connection_adapters/abstract_adapter.rb +510 -111
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +278 -125
- 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 +26 -139
- data/lib/active_record/connection_adapters/mysql/quoting.rb +53 -54
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +9 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +6 -0
- data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +1 -1
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +25 -13
- 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 +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +100 -43
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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 +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +65 -61
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +3 -9
- 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 +370 -63
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +367 -201
- 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 +45 -46
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +22 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +14 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +50 -8
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +290 -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 +96 -104
- data/lib/active_record/core.rb +251 -176
- data/lib/active_record/counter_cache.rb +68 -34
- data/lib/active_record/database_configurations/connection_url_resolver.rb +8 -3
- 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 +87 -34
- data/lib/active_record/delegated_type.rb +39 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -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 +12 -19
- data/lib/active_record/encryption/context.rb +10 -3
- data/lib/active_record/encryption/contexts.rb +5 -1
- data/lib/active_record/encryption/derived_secret_key_provider.rb +8 -2
- data/lib/active_record/encryption/encryptable_record.rb +45 -21
- data/lib/active_record/encryption/encrypted_attribute_type.rb +47 -12
- data/lib/active_record/encryption/encryptor.rb +18 -3
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -69
- 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/key_provider.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 +3 -3
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +129 -28
- data/lib/active_record/errors.rb +151 -31
- data/lib/active_record/explain.rb +21 -12
- 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 +167 -97
- 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 +11 -8
- data/lib/active_record/internal_metadata.rb +124 -20
- data/lib/active_record/locking/optimistic.rb +8 -7
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +18 -22
- data/lib/active_record/marshalling.rb +59 -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 +6 -8
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +106 -8
- data/lib/active_record/migration/compatibility.rb +147 -5
- 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 +234 -117
- data/lib/active_record/model_schema.rb +90 -102
- data/lib/active_record/nested_attributes.rb +48 -11
- data/lib/active_record/normalization.rb +163 -0
- data/lib/active_record/persistence.rb +168 -339
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +18 -25
- data/lib/active_record/query_logs.rb +92 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +33 -8
- data/lib/active_record/railtie.rb +129 -85
- data/lib/active_record/railties/controller_runtime.rb +22 -7
- data/lib/active_record/railties/databases.rake +145 -154
- 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 +267 -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 +250 -93
- data/lib/active_record/relation/delegation.rb +30 -19
- 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 +18 -3
- 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 +2 -1
- data/lib/active_record/relation/query_methods.rb +576 -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 +580 -90
- data/lib/active_record/result.rb +49 -48
- data/lib/active_record/runtime_registry.rb +63 -1
- data/lib/active_record/sanitization.rb +70 -25
- data/lib/active_record/schema.rb +8 -7
- data/lib/active_record/schema_dumper.rb +63 -14
- data/lib/active_record/schema_migration.rb +75 -24
- data/lib/active_record/scoping/default.rb +15 -5
- 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/signed_id.rb +27 -6
- data/lib/active_record/statement_cache.rb +7 -7
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +190 -118
- data/lib/active_record/tasks/mysql_database_tasks.rb +15 -6
- data/lib/active_record/tasks/postgresql_database_tasks.rb +16 -13
- data/lib/active_record/tasks/sqlite_database_tasks.rb +16 -7
- data/lib/active_record/test_fixtures.rb +170 -155
- 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 +106 -24
- data/lib/active_record/translation.rb +0 -2
- 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 +1 -3
- 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 +9 -3
- data/lib/active_record/validations/numericality.rb +5 -4
- data/lib/active_record/validations/presence.rb +5 -28
- data/lib/active_record/validations/uniqueness.rb +61 -11
- data/lib/active_record/validations.rb +12 -5
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +247 -33
- 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/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/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} +5 -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/sqlite.rb +25 -0
- 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
|
@@ -139,6 +146,11 @@ module ActiveRecord
|
|
139
146
|
# your own model for something else, you can set +inheritance_column+:
|
140
147
|
#
|
141
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
|
142
154
|
|
143
155
|
##
|
144
156
|
# :singleton-method: inheritance_column=
|
@@ -180,8 +192,9 @@ module ActiveRecord
|
|
180
192
|
# artists, records => artists_records
|
181
193
|
# records, artists => artists_records
|
182
194
|
# music_artists, music_records => music_artists_records
|
195
|
+
# music.artists, music.records => music.artists_records
|
183
196
|
def self.derive_join_table_name(first_table, second_table) # :nodoc:
|
184
|
-
[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", "_")
|
185
198
|
end
|
186
199
|
|
187
200
|
module ClassMethods
|
@@ -265,19 +278,21 @@ module ActiveRecord
|
|
265
278
|
@table_name = value
|
266
279
|
@quoted_table_name = nil
|
267
280
|
@arel_table = nil
|
268
|
-
@sequence_name = nil unless
|
281
|
+
@sequence_name = nil unless @explicit_sequence_name
|
269
282
|
@predicate_builder = nil
|
270
283
|
end
|
271
284
|
|
272
285
|
# Returns a quoted version of the table name, used to construct SQL statements.
|
273
286
|
def quoted_table_name
|
274
|
-
@quoted_table_name ||=
|
287
|
+
@quoted_table_name ||= adapter_class.quote_table_name(table_name)
|
275
288
|
end
|
276
289
|
|
277
290
|
# Computes the table name, (re)sets it internally, and returns it.
|
278
291
|
def reset_table_name # :nodoc:
|
279
|
-
self.table_name = if
|
280
|
-
|
292
|
+
self.table_name = if self == Base
|
293
|
+
nil
|
294
|
+
elsif abstract_class?
|
295
|
+
superclass.table_name
|
281
296
|
elsif superclass.abstract_class?
|
282
297
|
superclass.table_name || compute_table_name
|
283
298
|
else
|
@@ -315,11 +330,7 @@ module ActiveRecord
|
|
315
330
|
# The list of columns names the model should ignore. Ignored columns won't have attribute
|
316
331
|
# accessors defined, and won't be referenced in SQL queries.
|
317
332
|
def ignored_columns
|
318
|
-
|
319
|
-
@ignored_columns
|
320
|
-
else
|
321
|
-
superclass.ignored_columns
|
322
|
-
end
|
333
|
+
@ignored_columns || superclass.ignored_columns
|
323
334
|
end
|
324
335
|
|
325
336
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
@@ -340,7 +351,7 @@ module ActiveRecord
|
|
340
351
|
# # name :string, limit: 255
|
341
352
|
# # category :string, limit: 255
|
342
353
|
#
|
343
|
-
# self.ignored_columns
|
354
|
+
# self.ignored_columns += [:category]
|
344
355
|
# end
|
345
356
|
#
|
346
357
|
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
@@ -368,7 +379,7 @@ module ActiveRecord
|
|
368
379
|
|
369
380
|
def reset_sequence_name # :nodoc:
|
370
381
|
@explicit_sequence_name = false
|
371
|
-
@sequence_name =
|
382
|
+
@sequence_name = with_connection { |c| c.default_sequence_name(table_name, primary_key) }
|
372
383
|
end
|
373
384
|
|
374
385
|
# Sets the name of the sequence to use when generating ids to the given
|
@@ -393,71 +404,52 @@ module ActiveRecord
|
|
393
404
|
# Determines if the primary key values should be selected from their
|
394
405
|
# corresponding sequence before the insert statement.
|
395
406
|
def prefetch_primary_key?
|
396
|
-
|
407
|
+
with_connection { |c| c.prefetch_primary_key?(table_name) }
|
397
408
|
end
|
398
409
|
|
399
410
|
# Returns the next value that will be used as the primary key on
|
400
411
|
# an insert statement.
|
401
412
|
def next_sequence_value
|
402
|
-
|
413
|
+
with_connection { |c| c.next_sequence_value(sequence_name) }
|
403
414
|
end
|
404
415
|
|
405
416
|
# Indicates whether the table associated with this class exists
|
406
417
|
def table_exists?
|
407
|
-
|
418
|
+
schema_cache.data_source_exists?(table_name)
|
408
419
|
end
|
409
420
|
|
410
421
|
def attributes_builder # :nodoc:
|
411
|
-
|
422
|
+
@attributes_builder ||= begin
|
412
423
|
defaults = _default_attributes.except(*(column_names - [primary_key]))
|
413
|
-
|
424
|
+
ActiveModel::AttributeSet::Builder.new(attribute_types, defaults)
|
414
425
|
end
|
415
|
-
@attributes_builder
|
416
426
|
end
|
417
427
|
|
418
428
|
def columns_hash # :nodoc:
|
419
|
-
load_schema
|
429
|
+
load_schema unless @columns_hash
|
420
430
|
@columns_hash
|
421
431
|
end
|
422
432
|
|
423
433
|
def columns
|
424
|
-
load_schema
|
425
434
|
@columns ||= columns_hash.values.freeze
|
426
435
|
end
|
427
436
|
|
428
|
-
def
|
429
|
-
|
430
|
-
|
437
|
+
def _returning_columns_for_insert(connection) # :nodoc:
|
438
|
+
@_returning_columns_for_insert ||= begin
|
439
|
+
auto_populated_columns = columns.filter_map do |c|
|
440
|
+
c.name if connection.return_value_after_insert?(c)
|
441
|
+
end
|
442
|
+
|
443
|
+
auto_populated_columns.empty? ? Array(primary_key) : auto_populated_columns
|
444
|
+
end
|
431
445
|
end
|
432
446
|
|
433
447
|
def yaml_encoder # :nodoc:
|
434
448
|
@yaml_encoder ||= ActiveModel::AttributeSet::YAMLEncoder.new(attribute_types)
|
435
449
|
end
|
436
450
|
|
437
|
-
# Returns the type of the attribute with the given name, after applying
|
438
|
-
# all modifiers. This method is the only valid source of information for
|
439
|
-
# anything related to the types of a model's attributes. This method will
|
440
|
-
# access the database and load the model's schema if it is required.
|
441
|
-
#
|
442
|
-
# The return value of this method will implement the interface described
|
443
|
-
# by ActiveModel::Type::Value (though the object itself may not subclass
|
444
|
-
# it).
|
445
|
-
#
|
446
|
-
# +attr_name+ The name of the attribute to retrieve the type for. Must be
|
447
|
-
# a string or a symbol.
|
448
|
-
def type_for_attribute(attr_name, &block)
|
449
|
-
attr_name = attr_name.to_s
|
450
|
-
attr_name = attribute_aliases[attr_name] || attr_name
|
451
|
-
|
452
|
-
if block
|
453
|
-
attribute_types.fetch(attr_name, &block)
|
454
|
-
else
|
455
|
-
attribute_types[attr_name]
|
456
|
-
end
|
457
|
-
end
|
458
|
-
|
459
451
|
# Returns the column object for the named attribute.
|
460
|
-
# Returns an
|
452
|
+
# Returns an ActiveRecord::ConnectionAdapters::NullColumn if the
|
461
453
|
# named attribute does not exist.
|
462
454
|
#
|
463
455
|
# class Person < ActiveRecord::Base
|
@@ -483,11 +475,6 @@ module ActiveRecord
|
|
483
475
|
@column_defaults ||= _default_attributes.deep_dup.to_hash.freeze
|
484
476
|
end
|
485
477
|
|
486
|
-
def _default_attributes # :nodoc:
|
487
|
-
load_schema
|
488
|
-
@default_attributes ||= ActiveModel::AttributeSet.new({})
|
489
|
-
end
|
490
|
-
|
491
478
|
# Returns an array of column names as strings.
|
492
479
|
def column_names
|
493
480
|
@column_names ||= columns.map(&:name).freeze
|
@@ -515,7 +502,7 @@ module ActiveRecord
|
|
515
502
|
# when just after creating a table you want to populate it with some default
|
516
503
|
# values, e.g.:
|
517
504
|
#
|
518
|
-
# class CreateJobLevels < ActiveRecord::Migration[7.
|
505
|
+
# class CreateJobLevels < ActiveRecord::Migration[7.2]
|
519
506
|
# def up
|
520
507
|
# create_table :job_levels do |t|
|
521
508
|
# t.integer :id
|
@@ -535,41 +522,67 @@ module ActiveRecord
|
|
535
522
|
# end
|
536
523
|
# end
|
537
524
|
def reset_column_information
|
538
|
-
|
525
|
+
connection_pool.active_connection&.clear_cache!
|
539
526
|
([self] + descendants).each(&:undefine_attribute_methods)
|
540
|
-
|
527
|
+
schema_cache.clear_data_source_cache!(table_name)
|
541
528
|
|
542
529
|
reload_schema_from_cache
|
543
530
|
initialize_find_by_cache
|
544
531
|
end
|
545
532
|
|
533
|
+
# Load the model's schema information either from the schema cache
|
534
|
+
# or directly from the database.
|
535
|
+
def load_schema
|
536
|
+
return if schema_loaded?
|
537
|
+
@load_schema_monitor.synchronize do
|
538
|
+
return if schema_loaded?
|
539
|
+
|
540
|
+
load_schema!
|
541
|
+
|
542
|
+
@schema_loaded = true
|
543
|
+
rescue
|
544
|
+
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
|
545
|
+
raise
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
546
549
|
protected
|
547
550
|
def initialize_load_schema_monitor
|
548
551
|
@load_schema_monitor = Monitor.new
|
549
552
|
end
|
550
553
|
|
554
|
+
def reload_schema_from_cache(recursive = true)
|
555
|
+
@_returning_columns_for_insert = nil
|
556
|
+
@arel_table = nil
|
557
|
+
@column_names = nil
|
558
|
+
@symbol_column_to_string_name_hash = nil
|
559
|
+
@content_columns = nil
|
560
|
+
@column_defaults = nil
|
561
|
+
@attributes_builder = nil
|
562
|
+
@columns = nil
|
563
|
+
@columns_hash = nil
|
564
|
+
@schema_loaded = false
|
565
|
+
@attribute_names = nil
|
566
|
+
@yaml_encoder = nil
|
567
|
+
if recursive
|
568
|
+
subclasses.each do |descendant|
|
569
|
+
descendant.send(:reload_schema_from_cache)
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
551
574
|
private
|
552
575
|
def inherited(child_class)
|
553
576
|
super
|
554
577
|
child_class.initialize_load_schema_monitor
|
578
|
+
child_class.reload_schema_from_cache(false)
|
579
|
+
child_class.class_eval do
|
580
|
+
@ignored_columns = nil
|
581
|
+
end
|
555
582
|
end
|
556
583
|
|
557
584
|
def schema_loaded?
|
558
|
-
|
559
|
-
end
|
560
|
-
|
561
|
-
def load_schema
|
562
|
-
return if schema_loaded?
|
563
|
-
@load_schema_monitor.synchronize do
|
564
|
-
return if defined?(@columns_hash) && @columns_hash
|
565
|
-
|
566
|
-
load_schema!
|
567
|
-
|
568
|
-
@schema_loaded = true
|
569
|
-
rescue
|
570
|
-
reload_schema_from_cache # If the schema loading failed half way through, we must reset the state.
|
571
|
-
raise
|
572
|
-
end
|
585
|
+
@schema_loaded
|
573
586
|
end
|
574
587
|
|
575
588
|
def load_schema!
|
@@ -577,38 +590,11 @@ module ActiveRecord
|
|
577
590
|
raise ActiveRecord::TableNotSpecified, "#{self} has no table configured. Set one with #{self}.table_name="
|
578
591
|
end
|
579
592
|
|
580
|
-
columns_hash =
|
593
|
+
columns_hash = schema_cache.columns_hash(table_name)
|
581
594
|
columns_hash = columns_hash.except(*ignored_columns) unless ignored_columns.empty?
|
582
595
|
@columns_hash = columns_hash.freeze
|
583
|
-
@columns_hash.each do |name, column|
|
584
|
-
type = connection.lookup_cast_type_from_column(column)
|
585
|
-
type = _convert_type_from_options(type)
|
586
|
-
define_attribute(
|
587
|
-
name,
|
588
|
-
type,
|
589
|
-
default: column.default,
|
590
|
-
user_provided_default: false
|
591
|
-
)
|
592
|
-
end
|
593
|
-
end
|
594
596
|
|
595
|
-
|
596
|
-
@arel_table = nil
|
597
|
-
@column_names = nil
|
598
|
-
@symbol_column_to_string_name_hash = nil
|
599
|
-
@attribute_types = nil
|
600
|
-
@content_columns = nil
|
601
|
-
@default_attributes = nil
|
602
|
-
@column_defaults = nil
|
603
|
-
@attributes_builder = nil
|
604
|
-
@columns = nil
|
605
|
-
@columns_hash = nil
|
606
|
-
@schema_loaded = false
|
607
|
-
@attribute_names = nil
|
608
|
-
@yaml_encoder = nil
|
609
|
-
subclasses.each do |descendant|
|
610
|
-
descendant.send(:reload_schema_from_cache)
|
611
|
-
end
|
597
|
+
_default_attributes # Precompute to cache DB-dependent attribute types
|
612
598
|
end
|
613
599
|
|
614
600
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -634,12 +620,14 @@ module ActiveRecord
|
|
634
620
|
end
|
635
621
|
end
|
636
622
|
|
637
|
-
def
|
623
|
+
def type_for_column(connection, column)
|
624
|
+
type = connection.lookup_cast_type_from_column(column)
|
625
|
+
|
638
626
|
if immutable_strings_by_default && type.respond_to?(:to_immutable_string)
|
639
|
-
type.to_immutable_string
|
640
|
-
else
|
641
|
-
type
|
627
|
+
type = type.to_immutable_string
|
642
628
|
end
|
629
|
+
|
630
|
+
type
|
643
631
|
end
|
644
632
|
end
|
645
633
|
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
|
@@ -501,12 +524,12 @@ module ActiveRecord
|
|
501
524
|
unless reject_new_record?(association_name, attributes)
|
502
525
|
association.reader.build(attributes.except(*UNASSIGNABLE_KEYS))
|
503
526
|
end
|
504
|
-
elsif existing_record = existing_records
|
527
|
+
elsif existing_record = find_record_by_id(existing_records, attributes["id"])
|
505
528
|
unless call_reject_if(association_name, attributes)
|
506
529
|
# Make sure we are operating on the actual object which is in the association's
|
507
530
|
# proxy_target array (either by finding it, or adding it if not found)
|
508
531
|
# Take into account that the proxy_target may have changed due to callbacks
|
509
|
-
target_record = association.target
|
532
|
+
target_record = find_record_by_id(association.target, attributes["id"])
|
510
533
|
if target_record
|
511
534
|
existing_record = target_record
|
512
535
|
else
|
@@ -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
|
@@ -594,5 +620,16 @@ module ActiveRecord
|
|
594
620
|
raise RecordNotFound.new("Couldn't find #{model} with ID=#{record_id} for #{self.class.name} with ID=#{id}",
|
595
621
|
model, "id", record_id)
|
596
622
|
end
|
623
|
+
|
624
|
+
def find_record_by_id(records, id)
|
625
|
+
return if records.empty?
|
626
|
+
|
627
|
+
if records.first.class.composite_primary_key?
|
628
|
+
id = Array(id).map(&:to_s)
|
629
|
+
records.find { |record| Array(record.id).map(&:to_s) == id }
|
630
|
+
else
|
631
|
+
records.find { |record| record.id.to_s == id.to_s }
|
632
|
+
end
|
633
|
+
end
|
597
634
|
end
|
598
635
|
end
|
@@ -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
|