activerecord 7.0.8.7 → 7.1.5.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 +1795 -1424
- data/MIT-LICENSE +1 -1
- data/README.rdoc +16 -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 +20 -4
- 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 +19 -13
- data/lib/active_record/associations/collection_proxy.rb +15 -10
- 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/join_association.rb +3 -2
- data/lib/active_record/associations/join_dependency.rb +10 -10
- data/lib/active_record/associations/preloader/association.rb +31 -7
- data/lib/active_record/associations/preloader.rb +13 -10
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/associations/through_association.rb +22 -11
- data/lib/active_record/associations.rb +319 -217
- 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 +53 -35
- 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 +21 -8
- data/lib/active_record/attribute_methods/serialization.rb +150 -31
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +4 -0
- data/lib/active_record/attribute_methods/write.rb +6 -6
- data/lib/active_record/attribute_methods.rb +145 -21
- data/lib/active_record/attributes.rb +3 -3
- data/lib/active_record/autosave_association.rb +59 -10
- 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 +80 -50
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +5 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +129 -31
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +62 -23
- 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 +296 -127
- data/lib/active_record/connection_adapters/abstract/transaction.rb +287 -58
- data/lib/active_record/connection_adapters/abstract_adapter.rb +511 -92
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +244 -121
- 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 +19 -13
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +151 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +106 -55
- 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 +14 -3
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +74 -40
- data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +6 -0
- 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/quoting.rb +10 -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 +53 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +364 -61
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +353 -192
- data/lib/active_record/connection_adapters/schema_cache.rb +287 -59
- data/lib/active_record/connection_adapters/sqlite3/column.rb +49 -0
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +52 -39
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +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 +211 -81
- data/lib/active_record/connection_adapters/statement_pool.rb +7 -0
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +99 -0
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +258 -0
- data/lib/active_record/connection_adapters.rb +3 -1
- data/lib/active_record/connection_handling.rb +72 -95
- data/lib/active_record/core.rb +181 -154
- data/lib/active_record/counter_cache.rb +52 -27
- data/lib/active_record/database_configurations/connection_url_resolver.rb +1 -1
- data/lib/active_record/database_configurations/database_config.rb +9 -3
- data/lib/active_record/database_configurations/hash_config.rb +28 -14
- 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 +15 -10
- data/lib/active_record/deprecator.rb +7 -0
- data/lib/active_record/destroy_association_async_job.rb +3 -1
- data/lib/active_record/encryption/auto_filtered_parameters.rb +66 -0
- data/lib/active_record/encryption/cipher/aes256_gcm.rb +4 -1
- data/lib/active_record/encryption/config.rb +25 -1
- data/lib/active_record/encryption/configurable.rb +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 +42 -18
- data/lib/active_record/encryption/encrypted_attribute_type.rb +23 -8
- 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/message_serializer.rb +2 -0
- data/lib/active_record/encryption/properties.rb +3 -3
- data/lib/active_record/encryption/scheme.rb +22 -21
- data/lib/active_record/encryption.rb +3 -0
- data/lib/active_record/enum.rb +112 -28
- data/lib/active_record/errors.rb +112 -18
- 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 +135 -71
- data/lib/active_record/future_result.rb +40 -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 +57 -10
- data/lib/active_record/integration.rb +8 -8
- data/lib/active_record/internal_metadata.rb +120 -30
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/locking/pessimistic.rb +5 -2
- data/lib/active_record/log_subscriber.rb +29 -12
- 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 +104 -5
- data/lib/active_record/migration/compatibility.rb +145 -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/pending_migration_connection.rb +21 -0
- data/lib/active_record/migration.rb +219 -111
- data/lib/active_record/model_schema.rb +69 -44
- data/lib/active_record/nested_attributes.rb +37 -8
- data/lib/active_record/normalization.rb +167 -0
- data/lib/active_record/persistence.rb +188 -37
- data/lib/active_record/promise.rb +84 -0
- data/lib/active_record/query_cache.rb +4 -22
- 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 +12 -6
- data/lib/active_record/railties/databases.rake +144 -150
- 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 +181 -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 +187 -63
- data/lib/active_record/relation/delegation.rb +23 -9
- data/lib/active_record/relation/finder_methods.rb +77 -16
- data/lib/active_record/relation/merger.rb +2 -0
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +11 -2
- 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 +26 -14
- data/lib/active_record/relation/query_attribute.rb +2 -1
- data/lib/active_record/relation/query_methods.rb +371 -68
- data/lib/active_record/relation/spawn_methods.rb +18 -1
- data/lib/active_record/relation.rb +103 -37
- data/lib/active_record/result.rb +19 -5
- data/lib/active_record/runtime_registry.rb +24 -1
- data/lib/active_record/sanitization.rb +51 -11
- data/lib/active_record/schema.rb +2 -3
- data/lib/active_record/schema_dumper.rb +46 -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 +152 -108
- 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 +15 -7
- data/lib/active_record/test_fixtures.rb +114 -96
- data/lib/active_record/timestamp.rb +30 -16
- 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 +122 -17
- 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 +1 -9
- data/lib/arel/nodes/leading_join.rb +8 -0
- data/lib/arel/nodes/node.rb +111 -2
- data/lib/arel/nodes/sql_literal.rb +6 -0
- data/lib/arel/nodes/table_alias.rb +4 -0
- data/lib/arel/nodes.rb +4 -0
- data/lib/arel/predications.rb +2 -0
- data/lib/arel/table.rb +9 -5
- data/lib/arel/tree_manager.rb +5 -1
- data/lib/arel/visitors/mysql.rb +8 -1
- data/lib/arel/visitors/to_sql.rb +83 -18
- 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 +46 -10
- data/lib/active_record/connection_adapters/legacy_pool_manager.rb +0 -35
- data/lib/active_record/null_relation.rb +0 -63
@@ -13,17 +13,31 @@ module ActiveRecord
|
|
13
13
|
|
14
14
|
def after_teardown # :nodoc:
|
15
15
|
super
|
16
|
+
ensure
|
16
17
|
teardown_fixtures
|
17
18
|
end
|
18
19
|
|
19
20
|
included do
|
20
|
-
|
21
|
+
##
|
22
|
+
# :singleton-method: fixture_paths
|
23
|
+
#
|
24
|
+
# Returns the ActiveRecord::FixtureSet collection
|
25
|
+
|
26
|
+
##
|
27
|
+
# :singleton-method: fixture_paths=
|
28
|
+
#
|
29
|
+
# :call-seq:
|
30
|
+
# fixture_paths=(fixture_paths)
|
31
|
+
class_attribute :fixture_paths, instance_writer: false, default: []
|
21
32
|
class_attribute :fixture_table_names, default: []
|
22
33
|
class_attribute :fixture_class_names, default: {}
|
23
34
|
class_attribute :use_transactional_tests, default: true
|
24
35
|
class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances
|
25
36
|
class_attribute :pre_loaded_fixtures, default: false
|
26
37
|
class_attribute :lock_threads, default: true
|
38
|
+
class_attribute :fixture_sets, default: {}
|
39
|
+
|
40
|
+
ActiveSupport.run_load_hooks(:active_record_fixtures, self)
|
27
41
|
end
|
28
42
|
|
29
43
|
module ClassMethods
|
@@ -39,12 +53,28 @@ module ActiveRecord
|
|
39
53
|
self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
|
40
54
|
end
|
41
55
|
|
56
|
+
def fixture_path # :nodoc:
|
57
|
+
ActiveRecord.deprecator.warn(<<~WARNING)
|
58
|
+
TestFixtures.fixture_path is deprecated and will be removed in Rails 7.2. Use .fixture_paths instead.
|
59
|
+
If multiple fixture paths have been configured with .fixture_paths, then .fixture_path will just return
|
60
|
+
the first path.
|
61
|
+
WARNING
|
62
|
+
fixture_paths.first
|
63
|
+
end
|
64
|
+
|
65
|
+
def fixture_path=(path) # :nodoc:
|
66
|
+
ActiveRecord.deprecator.warn("TestFixtures.fixture_path= is deprecated and will be removed in Rails 7.2. Use .fixture_paths= instead.")
|
67
|
+
self.fixture_paths = Array(path)
|
68
|
+
end
|
69
|
+
|
42
70
|
def fixtures(*fixture_set_names)
|
43
71
|
if fixture_set_names.first == :all
|
44
|
-
raise StandardError, "No fixture path found. Please set `#{self}.
|
45
|
-
fixture_set_names =
|
46
|
-
|
47
|
-
|
72
|
+
raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
|
73
|
+
fixture_set_names = fixture_paths.flat_map do |path|
|
74
|
+
names = Dir[::File.join(path, "{**,*}/*.{yml}")].uniq
|
75
|
+
names.reject! { |f| f.start_with?(file_fixture_path.to_s) } if defined?(file_fixture_path) && file_fixture_path
|
76
|
+
names.map! { |f| f[path.to_s.size..-5].delete_prefix("/") }
|
77
|
+
end.uniq
|
48
78
|
else
|
49
79
|
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
|
50
80
|
end
|
@@ -55,35 +85,15 @@ module ActiveRecord
|
|
55
85
|
|
56
86
|
def setup_fixture_accessors(fixture_set_names = nil)
|
57
87
|
fixture_set_names = Array(fixture_set_names || fixture_table_names)
|
58
|
-
|
88
|
+
unless fixture_set_names.empty?
|
89
|
+
self.fixture_sets = fixture_sets.dup
|
59
90
|
fixture_set_names.each do |fs_name|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
65
|
-
return_single_record = fixture_names.size == 1
|
66
|
-
fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
|
67
|
-
|
68
|
-
@fixture_cache[fs_name] ||= {}
|
69
|
-
|
70
|
-
instances = fixture_names.map do |f_name|
|
71
|
-
f_name = f_name.to_s if f_name.is_a?(Symbol)
|
72
|
-
@fixture_cache[fs_name].delete(f_name) if force_reload
|
73
|
-
|
74
|
-
if @loaded_fixtures[fs_name][f_name]
|
75
|
-
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
|
76
|
-
else
|
77
|
-
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
return_single_record ? instances.first : instances
|
82
|
-
end
|
83
|
-
private accessor_name
|
91
|
+
key = fs_name.to_s.include?("/") ? -fs_name.to_s.tr("/", "_") : fs_name
|
92
|
+
key = -key.to_s if key.is_a?(Symbol)
|
93
|
+
fs_name = -fs_name.to_s if fs_name.is_a?(Symbol)
|
94
|
+
fixture_sets[key] = fs_name
|
84
95
|
end
|
85
96
|
end
|
86
|
-
include methods
|
87
97
|
end
|
88
98
|
|
89
99
|
# Prevents automatically wrapping each specified test in a transaction,
|
@@ -100,6 +110,15 @@ module ActiveRecord
|
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
113
|
+
def fixture_path # :nodoc:
|
114
|
+
ActiveRecord.deprecator.warn(<<~WARNING)
|
115
|
+
TestFixtures#fixture_path is deprecated and will be removed in Rails 7.2. Use #fixture_paths instead.
|
116
|
+
If multiple fixture paths have been configured with #fixture_paths, then #fixture_path will just return
|
117
|
+
the first path.
|
118
|
+
WARNING
|
119
|
+
fixture_paths.first
|
120
|
+
end
|
121
|
+
|
103
122
|
def run_in_transaction?
|
104
123
|
use_transactional_tests &&
|
105
124
|
!self.class.uses_transaction?(name)
|
@@ -114,7 +133,6 @@ module ActiveRecord
|
|
114
133
|
@fixture_connections = []
|
115
134
|
@@already_loaded_fixtures ||= {}
|
116
135
|
@connection_subscriber = nil
|
117
|
-
@legacy_saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
118
136
|
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
119
137
|
|
120
138
|
# Load fixtures once and begin transaction.
|
@@ -135,19 +153,18 @@ module ActiveRecord
|
|
135
153
|
|
136
154
|
# When connections are established in the future, begin a transaction too
|
137
155
|
@connection_subscriber = ActiveSupport::Notifications.subscribe("!connection.active_record") do |_, _, _, _, payload|
|
138
|
-
|
156
|
+
connection_name = payload[:connection_name] if payload.key?(:connection_name)
|
139
157
|
shard = payload[:shard] if payload.key?(:shard)
|
140
|
-
setup_shared_connection_pool if ActiveRecord.legacy_connection_handling
|
141
158
|
|
142
|
-
if
|
159
|
+
if connection_name
|
143
160
|
begin
|
144
|
-
connection = ActiveRecord::Base.connection_handler.retrieve_connection(
|
161
|
+
connection = ActiveRecord::Base.connection_handler.retrieve_connection(connection_name, shard: shard)
|
145
162
|
rescue ConnectionNotEstablished
|
146
163
|
connection = nil
|
147
164
|
end
|
148
165
|
|
149
166
|
if connection
|
150
|
-
setup_shared_connection_pool
|
167
|
+
setup_shared_connection_pool
|
151
168
|
|
152
169
|
if !@fixture_connections.include?(connection)
|
153
170
|
connection.begin_transaction joinable: false, _lazy: false
|
@@ -183,13 +200,13 @@ module ActiveRecord
|
|
183
200
|
ActiveRecord::FixtureSet.reset_cache
|
184
201
|
end
|
185
202
|
|
186
|
-
ActiveRecord::Base.clear_active_connections!
|
203
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
187
204
|
end
|
188
205
|
|
189
206
|
def enlist_fixture_connections
|
190
207
|
setup_shared_connection_pool
|
191
208
|
|
192
|
-
ActiveRecord::Base.connection_handler.connection_pool_list.map(&:connection)
|
209
|
+
ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
|
193
210
|
end
|
194
211
|
|
195
212
|
private
|
@@ -200,79 +217,43 @@ module ActiveRecord
|
|
200
217
|
# need to share a connection pool so that the reading connection
|
201
218
|
# can see data in the open transaction on the writing connection.
|
202
219
|
def setup_shared_connection_pool
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
pool_config = pool_manager.get_pool_config(nil, shard_name)
|
217
|
-
next if pool_config == writing_pool_config
|
218
|
-
|
219
|
-
@legacy_saved_pool_configs[handler][name][shard_name] = pool_config
|
220
|
-
pool_manager.set_pool_config(nil, shard_name, writing_pool_config)
|
221
|
-
end
|
222
|
-
end
|
223
|
-
end
|
224
|
-
end
|
225
|
-
else
|
226
|
-
handler = ActiveRecord::Base.connection_handler
|
227
|
-
|
228
|
-
handler.connection_pool_names.each do |name|
|
229
|
-
pool_manager = handler.send(:owner_to_pool_manager)[name]
|
230
|
-
pool_manager.shard_names.each do |shard_name|
|
231
|
-
writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
|
232
|
-
@saved_pool_configs[name][shard_name] ||= {}
|
233
|
-
pool_manager.role_names.each do |role|
|
234
|
-
next unless pool_config = pool_manager.get_pool_config(role, shard_name)
|
235
|
-
next if pool_config == writing_pool_config
|
236
|
-
|
237
|
-
@saved_pool_configs[name][shard_name][role] = pool_config
|
238
|
-
pool_manager.set_pool_config(role, shard_name, writing_pool_config)
|
239
|
-
end
|
220
|
+
handler = ActiveRecord::Base.connection_handler
|
221
|
+
|
222
|
+
handler.connection_pool_names.each do |name|
|
223
|
+
pool_manager = handler.send(:connection_name_to_pool_manager)[name]
|
224
|
+
pool_manager.shard_names.each do |shard_name|
|
225
|
+
writing_pool_config = pool_manager.get_pool_config(ActiveRecord.writing_role, shard_name)
|
226
|
+
@saved_pool_configs[name][shard_name] ||= {}
|
227
|
+
pool_manager.role_names.each do |role|
|
228
|
+
next unless pool_config = pool_manager.get_pool_config(role, shard_name)
|
229
|
+
next if pool_config == writing_pool_config
|
230
|
+
|
231
|
+
@saved_pool_configs[name][shard_name][role] = pool_config
|
232
|
+
pool_manager.set_pool_config(role, shard_name, writing_pool_config)
|
240
233
|
end
|
241
234
|
end
|
242
235
|
end
|
243
236
|
end
|
244
237
|
|
245
238
|
def teardown_shared_connection_pool
|
246
|
-
|
247
|
-
@legacy_saved_pool_configs.each_pair do |handler, names|
|
248
|
-
names.each_pair do |name, shards|
|
249
|
-
shards.each_pair do |shard_name, pool_config|
|
250
|
-
pool_manager = handler.send(:owner_to_pool_manager)[name]
|
251
|
-
pool_manager.set_pool_config(nil, shard_name, pool_config)
|
252
|
-
end
|
253
|
-
end
|
254
|
-
end
|
255
|
-
else
|
256
|
-
handler = ActiveRecord::Base.connection_handler
|
239
|
+
handler = ActiveRecord::Base.connection_handler
|
257
240
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
241
|
+
@saved_pool_configs.each_pair do |name, shards|
|
242
|
+
pool_manager = handler.send(:connection_name_to_pool_manager)[name]
|
243
|
+
shards.each_pair do |shard_name, roles|
|
244
|
+
roles.each_pair do |role, pool_config|
|
245
|
+
next unless pool_manager.get_pool_config(role, shard_name)
|
263
246
|
|
264
|
-
|
265
|
-
end
|
247
|
+
pool_manager.set_pool_config(role, shard_name, pool_config)
|
266
248
|
end
|
267
249
|
end
|
268
250
|
end
|
269
251
|
|
270
|
-
@legacy_saved_pool_configs.clear
|
271
252
|
@saved_pool_configs.clear
|
272
253
|
end
|
273
254
|
|
274
255
|
def load_fixtures(config)
|
275
|
-
ActiveRecord::FixtureSet.create_fixtures(
|
256
|
+
ActiveRecord::FixtureSet.create_fixtures(fixture_paths, fixture_table_names, fixture_class_names, config).index_by(&:name)
|
276
257
|
end
|
277
258
|
|
278
259
|
def instantiate_fixtures
|
@@ -290,5 +271,42 @@ module ActiveRecord
|
|
290
271
|
def load_instances?
|
291
272
|
use_instantiated_fixtures != :no_instances
|
292
273
|
end
|
274
|
+
|
275
|
+
def method_missing(name, *args, **kwargs, &block)
|
276
|
+
if fs_name = fixture_sets[name.to_s]
|
277
|
+
access_fixture(fs_name, *args, **kwargs, &block)
|
278
|
+
else
|
279
|
+
super
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
def respond_to_missing?(name, include_private = false)
|
284
|
+
if include_private && fixture_sets.key?(name.to_s)
|
285
|
+
true
|
286
|
+
else
|
287
|
+
super
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def access_fixture(fs_name, *fixture_names)
|
292
|
+
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
293
|
+
return_single_record = fixture_names.size == 1
|
294
|
+
|
295
|
+
fixture_names = @loaded_fixtures[fs_name].fixtures.keys if fixture_names.empty?
|
296
|
+
@fixture_cache[fs_name] ||= {}
|
297
|
+
|
298
|
+
instances = fixture_names.map do |f_name|
|
299
|
+
f_name = f_name.to_s if f_name.is_a?(Symbol)
|
300
|
+
@fixture_cache[fs_name].delete(f_name) if force_reload
|
301
|
+
|
302
|
+
if @loaded_fixtures[fs_name][f_name]
|
303
|
+
@fixture_cache[fs_name][f_name] ||= @loaded_fixtures[fs_name][f_name].find
|
304
|
+
else
|
305
|
+
raise StandardError, "No fixture named '#{f_name}' found for fixture set '#{fs_name}'"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
return_single_record ? instances.first : instances
|
310
|
+
end
|
293
311
|
end
|
294
312
|
end
|
@@ -30,7 +30,7 @@ module ActiveRecord
|
|
30
30
|
#
|
31
31
|
# ActiveRecord::Base.time_zone_aware_types = [:datetime]
|
32
32
|
#
|
33
|
-
# You can also add database
|
33
|
+
# You can also add database-specific timezone aware types. For example, for PostgreSQL:
|
34
34
|
#
|
35
35
|
# ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
|
36
36
|
#
|
@@ -54,8 +54,10 @@ module ActiveRecord
|
|
54
54
|
|
55
55
|
module ClassMethods # :nodoc:
|
56
56
|
def touch_attributes_with_time(*names, time: nil)
|
57
|
+
names = names.map(&:to_s)
|
58
|
+
names = names.map { |name| attribute_aliases[name] || name }
|
57
59
|
attribute_names = timestamp_attributes_for_update_in_model
|
58
|
-
attribute_names |= names
|
60
|
+
attribute_names |= names
|
59
61
|
attribute_names.index_with(time || current_time_from_proper_timezone)
|
60
62
|
end
|
61
63
|
|
@@ -75,9 +77,17 @@ module ActiveRecord
|
|
75
77
|
end
|
76
78
|
|
77
79
|
def current_time_from_proper_timezone
|
78
|
-
|
80
|
+
connection.default_timezone == :utc ? Time.now.utc : Time.now
|
79
81
|
end
|
80
82
|
|
83
|
+
protected
|
84
|
+
def reload_schema_from_cache(recursive = true)
|
85
|
+
@timestamp_attributes_for_create_in_model = nil
|
86
|
+
@timestamp_attributes_for_update_in_model = nil
|
87
|
+
@all_timestamp_attributes_in_model = nil
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
81
91
|
private
|
82
92
|
def timestamp_attributes_for_create
|
83
93
|
["created_at", "created_on"].map! { |name| attribute_aliases[name] || name }
|
@@ -86,16 +96,14 @@ module ActiveRecord
|
|
86
96
|
def timestamp_attributes_for_update
|
87
97
|
["updated_at", "updated_on"].map! { |name| attribute_aliases[name] || name }
|
88
98
|
end
|
89
|
-
|
90
|
-
def reload_schema_from_cache
|
91
|
-
@timestamp_attributes_for_create_in_model = nil
|
92
|
-
@timestamp_attributes_for_update_in_model = nil
|
93
|
-
@all_timestamp_attributes_in_model = nil
|
94
|
-
super
|
95
|
-
end
|
96
99
|
end
|
97
100
|
|
98
101
|
private
|
102
|
+
def init_internals
|
103
|
+
super
|
104
|
+
@_touch_record = nil
|
105
|
+
end
|
106
|
+
|
99
107
|
def _create_record
|
100
108
|
if record_timestamps
|
101
109
|
current_time = current_time_from_proper_timezone
|
@@ -109,6 +117,17 @@ module ActiveRecord
|
|
109
117
|
end
|
110
118
|
|
111
119
|
def _update_record
|
120
|
+
record_update_timestamps
|
121
|
+
|
122
|
+
super
|
123
|
+
end
|
124
|
+
|
125
|
+
def create_or_update(touch: true, **)
|
126
|
+
@_touch_record = touch
|
127
|
+
super
|
128
|
+
end
|
129
|
+
|
130
|
+
def record_update_timestamps
|
112
131
|
if @_touch_record && should_record_timestamps?
|
113
132
|
current_time = current_time_from_proper_timezone
|
114
133
|
|
@@ -118,12 +137,7 @@ module ActiveRecord
|
|
118
137
|
end
|
119
138
|
end
|
120
139
|
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
def create_or_update(touch: true, **)
|
125
|
-
@_touch_record = touch
|
126
|
-
super
|
140
|
+
yield if block_given?
|
127
141
|
end
|
128
142
|
|
129
143
|
def should_record_timestamps?
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/object/json"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module TokenFor
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
class_attribute :token_definitions, instance_accessor: false, instance_predicate: false, default: {}
|
11
|
+
class_attribute :generated_token_verifier, instance_accessor: false, instance_predicate: false
|
12
|
+
end
|
13
|
+
|
14
|
+
TokenDefinition = Struct.new(:defining_class, :purpose, :expires_in, :block) do # :nodoc:
|
15
|
+
def full_purpose
|
16
|
+
@full_purpose ||= [defining_class.name, purpose, expires_in].join("\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def message_verifier
|
20
|
+
defining_class.generated_token_verifier
|
21
|
+
end
|
22
|
+
|
23
|
+
def payload_for(model)
|
24
|
+
block ? [model.id, model.instance_eval(&block).as_json] : [model.id]
|
25
|
+
end
|
26
|
+
|
27
|
+
def generate_token(model)
|
28
|
+
message_verifier.generate(payload_for(model), expires_in: expires_in, purpose: full_purpose)
|
29
|
+
end
|
30
|
+
|
31
|
+
def resolve_token(token)
|
32
|
+
payload = message_verifier.verified(token, purpose: full_purpose)
|
33
|
+
model = yield(payload[0]) if payload
|
34
|
+
model if model && payload_for(model) == payload
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
# Defines the behavior of tokens generated for a specific +purpose+.
|
40
|
+
# A token can be generated by calling TokenFor#generate_token_for on a
|
41
|
+
# record. Later, that record can be fetched by calling #find_by_token_for
|
42
|
+
# (or #find_by_token_for!) with the same purpose and token.
|
43
|
+
#
|
44
|
+
# Tokens are signed so that they are tamper-proof. Thus they can be
|
45
|
+
# exposed to outside world as, for example, password reset tokens.
|
46
|
+
#
|
47
|
+
# By default, tokens do not expire. They can be configured to expire by
|
48
|
+
# specifying a duration via the +expires_in+ option. The duration becomes
|
49
|
+
# part of the token's signature, so changing the value of +expires_in+
|
50
|
+
# will automatically invalidate previously generated tokens.
|
51
|
+
#
|
52
|
+
# A block may also be specified. When generating a token with
|
53
|
+
# TokenFor#generate_token_for, the block will be evaluated in the context
|
54
|
+
# of the record, and its return value will be embedded in the token as
|
55
|
+
# JSON. Later, when fetching the record with #find_by_token_for, the block
|
56
|
+
# will be evaluated again in the context of the fetched record. If the two
|
57
|
+
# JSON values do not match, the token will be treated as invalid. Note
|
58
|
+
# that the value returned by the block <b>should not contain sensitive
|
59
|
+
# information</b> because it will be embedded in the token as
|
60
|
+
# <b>human-readable plaintext JSON</b>.
|
61
|
+
#
|
62
|
+
# ==== Examples
|
63
|
+
#
|
64
|
+
# class User < ActiveRecord::Base
|
65
|
+
# has_secure_password
|
66
|
+
#
|
67
|
+
# generates_token_for :password_reset, expires_in: 15.minutes do
|
68
|
+
# # Last 10 characters of password salt, which changes when password is updated:
|
69
|
+
# password_salt&.last(10)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
#
|
73
|
+
# user = User.first
|
74
|
+
#
|
75
|
+
# token = user.generate_token_for(:password_reset)
|
76
|
+
# User.find_by_token_for(:password_reset, token) # => user
|
77
|
+
# # 16 minutes later...
|
78
|
+
# User.find_by_token_for(:password_reset, token) # => nil
|
79
|
+
#
|
80
|
+
# token = user.generate_token_for(:password_reset)
|
81
|
+
# User.find_by_token_for(:password_reset, token) # => user
|
82
|
+
# user.update!(password: "new password")
|
83
|
+
# User.find_by_token_for(:password_reset, token) # => nil
|
84
|
+
def generates_token_for(purpose, expires_in: nil, &block)
|
85
|
+
self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
|
86
|
+
end
|
87
|
+
|
88
|
+
# Finds a record using a given +token+ for a predefined +purpose+. Returns
|
89
|
+
# +nil+ if the token is invalid or the record was not found.
|
90
|
+
def find_by_token_for(purpose, token)
|
91
|
+
raise UnknownPrimaryKey.new(self) unless primary_key
|
92
|
+
token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(primary_key => id) }
|
93
|
+
end
|
94
|
+
|
95
|
+
# Finds a record using a given +token+ for a predefined +purpose+. Raises
|
96
|
+
# ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
|
97
|
+
# (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
|
98
|
+
# the token is valid but the record was not found.
|
99
|
+
def find_by_token_for!(purpose, token)
|
100
|
+
token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
|
101
|
+
(raise ActiveSupport::MessageVerifier::InvalidSignature)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Generates a token for a predefined +purpose+.
|
106
|
+
#
|
107
|
+
# Use ClassMethods#generates_token_for to define a token purpose and
|
108
|
+
# behavior.
|
109
|
+
def generate_token_for(purpose)
|
110
|
+
self.class.token_definitions.fetch(purpose).generate_token(self)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -24,9 +24,13 @@ module ActiveRecord
|
|
24
24
|
@_new_record_before_last_commit ||= false
|
25
25
|
|
26
26
|
# touch the parents as we are not calling the after_save callbacks
|
27
|
-
self.class.reflect_on_all_associations
|
27
|
+
self.class.reflect_on_all_associations.each do |r|
|
28
28
|
if touch = r.options[:touch]
|
29
|
-
|
29
|
+
if r.macro == :belongs_to
|
30
|
+
ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch)
|
31
|
+
elsif r.macro == :has_one
|
32
|
+
ActiveRecord::Associations::Builder::HasOne.touch_record(self, r.name, touch)
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
36
|
end
|
@@ -42,6 +46,11 @@ module ActiveRecord
|
|
42
46
|
end
|
43
47
|
|
44
48
|
private
|
49
|
+
def init_internals
|
50
|
+
super
|
51
|
+
@_defer_touch_attrs = nil
|
52
|
+
end
|
53
|
+
|
45
54
|
def surreptitiously_touch(attr_names)
|
46
55
|
attr_names.each do |attr_name|
|
47
56
|
_write_attribute(attr_name, @_touch_time)
|
@@ -57,9 +66,5 @@ module ActiveRecord
|
|
57
66
|
def has_defer_touch_attrs?
|
58
67
|
defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
|
59
68
|
end
|
60
|
-
|
61
|
-
def belongs_to_touch_method
|
62
|
-
:touch_later
|
63
|
-
end
|
64
69
|
end
|
65
70
|
end
|
@@ -13,7 +13,9 @@ module ActiveRecord
|
|
13
13
|
scope: [:kind, :name]
|
14
14
|
end
|
15
15
|
|
16
|
-
|
16
|
+
attr_accessor :_new_record_before_last_commit # :nodoc:
|
17
|
+
|
18
|
+
# = Active Record \Transactions
|
17
19
|
#
|
18
20
|
# \Transactions are protective blocks where SQL statements are only permanent
|
19
21
|
# if they can all succeed as one atomic action. The classic example is a
|
@@ -98,7 +100,8 @@ module ActiveRecord
|
|
98
100
|
# catch those in your application code.
|
99
101
|
#
|
100
102
|
# One exception is the ActiveRecord::Rollback exception, which will trigger
|
101
|
-
# a ROLLBACK when raised, but not be re-raised by the transaction block.
|
103
|
+
# a ROLLBACK when raised, but not be re-raised by the transaction block. Any
|
104
|
+
# other exception will be re-raised.
|
102
105
|
#
|
103
106
|
# *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
|
104
107
|
# inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
|
@@ -227,31 +230,31 @@ module ActiveRecord
|
|
227
230
|
# after_commit :do_bar_baz, on: [:update, :destroy]
|
228
231
|
#
|
229
232
|
def after_commit(*args, &block)
|
230
|
-
set_options_for_callbacks!(args)
|
233
|
+
set_options_for_callbacks!(args, prepend_option)
|
231
234
|
set_callback(:commit, :after, *args, &block)
|
232
235
|
end
|
233
236
|
|
234
237
|
# Shortcut for <tt>after_commit :hook, on: [ :create, :update ]</tt>.
|
235
238
|
def after_save_commit(*args, &block)
|
236
|
-
set_options_for_callbacks!(args, on: [ :create, :update ])
|
239
|
+
set_options_for_callbacks!(args, on: [ :create, :update ], **prepend_option)
|
237
240
|
set_callback(:commit, :after, *args, &block)
|
238
241
|
end
|
239
242
|
|
240
243
|
# Shortcut for <tt>after_commit :hook, on: :create</tt>.
|
241
244
|
def after_create_commit(*args, &block)
|
242
|
-
set_options_for_callbacks!(args, on: :create)
|
245
|
+
set_options_for_callbacks!(args, on: :create, **prepend_option)
|
243
246
|
set_callback(:commit, :after, *args, &block)
|
244
247
|
end
|
245
248
|
|
246
249
|
# Shortcut for <tt>after_commit :hook, on: :update</tt>.
|
247
250
|
def after_update_commit(*args, &block)
|
248
|
-
set_options_for_callbacks!(args, on: :update)
|
251
|
+
set_options_for_callbacks!(args, on: :update, **prepend_option)
|
249
252
|
set_callback(:commit, :after, *args, &block)
|
250
253
|
end
|
251
254
|
|
252
255
|
# Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
|
253
256
|
def after_destroy_commit(*args, &block)
|
254
|
-
set_options_for_callbacks!(args, on: :destroy)
|
257
|
+
set_options_for_callbacks!(args, on: :destroy, **prepend_option)
|
255
258
|
set_callback(:commit, :after, *args, &block)
|
256
259
|
end
|
257
260
|
|
@@ -259,11 +262,19 @@ module ActiveRecord
|
|
259
262
|
#
|
260
263
|
# Please check the documentation of #after_commit for options.
|
261
264
|
def after_rollback(*args, &block)
|
262
|
-
set_options_for_callbacks!(args)
|
265
|
+
set_options_for_callbacks!(args, prepend_option)
|
263
266
|
set_callback(:rollback, :after, *args, &block)
|
264
267
|
end
|
265
268
|
|
266
269
|
private
|
270
|
+
def prepend_option
|
271
|
+
if ActiveRecord.run_after_transaction_callbacks_in_order_defined
|
272
|
+
{ prepend: true }
|
273
|
+
else
|
274
|
+
{}
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
267
278
|
def set_options_for_callbacks!(args, enforced_options = {})
|
268
279
|
options = args.extract_options!.merge!(enforced_options)
|
269
280
|
args << options
|
@@ -365,6 +376,13 @@ module ActiveRecord
|
|
365
376
|
private
|
366
377
|
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
|
367
378
|
|
379
|
+
def init_internals
|
380
|
+
super
|
381
|
+
@_start_transaction_state = nil
|
382
|
+
@_committed_already_called = nil
|
383
|
+
@_new_record_before_last_commit = nil
|
384
|
+
end
|
385
|
+
|
368
386
|
# Save the new record state and id of a record so it can be restored later if a transaction fails.
|
369
387
|
def remember_transaction_record_state
|
370
388
|
@_start_transaction_state ||= {
|
@@ -406,8 +424,16 @@ module ActiveRecord
|
|
406
424
|
end
|
407
425
|
@mutations_from_database = nil
|
408
426
|
@mutations_before_last_save = nil
|
409
|
-
if
|
410
|
-
|
427
|
+
if self.class.composite_primary_key?
|
428
|
+
if restore_state[:id] != @primary_key.map { |col| @attributes.fetch_value(col) }
|
429
|
+
@primary_key.zip(restore_state[:id]).each do |col, val|
|
430
|
+
@attributes.write_from_user(col, val)
|
431
|
+
end
|
432
|
+
end
|
433
|
+
else
|
434
|
+
if @attributes.fetch_value(@primary_key) != restore_state[:id]
|
435
|
+
@attributes.write_from_user(@primary_key, restore_state[:id])
|
436
|
+
end
|
411
437
|
end
|
412
438
|
freeze if restore_state[:frozen?]
|
413
439
|
end
|