activerecord 7.0.8.7 → 7.1.0.beta1
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 +1339 -1572
- data/MIT-LICENSE +1 -1
- data/README.rdoc +15 -16
- data/lib/active_record/aggregations.rb +16 -13
- data/lib/active_record/association_relation.rb +1 -1
- data/lib/active_record/associations/association.rb +18 -3
- data/lib/active_record/associations/association_scope.rb +16 -9
- data/lib/active_record/associations/belongs_to_association.rb +14 -6
- data/lib/active_record/associations/builder/association.rb +3 -3
- data/lib/active_record/associations/builder/belongs_to.rb +21 -8
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +1 -5
- data/lib/active_record/associations/builder/singular_association.rb +4 -0
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +16 -11
- data/lib/active_record/associations/foreign_association.rb +10 -3
- data/lib/active_record/associations/has_many_association.rb +20 -13
- data/lib/active_record/associations/has_many_through_association.rb +10 -6
- data/lib/active_record/associations/has_one_association.rb +10 -3
- data/lib/active_record/associations/join_dependency.rb +10 -8
- data/lib/active_record/associations/preloader/association.rb +27 -6
- data/lib/active_record/associations/preloader.rb +12 -9
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +193 -97
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/before_type_cast.rb +17 -0
- data/lib/active_record/attribute_methods/dirty.rb +40 -26
- data/lib/active_record/attribute_methods/primary_key.rb +76 -24
- data/lib/active_record/attribute_methods/query.rb +28 -16
- data/lib/active_record/attribute_methods/read.rb +18 -5
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/write.rb +3 -3
- data/lib/active_record/attribute_methods.rb +105 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +55 -9
- data/lib/active_record/base.rb +7 -2
- data/lib/active_record/callbacks.rb +10 -24
- 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 +163 -88
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +2 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +63 -43
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +109 -32
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +60 -22
- data/lib/active_record/connection_adapters/abstract/quoting.rb +41 -6
- 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 +289 -122
- data/lib/active_record/connection_adapters/abstract/transaction.rb +280 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +502 -91
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +200 -108
- 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 +22 -143
- data/lib/active_record/connection_adapters/mysql/quoting.rb +16 -12
- 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 +17 -12
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +148 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +98 -53
- data/lib/active_record/connection_adapters/pool_config.rb +14 -5
- data/lib/active_record/connection_adapters/pool_manager.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/column.rb +1 -2
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +76 -29
- data/lib/active_record/connection_adapters/postgresql/oid/range.rb +11 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +9 -6
- 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 +131 -2
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +42 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +351 -54
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +336 -168
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +42 -36
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +4 -3
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +1 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +26 -7
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +162 -77
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +98 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +254 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +71 -94
- data/lib/active_record/core.rb +128 -138
- data/lib/active_record/counter_cache.rb +46 -25
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +22 -12
- data/lib/active_record/database_configurations/url_config.rb +17 -11
- data/lib/active_record/database_configurations.rb +86 -33
- data/lib/active_record/delegated_type.rb +8 -3
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +2 -0
- 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 +36 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +17 -6
- data/lib/active_record/encryption/extended_deterministic_queries.rb +66 -54
- data/lib/active_record/encryption/extended_deterministic_uniqueness_validator.rb +2 -2
- data/lib/active_record/encryption/key_generator.rb +12 -1
- data/lib/active_record/encryption/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +19 -22
- data/lib/active_record/encryption.rb +1 -0
- data/lib/active_record/enum.rb +113 -26
- data/lib/active_record/errors.rb +89 -15
- data/lib/active_record/explain.rb +23 -3
- 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 +119 -71
- data/lib/active_record/future_result.rb +30 -5
- data/lib/active_record/gem_version.rb +4 -4
- data/lib/active_record/inheritance.rb +30 -16
- data/lib/active_record/insert_all.rb +55 -8
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +118 -30
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +5 -7
- data/lib/active_record/middleware/shard_selector.rb +3 -1
- data/lib/active_record/migration/command_recorder.rb +100 -4
- data/lib/active_record/migration/compatibility.rb +131 -5
- 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.rb +213 -109
- data/lib/active_record/model_schema.rb +47 -27
- data/lib/active_record/nested_attributes.rb +28 -3
- data/lib/active_record/normalization.rb +158 -0
- data/lib/active_record/persistence.rb +183 -33
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +3 -21
- data/lib/active_record/query_logs.rb +77 -52
- data/lib/active_record/query_logs_formatter.rb +41 -0
- data/lib/active_record/querying.rb +15 -2
- data/lib/active_record/railtie.rb +107 -45
- data/lib/active_record/railties/controller_runtime.rb +10 -5
- data/lib/active_record/railties/databases.rake +139 -145
- 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 +169 -45
- data/lib/active_record/relation/batches/batch_enumerator.rb +5 -3
- data/lib/active_record/relation/batches.rb +190 -61
- data/lib/active_record/relation/calculations.rb +152 -63
- data/lib/active_record/relation/delegation.rb +22 -8
- data/lib/active_record/relation/finder_methods.rb +85 -15
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +5 -1
- data/lib/active_record/relation/predicate_builder.rb +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +351 -62
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +76 -35
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +10 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +41 -7
- data/lib/active_record/schema_migration.rb +68 -33
- data/lib/active_record/scoping/default.rb +15 -5
- data/lib/active_record/scoping/named.rb +2 -2
- data/lib/active_record/scoping.rb +2 -1
- data/lib/active_record/secure_password.rb +60 -0
- data/lib/active_record/secure_token.rb +21 -3
- data/lib/active_record/signed_id.rb +7 -5
- data/lib/active_record/store.rb +8 -8
- data/lib/active_record/suppressor.rb +3 -1
- data/lib/active_record/table_metadata.rb +10 -1
- data/lib/active_record/tasks/database_tasks.rb +127 -105
- 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 +14 -7
- data/lib/active_record/test_fixtures.rb +113 -96
- data/lib/active_record/timestamp.rb +26 -14
- data/lib/active_record/token_for.rb +113 -0
- data/lib/active_record/touch_later.rb +11 -6
- data/lib/active_record/transactions.rb +36 -10
- 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/time.rb +4 -0
- data/lib/active_record/validations/absence.rb +1 -1
- 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 +47 -2
- data/lib/active_record/validations.rb +8 -4
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +121 -16
- data/lib/arel/errors.rb +10 -0
- data/lib/arel/factory_methods.rb +4 -0
- data/lib/arel/nodes/binary.rb +6 -1
- data/lib/arel/nodes/bound_sql_literal.rb +61 -0
- data/lib/arel/nodes/cte.rb +36 -0
- data/lib/arel/nodes/fragments.rb +35 -0
- data/lib/arel/nodes/homogeneous_in.rb +0 -8
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +81 -17
- data/lib/arel/visitors/visitor.rb +2 -2
- data/lib/arel.rb +16 -2
- data/lib/rails/generators/active_record/application_record/USAGE +8 -0
- data/lib/rails/generators/active_record/migration.rb +3 -1
- data/lib/rails/generators/active_record/model/USAGE +113 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +15 -6
- metadata +52 -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
|
@@ -180,8 +187,9 @@ module ActiveRecord
|
|
180
187
|
# artists, records => artists_records
|
181
188
|
# records, artists => artists_records
|
182
189
|
# music_artists, music_records => music_artists_records
|
190
|
+
# music.artists, music.records => music.artists_records
|
183
191
|
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", "_")
|
192
|
+
[first_table.to_s, second_table.to_s].sort.join("\0").gsub(/^(.*[_.])(.+)\0\1(.+)/, '\1\2_\3').tr("\0", "_")
|
185
193
|
end
|
186
194
|
|
187
195
|
module ClassMethods
|
@@ -315,11 +323,7 @@ module ActiveRecord
|
|
315
323
|
# The list of columns names the model should ignore. Ignored columns won't have attribute
|
316
324
|
# accessors defined, and won't be referenced in SQL queries.
|
317
325
|
def ignored_columns
|
318
|
-
|
319
|
-
@ignored_columns
|
320
|
-
else
|
321
|
-
superclass.ignored_columns
|
322
|
-
end
|
326
|
+
@ignored_columns || superclass.ignored_columns
|
323
327
|
end
|
324
328
|
|
325
329
|
# Sets the columns names the model should ignore. Ignored columns won't have attribute
|
@@ -340,7 +344,7 @@ module ActiveRecord
|
|
340
344
|
# # name :string, limit: 255
|
341
345
|
# # category :string, limit: 255
|
342
346
|
#
|
343
|
-
# self.ignored_columns
|
347
|
+
# self.ignored_columns += [:category]
|
344
348
|
# end
|
345
349
|
#
|
346
350
|
# The schema still contains "category", but now the model omits it, so any meta-driven code or
|
@@ -425,6 +429,12 @@ module ActiveRecord
|
|
425
429
|
@columns ||= columns_hash.values.freeze
|
426
430
|
end
|
427
431
|
|
432
|
+
def _returning_columns_for_insert # :nodoc:
|
433
|
+
@_returning_columns_for_insert ||= columns.filter_map do |c|
|
434
|
+
c.name if connection.return_value_after_insert?(c)
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
428
438
|
def attribute_types # :nodoc:
|
429
439
|
load_schema
|
430
440
|
@attribute_types ||= Hash.new(Type.default_value)
|
@@ -515,7 +525,7 @@ module ActiveRecord
|
|
515
525
|
# when just after creating a table you want to populate it with some default
|
516
526
|
# values, e.g.:
|
517
527
|
#
|
518
|
-
# class CreateJobLevels < ActiveRecord::Migration[7.
|
528
|
+
# class CreateJobLevels < ActiveRecord::Migration[7.1]
|
519
529
|
# def up
|
520
530
|
# create_table :job_levels do |t|
|
521
531
|
# t.integer :id
|
@@ -548,10 +558,36 @@ module ActiveRecord
|
|
548
558
|
@load_schema_monitor = Monitor.new
|
549
559
|
end
|
550
560
|
|
561
|
+
def reload_schema_from_cache(recursive = true)
|
562
|
+
@_returning_columns_for_insert = nil
|
563
|
+
@arel_table = nil
|
564
|
+
@column_names = nil
|
565
|
+
@symbol_column_to_string_name_hash = nil
|
566
|
+
@attribute_types = nil
|
567
|
+
@content_columns = nil
|
568
|
+
@default_attributes = nil
|
569
|
+
@column_defaults = nil
|
570
|
+
@attributes_builder = nil
|
571
|
+
@columns = nil
|
572
|
+
@columns_hash = nil
|
573
|
+
@schema_loaded = false
|
574
|
+
@attribute_names = nil
|
575
|
+
@yaml_encoder = nil
|
576
|
+
if recursive
|
577
|
+
subclasses.each do |descendant|
|
578
|
+
descendant.send(:reload_schema_from_cache)
|
579
|
+
end
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
551
583
|
private
|
552
584
|
def inherited(child_class)
|
553
585
|
super
|
554
586
|
child_class.initialize_load_schema_monitor
|
587
|
+
child_class.reload_schema_from_cache(false)
|
588
|
+
child_class.class_eval do
|
589
|
+
@ignored_columns = nil
|
590
|
+
end
|
555
591
|
end
|
556
592
|
|
557
593
|
def schema_loaded?
|
@@ -561,7 +597,7 @@ module ActiveRecord
|
|
561
597
|
def load_schema
|
562
598
|
return if schema_loaded?
|
563
599
|
@load_schema_monitor.synchronize do
|
564
|
-
return if
|
600
|
+
return if @columns_hash
|
565
601
|
|
566
602
|
load_schema!
|
567
603
|
|
@@ -589,26 +625,10 @@ module ActiveRecord
|
|
589
625
|
default: column.default,
|
590
626
|
user_provided_default: false
|
591
627
|
)
|
628
|
+
alias_attribute :id_value, :id if name == "id"
|
592
629
|
end
|
593
|
-
end
|
594
630
|
|
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
|
631
|
+
super
|
612
632
|
end
|
613
633
|
|
614
634
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
@@ -15,7 +15,7 @@ module ActiveRecord
|
|
15
15
|
class_attribute :nested_attributes_options, instance_writer: false, default: {}
|
16
16
|
end
|
17
17
|
|
18
|
-
# = Active Record Nested Attributes
|
18
|
+
# = Active Record Nested \Attributes
|
19
19
|
#
|
20
20
|
# Nested attributes allow you to save attributes on associated records
|
21
21
|
# through the parent. By default nested attribute updating is turned off
|
@@ -280,6 +280,26 @@ module ActiveRecord
|
|
280
280
|
# member = Member.new
|
281
281
|
# member.avatar_attributes = {icon: 'sad'}
|
282
282
|
# member.avatar.width # => 200
|
283
|
+
#
|
284
|
+
# === Creating forms with nested attributes
|
285
|
+
#
|
286
|
+
# Use ActionView::Helpers::FormHelper#fields_for to create form elements
|
287
|
+
# for updating or destroying nested attributes.
|
288
|
+
#
|
289
|
+
# === Testing
|
290
|
+
#
|
291
|
+
# If you are using ActionView::Helpers::FormHelper#fields_for, your integration
|
292
|
+
# tests should replicate the HTML structure it provides. For example;
|
293
|
+
#
|
294
|
+
# post members_path, params: {
|
295
|
+
# member: {
|
296
|
+
# name: 'joe',
|
297
|
+
# posts_attributes: {
|
298
|
+
# '0' => { title: 'Foo' },
|
299
|
+
# '1' => { title: 'Bar' }
|
300
|
+
# }
|
301
|
+
# }
|
302
|
+
# }
|
283
303
|
module ClassMethods
|
284
304
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } }
|
285
305
|
|
@@ -335,12 +355,17 @@ module ActiveRecord
|
|
335
355
|
options.update(attr_names.extract_options!)
|
336
356
|
options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only)
|
337
357
|
options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
|
358
|
+
options[:class] = self
|
338
359
|
|
339
360
|
attr_names.each do |association_name|
|
340
361
|
if reflection = _reflect_on_association(association_name)
|
341
362
|
reflection.autosave = true
|
342
363
|
define_autosave_validation_callbacks(reflection)
|
343
364
|
|
365
|
+
if nested_attributes_options.dig(association_name.to_sym, :class) == self
|
366
|
+
raise ArgumentError, "Already declared #{association_name} as an accepts_nested_attributes association for this class."
|
367
|
+
end
|
368
|
+
|
344
369
|
nested_attributes_options = self.nested_attributes_options.dup
|
345
370
|
nested_attributes_options[association_name.to_sym] = options
|
346
371
|
self.nested_attributes_options = nested_attributes_options
|
@@ -375,11 +400,11 @@ module ActiveRecord
|
|
375
400
|
end
|
376
401
|
end
|
377
402
|
|
378
|
-
# Returns ActiveRecord::AutosaveAssociation
|
403
|
+
# Returns ActiveRecord::AutosaveAssociation#marked_for_destruction? It's
|
379
404
|
# used in conjunction with fields_for to build a form element for the
|
380
405
|
# destruction of this association.
|
381
406
|
#
|
382
|
-
# See ActionView::Helpers::FormHelper
|
407
|
+
# See ActionView::Helpers::FormHelper#fields_for for more info.
|
383
408
|
def _destroy
|
384
409
|
marked_for_destruction?
|
385
410
|
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord # :nodoc:
|
4
|
+
module Normalization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
class_attribute :normalized_attributes, default: Set.new
|
9
|
+
|
10
|
+
before_validation :normalize_changed_in_place_attributes
|
11
|
+
end
|
12
|
+
|
13
|
+
# Normalizes a specified attribute using its declared normalizations.
|
14
|
+
#
|
15
|
+
# ==== Examples
|
16
|
+
#
|
17
|
+
# class User < ActiveRecord::Base
|
18
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# legacy_user = User.find(1)
|
22
|
+
# legacy_user.email # => " CRUISE-CONTROL@EXAMPLE.COM\n"
|
23
|
+
# legacy_user.normalize_attribute(:email)
|
24
|
+
# legacy_user.email # => "cruise-control@example.com"
|
25
|
+
# legacy_user.save
|
26
|
+
def normalize_attribute(name)
|
27
|
+
# Treat the value as a new, unnormalized value.
|
28
|
+
self[name] = self[name]
|
29
|
+
end
|
30
|
+
|
31
|
+
module ClassMethods
|
32
|
+
# Declares a normalization for one or more attributes. The normalization
|
33
|
+
# is applied when the attribute is assigned or updated, and the normalized
|
34
|
+
# value will be persisted to the database. The normalization is also
|
35
|
+
# applied to the corresponding keyword argument of query methods. This
|
36
|
+
# allows a record to be created and later queried using unnormalized
|
37
|
+
# values.
|
38
|
+
#
|
39
|
+
# However, to prevent confusion, the normalization will not be applied
|
40
|
+
# when the attribute is fetched from the database. This means that if a
|
41
|
+
# record was persisted before the normalization was declared, the record's
|
42
|
+
# attribute will not be normalized until either it is assigned a new
|
43
|
+
# value, or it is explicitly migrated via Normalization#normalize_attribute.
|
44
|
+
#
|
45
|
+
# Because the normalization may be applied multiple times, it should be
|
46
|
+
# _idempotent_. In other words, applying the normalization more than once
|
47
|
+
# should have the same result as applying it only once.
|
48
|
+
#
|
49
|
+
# By default, the normalization will not be applied to +nil+ values. This
|
50
|
+
# behavior can be changed with the +:apply_to_nil+ option.
|
51
|
+
#
|
52
|
+
# ==== Options
|
53
|
+
#
|
54
|
+
# * +:with+ - The normalization to apply.
|
55
|
+
# * +:apply_to_nil+ - Whether to apply the normalization to +nil+ values.
|
56
|
+
# Defaults to +false+.
|
57
|
+
#
|
58
|
+
# ==== Examples
|
59
|
+
#
|
60
|
+
# class User < ActiveRecord::Base
|
61
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
62
|
+
# normalizes :phone, with: -> phone { phone.delete("^0-9").delete_prefix("1") }
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# user = User.create(email: " CRUISE-CONTROL@EXAMPLE.COM\n")
|
66
|
+
# user.email # => "cruise-control@example.com"
|
67
|
+
#
|
68
|
+
# user = User.find_by(email: "\tCRUISE-CONTROL@EXAMPLE.COM ")
|
69
|
+
# user.email # => "cruise-control@example.com"
|
70
|
+
# user.email_before_type_cast # => "cruise-control@example.com"
|
71
|
+
#
|
72
|
+
# User.where(email: "\tCRUISE-CONTROL@EXAMPLE.COM ").count # => 1
|
73
|
+
# User.where(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]).count # => 0
|
74
|
+
#
|
75
|
+
# User.exists?(email: "\tCRUISE-CONTROL@EXAMPLE.COM ") # => true
|
76
|
+
# User.exists?(["email = ?", "\tCRUISE-CONTROL@EXAMPLE.COM "]) # => false
|
77
|
+
#
|
78
|
+
# User.normalize_value_for(:phone, "+1 (555) 867-5309") # => "5558675309"
|
79
|
+
def normalizes(*names, with:, apply_to_nil: false)
|
80
|
+
names.each do |name|
|
81
|
+
attribute(name) do |cast_type|
|
82
|
+
NormalizedValueType.new(cast_type: cast_type, normalizer: with, normalize_nil: apply_to_nil)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
self.normalized_attributes += names.map(&:to_sym)
|
87
|
+
end
|
88
|
+
|
89
|
+
# Normalizes a given +value+ using normalizations declared for +name+.
|
90
|
+
#
|
91
|
+
# ==== Examples
|
92
|
+
#
|
93
|
+
# class User < ActiveRecord::Base
|
94
|
+
# normalizes :email, with: -> email { email.strip.downcase }
|
95
|
+
# end
|
96
|
+
#
|
97
|
+
# User.normalize_value_for(:email, " CRUISE-CONTROL@EXAMPLE.COM\n")
|
98
|
+
# # => "cruise-control@example.com"
|
99
|
+
def normalize_value_for(name, value)
|
100
|
+
type_for_attribute(name).cast(value)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
def normalize_changed_in_place_attributes
|
106
|
+
self.class.normalized_attributes.each do |name|
|
107
|
+
normalize_attribute(name) if attribute_changed_in_place?(name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class NormalizedValueType < DelegateClass(ActiveModel::Type::Value) # :nodoc:
|
112
|
+
include ActiveModel::Type::SerializeCastValue
|
113
|
+
|
114
|
+
attr_reader :cast_type, :normalizer, :normalize_nil
|
115
|
+
alias :normalize_nil? :normalize_nil
|
116
|
+
|
117
|
+
def initialize(cast_type:, normalizer:, normalize_nil:)
|
118
|
+
@cast_type = cast_type
|
119
|
+
@normalizer = normalizer
|
120
|
+
@normalize_nil = normalize_nil
|
121
|
+
super(cast_type)
|
122
|
+
end
|
123
|
+
|
124
|
+
def cast(value)
|
125
|
+
normalize(super(value))
|
126
|
+
end
|
127
|
+
|
128
|
+
def serialize(value)
|
129
|
+
serialize_cast_value(cast(value))
|
130
|
+
end
|
131
|
+
|
132
|
+
def serialize_cast_value(value)
|
133
|
+
ActiveModel::Type::SerializeCastValue.serialize(cast_type, value)
|
134
|
+
end
|
135
|
+
|
136
|
+
def ==(other)
|
137
|
+
self.class == other.class &&
|
138
|
+
normalize_nil? == other.normalize_nil? &&
|
139
|
+
normalizer == other.normalizer &&
|
140
|
+
cast_type == other.cast_type
|
141
|
+
end
|
142
|
+
alias eql? ==
|
143
|
+
|
144
|
+
def hash
|
145
|
+
[self.class, cast_type, normalizer, normalize_nil?].hash
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
Kernel.instance_method(:inspect).bind_call(self)
|
150
|
+
end
|
151
|
+
|
152
|
+
private
|
153
|
+
def normalize(value)
|
154
|
+
normalizer.call(value) unless value.nil? && !normalize_nil?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|