activerecord 7.1.5.2 → 8.1.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 +567 -2443
- data/README.rdoc +16 -16
- data/examples/performance.rb +2 -2
- data/lib/active_record/association_relation.rb +3 -2
- data/lib/active_record/associations/alias_tracker.rb +31 -23
- data/lib/active_record/associations/association.rb +43 -12
- data/lib/active_record/associations/belongs_to_association.rb +31 -8
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +3 -2
- data/lib/active_record/associations/builder/association.rb +23 -11
- data/lib/active_record/associations/builder/belongs_to.rb +18 -4
- data/lib/active_record/associations/builder/collection_association.rb +7 -3
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -2
- 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 +33 -5
- data/lib/active_record/associations/collection_association.rb +17 -9
- data/lib/active_record/associations/collection_proxy.rb +36 -5
- data/lib/active_record/associations/deprecation.rb +88 -0
- data/lib/active_record/associations/disable_joins_association_scope.rb +1 -1
- data/lib/active_record/associations/errors.rb +268 -0
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/has_many_through_association.rb +10 -3
- data/lib/active_record/associations/join_dependency/join_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +4 -2
- data/lib/active_record/associations/nested_error.rb +47 -0
- data/lib/active_record/associations/preloader/association.rb +4 -3
- data/lib/active_record/associations/preloader/batch.rb +7 -1
- data/lib/active_record/associations/preloader/branch.rb +8 -1
- data/lib/active_record/associations/preloader/through_association.rb +1 -3
- data/lib/active_record/associations/singular_association.rb +14 -3
- data/lib/active_record/associations/through_association.rb +1 -1
- data/lib/active_record/associations.rb +250 -315
- data/lib/active_record/asynchronous_queries_tracker.rb +28 -24
- data/lib/active_record/attribute_assignment.rb +0 -2
- data/lib/active_record/attribute_methods/composite_primary_key.rb +84 -0
- data/lib/active_record/attribute_methods/primary_key.rb +25 -61
- data/lib/active_record/attribute_methods/query.rb +34 -0
- data/lib/active_record/attribute_methods/read.rb +1 -13
- data/lib/active_record/attribute_methods/serialization.rb +20 -27
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +19 -20
- data/lib/active_record/attribute_methods.rb +71 -75
- data/lib/active_record/attributes.rb +96 -68
- data/lib/active_record/autosave_association.rb +93 -58
- data/lib/active_record/base.rb +5 -7
- data/lib/active_record/callbacks.rb +1 -1
- data/lib/active_record/coders/json.rb +14 -5
- data/lib/active_record/connection_adapters/abstract/connection_handler.rb +50 -126
- data/lib/active_record/connection_adapters/abstract/connection_pool/queue.rb +16 -4
- data/lib/active_record/connection_adapters/abstract/connection_pool/reaper.rb +52 -13
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +681 -135
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +165 -86
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +226 -78
- data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -114
- data/lib/active_record/connection_adapters/abstract/schema_creation.rb +11 -7
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +37 -36
- data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +136 -34
- data/lib/active_record/connection_adapters/abstract/transaction.rb +163 -68
- data/lib/active_record/connection_adapters/abstract_adapter.rb +197 -129
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +112 -63
- data/lib/active_record/connection_adapters/column.rb +17 -4
- data/lib/active_record/connection_adapters/mysql/database_statements.rb +11 -3
- data/lib/active_record/connection_adapters/mysql/quoting.rb +50 -57
- data/lib/active_record/connection_adapters/mysql/schema_creation.rb +2 -0
- data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +41 -10
- data/lib/active_record/connection_adapters/mysql/schema_statements.rb +79 -46
- data/lib/active_record/connection_adapters/mysql2/database_statements.rb +97 -101
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +14 -33
- data/lib/active_record/connection_adapters/pool_config.rb +14 -13
- data/lib/active_record/connection_adapters/postgresql/column.rb +4 -0
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +98 -44
- data/lib/active_record/connection_adapters/postgresql/oid/array.rb +3 -3
- data/lib/active_record/connection_adapters/postgresql/oid/interval.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/point.rb +10 -0
- data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +14 -4
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +79 -68
- data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +2 -4
- data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +9 -17
- data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +44 -41
- data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +69 -32
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +155 -75
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +104 -121
- data/lib/active_record/connection_adapters/schema_cache.rb +126 -133
- data/lib/active_record/connection_adapters/sqlite3/column.rb +14 -1
- data/lib/active_record/connection_adapters/sqlite3/database_statements.rb +91 -95
- data/lib/active_record/connection_adapters/sqlite3/quoting.rb +57 -54
- data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +16 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +13 -0
- data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +43 -2
- data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +38 -15
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +231 -111
- data/lib/active_record/connection_adapters/statement_pool.rb +4 -2
- data/lib/active_record/connection_adapters/trilogy/database_statements.rb +40 -69
- data/lib/active_record/connection_adapters/trilogy_adapter.rb +19 -65
- data/lib/active_record/connection_adapters.rb +66 -0
- data/lib/active_record/connection_handling.rb +88 -46
- data/lib/active_record/core.rb +142 -58
- data/lib/active_record/counter_cache.rb +52 -18
- data/lib/active_record/database_configurations/connection_url_resolver.rb +9 -2
- data/lib/active_record/database_configurations/database_config.rb +28 -5
- data/lib/active_record/database_configurations/hash_config.rb +103 -41
- data/lib/active_record/database_configurations/url_config.rb +30 -1
- data/lib/active_record/database_configurations.rb +8 -4
- data/lib/active_record/delegated_type.rb +43 -19
- data/lib/active_record/dynamic_matchers.rb +56 -71
- data/lib/active_record/encryption/config.rb +3 -1
- data/lib/active_record/encryption/encryptable_record.rb +12 -12
- data/lib/active_record/encryption/encrypted_attribute_type.rb +35 -6
- data/lib/active_record/encryption/encryptor.rb +58 -22
- data/lib/active_record/encryption/extended_deterministic_queries.rb +4 -2
- 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 +4 -0
- data/lib/active_record/encryption/null_encryptor.rb +4 -0
- data/lib/active_record/encryption/read_only_null_encryptor.rb +4 -0
- data/lib/active_record/encryption/scheme.rb +9 -2
- data/lib/active_record/enum.rb +57 -36
- data/lib/active_record/errors.rb +76 -26
- data/lib/active_record/explain.rb +14 -25
- data/lib/active_record/explain_registry.rb +51 -2
- data/lib/active_record/filter_attribute_handler.rb +73 -0
- data/lib/active_record/fixture_set/table_row.rb +19 -2
- data/lib/active_record/fixtures.rb +39 -35
- data/lib/active_record/future_result.rb +21 -13
- data/lib/active_record/gem_version.rb +3 -3
- data/lib/active_record/inheritance.rb +5 -3
- data/lib/active_record/insert_all.rb +28 -20
- data/lib/active_record/integration.rb +4 -1
- data/lib/active_record/internal_metadata.rb +48 -34
- data/lib/active_record/locking/optimistic.rb +15 -7
- data/lib/active_record/locking/pessimistic.rb +5 -0
- data/lib/active_record/log_subscriber.rb +3 -34
- data/lib/active_record/message_pack.rb +1 -1
- data/lib/active_record/middleware/shard_selector.rb +34 -17
- data/lib/active_record/migration/command_recorder.rb +46 -14
- data/lib/active_record/migration/compatibility.rb +40 -25
- data/lib/active_record/migration/default_schema_versions_formatter.rb +30 -0
- data/lib/active_record/migration/default_strategy.rb +4 -5
- data/lib/active_record/migration/pending_migration_connection.rb +2 -2
- data/lib/active_record/migration.rb +119 -103
- data/lib/active_record/model_schema.rb +66 -78
- data/lib/active_record/nested_attributes.rb +17 -9
- data/lib/active_record/persistence.rb +161 -454
- data/lib/active_record/query_cache.rb +33 -15
- data/lib/active_record/query_logs.rb +106 -39
- data/lib/active_record/query_logs_formatter.rb +17 -28
- data/lib/active_record/querying.rb +28 -16
- data/lib/active_record/railtie.rb +61 -74
- data/lib/active_record/railties/controller_runtime.rb +19 -5
- data/lib/active_record/railties/databases.rake +64 -78
- data/lib/active_record/railties/job_checkpoints.rb +15 -0
- data/lib/active_record/railties/job_runtime.rb +10 -11
- data/lib/active_record/reflection.rb +147 -53
- data/lib/active_record/relation/batches/batch_enumerator.rb +19 -5
- data/lib/active_record/relation/batches.rb +153 -73
- data/lib/active_record/relation/calculations.rb +146 -96
- data/lib/active_record/relation/delegation.rb +30 -23
- data/lib/active_record/relation/finder_methods.rb +64 -34
- data/lib/active_record/relation/merger.rb +12 -14
- data/lib/active_record/relation/predicate_builder/array_handler.rb +2 -2
- data/lib/active_record/relation/predicate_builder/association_query_value.rb +19 -11
- data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +8 -8
- data/lib/active_record/relation/predicate_builder/relation_handler.rb +4 -3
- data/lib/active_record/relation/predicate_builder.rb +25 -10
- data/lib/active_record/relation/query_attribute.rb +4 -2
- data/lib/active_record/relation/query_methods.rb +351 -124
- data/lib/active_record/relation/spawn_methods.rb +9 -25
- data/lib/active_record/relation/where_clause.rb +17 -30
- data/lib/active_record/relation.rb +590 -124
- data/lib/active_record/result.rb +131 -59
- data/lib/active_record/runtime_registry.rb +44 -21
- data/lib/active_record/sanitization.rb +33 -25
- data/lib/active_record/schema.rb +8 -6
- data/lib/active_record/schema_dumper.rb +66 -31
- data/lib/active_record/schema_migration.rb +31 -14
- data/lib/active_record/scoping/named.rb +6 -2
- data/lib/active_record/scoping.rb +0 -1
- data/lib/active_record/secure_token.rb +3 -3
- data/lib/active_record/signed_id.rb +68 -20
- data/lib/active_record/statement_cache.rb +29 -25
- data/lib/active_record/store.rb +51 -22
- data/lib/active_record/structured_event_subscriber.rb +85 -0
- data/lib/active_record/table_metadata.rb +7 -33
- data/lib/active_record/tasks/abstract_tasks.rb +76 -0
- data/lib/active_record/tasks/database_tasks.rb +118 -90
- data/lib/active_record/tasks/mysql_database_tasks.rb +3 -42
- data/lib/active_record/tasks/postgresql_database_tasks.rb +14 -40
- data/lib/active_record/tasks/sqlite_database_tasks.rb +17 -28
- data/lib/active_record/test_databases.rb +14 -4
- data/lib/active_record/test_fixtures.rb +123 -89
- data/lib/active_record/testing/query_assertions.rb +127 -0
- data/lib/active_record/timestamp.rb +6 -4
- data/lib/active_record/token_for.rb +22 -12
- data/lib/active_record/touch_later.rb +1 -1
- data/lib/active_record/transaction.rb +129 -0
- data/lib/active_record/transactions.rb +97 -18
- data/lib/active_record/translation.rb +0 -2
- data/lib/active_record/type/hash_lookup_type_map.rb +2 -1
- data/lib/active_record/type/internal/timezone.rb +7 -0
- data/lib/active_record/type/json.rb +15 -2
- data/lib/active_record/type/serialized.rb +12 -7
- data/lib/active_record/type/type_map.rb +1 -1
- data/lib/active_record/type_caster/connection.rb +5 -4
- data/lib/active_record/validations/associated.rb +10 -4
- data/lib/active_record/validations/uniqueness.rb +23 -18
- data/lib/active_record/validations.rb +4 -1
- data/lib/active_record.rb +203 -59
- data/lib/arel/alias_predication.rb +3 -1
- data/lib/arel/collectors/bind.rb +4 -2
- data/lib/arel/collectors/composite.rb +7 -0
- data/lib/arel/collectors/sql_string.rb +2 -2
- data/lib/arel/collectors/substitute_binds.rb +3 -3
- data/lib/arel/crud.rb +8 -11
- data/lib/arel/delete_manager.rb +5 -0
- data/lib/arel/nodes/binary.rb +1 -7
- data/lib/arel/nodes/bound_sql_literal.rb +9 -5
- data/lib/arel/nodes/count.rb +2 -2
- data/lib/arel/nodes/delete_statement.rb +4 -2
- data/lib/arel/nodes/function.rb +4 -10
- data/lib/arel/nodes/named_function.rb +2 -2
- data/lib/arel/nodes/{and.rb → nary.rb} +5 -2
- data/lib/arel/nodes/node.rb +6 -5
- data/lib/arel/nodes/sql_literal.rb +8 -1
- data/lib/arel/nodes/update_statement.rb +4 -2
- data/lib/arel/nodes.rb +2 -4
- data/lib/arel/predications.rb +1 -1
- data/lib/arel/select_manager.rb +14 -5
- data/lib/arel/table.rb +3 -7
- data/lib/arel/tree_manager.rb +3 -2
- data/lib/arel/update_manager.rb +7 -1
- data/lib/arel/visitors/dot.rb +3 -3
- data/lib/arel/visitors/mysql.rb +9 -4
- data/lib/arel/visitors/postgresql.rb +56 -12
- data/lib/arel/visitors/sqlite.rb +80 -8
- data/lib/arel/visitors/to_sql.rb +34 -37
- data/lib/arel.rb +10 -4
- data/lib/rails/generators/active_record/application_record/USAGE +1 -1
- data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +4 -1
- metadata +23 -14
- data/lib/active_record/explain_subscriber.rb +0 -34
- data/lib/active_record/normalization.rb +0 -167
- data/lib/active_record/relation/record_fetch_warning.rb +0 -49
|
@@ -36,11 +36,26 @@ module ActiveRecord
|
|
|
36
36
|
class_attribute :pre_loaded_fixtures, default: false
|
|
37
37
|
class_attribute :lock_threads, default: true
|
|
38
38
|
class_attribute :fixture_sets, default: {}
|
|
39
|
+
class_attribute :database_transactions_config, default: {}
|
|
39
40
|
|
|
40
41
|
ActiveSupport.run_load_hooks(:active_record_fixtures, self)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
module ClassMethods
|
|
45
|
+
# Do not use transactional tests for the given database. This overrides
|
|
46
|
+
# the default setting as defined by `use_transactional_tests`, which
|
|
47
|
+
# applies to all database connection pools not explicitly configured here.
|
|
48
|
+
def skip_transactional_tests_for_database(database_name)
|
|
49
|
+
use_transactional_tests_for_database(database_name, false)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Enable or disable transactions per database. This overrides the default
|
|
53
|
+
# setting as defined by `use_transactional_tests`, which applies to all
|
|
54
|
+
# database connection pools not explicitly configured here.
|
|
55
|
+
def use_transactional_tests_for_database(database_name, enabled = true)
|
|
56
|
+
self.database_transactions_config = database_transactions_config.merge(database_name => enabled)
|
|
57
|
+
end
|
|
58
|
+
|
|
44
59
|
# Sets the model class for a fixture when the class name cannot be inferred from the fixture name.
|
|
45
60
|
#
|
|
46
61
|
# Examples:
|
|
@@ -53,20 +68,6 @@ module ActiveRecord
|
|
|
53
68
|
self.fixture_class_names = fixture_class_names.merge(class_names.stringify_keys)
|
|
54
69
|
end
|
|
55
70
|
|
|
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
|
-
|
|
70
71
|
def fixtures(*fixture_set_names)
|
|
71
72
|
if fixture_set_names.first == :all
|
|
72
73
|
raise StandardError, "No fixture path found. Please set `#{self}.fixture_paths`." if fixture_paths.blank?
|
|
@@ -79,7 +80,7 @@ module ActiveRecord
|
|
|
79
80
|
fixture_set_names = fixture_set_names.flatten.map(&:to_s)
|
|
80
81
|
end
|
|
81
82
|
|
|
82
|
-
self.fixture_table_names
|
|
83
|
+
self.fixture_table_names = (fixture_table_names | fixture_set_names).sort
|
|
83
84
|
setup_fixture_accessors(fixture_set_names)
|
|
84
85
|
end
|
|
85
86
|
|
|
@@ -110,45 +111,96 @@ module ActiveRecord
|
|
|
110
111
|
end
|
|
111
112
|
end
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
fixture_paths.first
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def run_in_transaction?
|
|
123
|
-
use_transactional_tests &&
|
|
124
|
-
!self.class.uses_transaction?(name)
|
|
114
|
+
# Generic fixture accessor for fixture names that may conflict with other methods.
|
|
115
|
+
#
|
|
116
|
+
# assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
|
|
117
|
+
# assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name
|
|
118
|
+
def fixture(fixture_set_name, *fixture_names)
|
|
119
|
+
active_record_fixture(fixture_set_name, *fixture_names)
|
|
125
120
|
end
|
|
126
121
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
122
|
+
private
|
|
123
|
+
def run_in_transaction?
|
|
124
|
+
has_explicit_config = database_transactions_config.any? { |_, enabled| enabled }
|
|
125
|
+
(use_transactional_tests || has_explicit_config) &&
|
|
126
|
+
!self.class.uses_transaction?(name)
|
|
130
127
|
end
|
|
131
128
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
|
129
|
+
def setup_fixtures(config = ActiveRecord::Base)
|
|
130
|
+
if pre_loaded_fixtures && !use_transactional_tests
|
|
131
|
+
raise RuntimeError, "pre_loaded_fixtures requires use_transactional_tests"
|
|
132
|
+
end
|
|
137
133
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
134
|
+
@fixture_cache = {}
|
|
135
|
+
@fixture_cache_key = [self.class.fixture_table_names.dup, self.class.fixture_paths.dup, self.class.fixture_class_names.dup]
|
|
136
|
+
@fixture_connection_pools = []
|
|
137
|
+
@@already_loaded_fixtures ||= {}
|
|
138
|
+
@connection_subscriber = nil
|
|
139
|
+
@saved_pool_configs = Hash.new { |hash, key| hash[key] = {} }
|
|
140
|
+
|
|
141
|
+
if run_in_transaction?
|
|
142
|
+
# Load fixtures once and begin transaction.
|
|
143
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key]
|
|
144
|
+
unless @loaded_fixtures
|
|
145
|
+
@@already_loaded_fixtures.clear
|
|
146
|
+
@loaded_fixtures = @@already_loaded_fixtures[@fixture_cache_key] = load_fixtures(config)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
setup_transactional_fixtures
|
|
142
150
|
else
|
|
151
|
+
# Load fixtures for every test.
|
|
152
|
+
ActiveRecord::FixtureSet.reset_cache
|
|
153
|
+
invalidate_already_loaded_fixtures
|
|
143
154
|
@loaded_fixtures = load_fixtures(config)
|
|
144
|
-
@@already_loaded_fixtures[self.class] = @loaded_fixtures
|
|
145
155
|
end
|
|
156
|
+
setup_asynchronous_queries_session
|
|
157
|
+
|
|
158
|
+
# Instantiate fixtures for every test if requested.
|
|
159
|
+
instantiate_fixtures if use_instantiated_fixtures
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def teardown_fixtures
|
|
163
|
+
teardown_asynchronous_queries_session
|
|
164
|
+
|
|
165
|
+
# Rollback changes if a transaction is active.
|
|
166
|
+
if run_in_transaction?
|
|
167
|
+
teardown_transactional_fixtures
|
|
168
|
+
else
|
|
169
|
+
ActiveRecord::FixtureSet.reset_cache
|
|
170
|
+
invalidate_already_loaded_fixtures
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def setup_asynchronous_queries_session
|
|
177
|
+
@_async_queries_session = ActiveRecord::Base.asynchronous_queries_tracker.start_session
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def teardown_asynchronous_queries_session
|
|
181
|
+
ActiveRecord::Base.asynchronous_queries_tracker.finalize_session(true) if @_async_queries_session
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def invalidate_already_loaded_fixtures
|
|
185
|
+
@@already_loaded_fixtures.clear
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def transactional_tests_for_pool?(pool)
|
|
189
|
+
database_transactions_config.fetch(pool.db_config.name.to_sym, use_transactional_tests)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def setup_transactional_fixtures
|
|
193
|
+
setup_shared_connection_pool
|
|
146
194
|
|
|
147
195
|
# Begin transactions for connections already established
|
|
148
|
-
@
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
196
|
+
@fixture_connection_pools = ActiveRecord::Base.connection_handler.connection_pool_list(:writing)
|
|
197
|
+
|
|
198
|
+
# Filter to pools that want to use transactions
|
|
199
|
+
@fixture_connection_pools.select! { |pool| transactional_tests_for_pool?(pool) }
|
|
200
|
+
|
|
201
|
+
@fixture_connection_pools.each do |pool|
|
|
202
|
+
pool.pin_connection!(lock_threads)
|
|
203
|
+
pool.lease_connection
|
|
152
204
|
end
|
|
153
205
|
|
|
154
206
|
# When connections are established in the future, begin a transaction too
|
|
@@ -157,59 +209,33 @@ module ActiveRecord
|
|
|
157
209
|
shard = payload[:shard] if payload.key?(:shard)
|
|
158
210
|
|
|
159
211
|
if connection_name
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
rescue ConnectionNotEstablished
|
|
163
|
-
connection = nil
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
if connection
|
|
212
|
+
pool = ActiveRecord::Base.connection_handler.retrieve_connection_pool(connection_name, shard: shard)
|
|
213
|
+
if pool
|
|
167
214
|
setup_shared_connection_pool
|
|
168
215
|
|
|
169
|
-
if
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
216
|
+
# Don't begin a transaction if we've already done so, or are not using them for this pool
|
|
217
|
+
if !@fixture_connection_pools.include?(pool) && transactional_tests_for_pool?(pool)
|
|
218
|
+
pool.pin_connection!(lock_threads)
|
|
219
|
+
pool.lease_connection
|
|
220
|
+
@fixture_connection_pools << pool
|
|
173
221
|
end
|
|
174
222
|
end
|
|
175
223
|
end
|
|
176
224
|
end
|
|
177
|
-
|
|
178
|
-
# Load fixtures for every test.
|
|
179
|
-
else
|
|
180
|
-
ActiveRecord::FixtureSet.reset_cache
|
|
181
|
-
@@already_loaded_fixtures[self.class] = nil
|
|
182
|
-
@loaded_fixtures = load_fixtures(config)
|
|
183
225
|
end
|
|
184
226
|
|
|
185
|
-
|
|
186
|
-
instantiate_fixtures if use_instantiated_fixtures
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def teardown_fixtures
|
|
190
|
-
# Rollback changes if a transaction is active.
|
|
191
|
-
if run_in_transaction?
|
|
227
|
+
def teardown_transactional_fixtures
|
|
192
228
|
ActiveSupport::Notifications.unsubscribe(@connection_subscriber) if @connection_subscriber
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
229
|
+
|
|
230
|
+
unless @fixture_connection_pools.map(&:unpin_connection!).all?
|
|
231
|
+
# Something caused the transaction to be committed or rolled back
|
|
232
|
+
# We can no longer trust the database is in a clean state.
|
|
233
|
+
@@already_loaded_fixtures.clear
|
|
196
234
|
end
|
|
197
|
-
@
|
|
235
|
+
@fixture_connection_pools.clear
|
|
198
236
|
teardown_shared_connection_pool
|
|
199
|
-
else
|
|
200
|
-
ActiveRecord::FixtureSet.reset_cache
|
|
201
237
|
end
|
|
202
238
|
|
|
203
|
-
ActiveRecord::Base.connection_handler.clear_active_connections!(:all)
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def enlist_fixture_connections
|
|
207
|
-
setup_shared_connection_pool
|
|
208
|
-
|
|
209
|
-
ActiveRecord::Base.connection_handler.connection_pool_list(:writing).map(&:connection)
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
private
|
|
213
239
|
# Shares the writing connection pool with connections on
|
|
214
240
|
# other handlers.
|
|
215
241
|
#
|
|
@@ -272,22 +298,30 @@ module ActiveRecord
|
|
|
272
298
|
use_instantiated_fixtures != :no_instances
|
|
273
299
|
end
|
|
274
300
|
|
|
275
|
-
def method_missing(
|
|
276
|
-
if
|
|
277
|
-
|
|
301
|
+
def method_missing(method, ...)
|
|
302
|
+
if fixture_sets.key?(method.name)
|
|
303
|
+
active_record_fixture(method, ...)
|
|
278
304
|
else
|
|
279
305
|
super
|
|
280
306
|
end
|
|
281
307
|
end
|
|
282
308
|
|
|
283
|
-
def respond_to_missing?(
|
|
284
|
-
if include_private && fixture_sets.key?(name
|
|
309
|
+
def respond_to_missing?(method, include_private = false)
|
|
310
|
+
if include_private && fixture_sets.key?(method.name)
|
|
285
311
|
true
|
|
286
312
|
else
|
|
287
313
|
super
|
|
288
314
|
end
|
|
289
315
|
end
|
|
290
316
|
|
|
317
|
+
def active_record_fixture(fixture_set_name, *fixture_names)
|
|
318
|
+
if fs_name = fixture_sets[fixture_set_name.name]
|
|
319
|
+
access_fixture(fs_name, *fixture_names)
|
|
320
|
+
else
|
|
321
|
+
raise StandardError, "No fixture set named '#{fixture_set_name.inspect}'"
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
291
325
|
def access_fixture(fs_name, *fixture_names)
|
|
292
326
|
force_reload = fixture_names.pop if fixture_names.last == true || fixture_names.last == :reload
|
|
293
327
|
return_single_record = fixture_names.size == 1
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveRecord
|
|
4
|
+
module Assertions
|
|
5
|
+
module QueryAssertions
|
|
6
|
+
# Asserts that the number of SQL queries executed in the given block matches the expected count.
|
|
7
|
+
#
|
|
8
|
+
# # Check for exact number of queries
|
|
9
|
+
# assert_queries_count(1) { Post.first }
|
|
10
|
+
#
|
|
11
|
+
# # Check for any number of queries
|
|
12
|
+
# assert_queries_count { Post.first }
|
|
13
|
+
#
|
|
14
|
+
# Any unmaterialized transactions will be materialized to ensure only
|
|
15
|
+
# queries attempted intside the block are counted.
|
|
16
|
+
#
|
|
17
|
+
# If the +:include_schema+ option is provided, any queries (including
|
|
18
|
+
# schema related) are counted. Setting this option also skips leasing a
|
|
19
|
+
# connection to materialize pending transactions since we want to count
|
|
20
|
+
# queries executed at connection open (e.g., type map).
|
|
21
|
+
#
|
|
22
|
+
# assert_queries_count(1, include_schema: true) { Post.columns }
|
|
23
|
+
#
|
|
24
|
+
def assert_queries_count(count = nil, include_schema: false, &block)
|
|
25
|
+
ActiveRecord::Base.lease_connection.materialize_transactions unless include_schema
|
|
26
|
+
|
|
27
|
+
counter = SQLCounter.new
|
|
28
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
29
|
+
result = _assert_nothing_raised_or_warn("assert_queries_count", &block)
|
|
30
|
+
queries = include_schema ? counter.log_all : counter.log
|
|
31
|
+
if count
|
|
32
|
+
assert_equal count, queries.size, "#{queries.size} instead of #{count} queries were executed. Queries: #{queries.join("\n\n")}"
|
|
33
|
+
else
|
|
34
|
+
assert_operator queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
|
35
|
+
end
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Asserts that no SQL queries are executed in the given block.
|
|
41
|
+
#
|
|
42
|
+
# assert_no_queries { post.comments }
|
|
43
|
+
#
|
|
44
|
+
# If the +:include_schema+ option is provided, any queries (including schema related) are counted.
|
|
45
|
+
#
|
|
46
|
+
# assert_no_queries(include_schema: true) { Post.columns }
|
|
47
|
+
#
|
|
48
|
+
def assert_no_queries(include_schema: false, &block)
|
|
49
|
+
assert_queries_count(0, include_schema: include_schema, &block)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Asserts that the SQL queries executed in the given block match expected pattern.
|
|
53
|
+
#
|
|
54
|
+
# # Check for exact number of queries
|
|
55
|
+
# assert_queries_match(/LIMIT \?/, count: 1) { Post.first }
|
|
56
|
+
#
|
|
57
|
+
# # Check for any number of queries
|
|
58
|
+
# assert_queries_match(/LIMIT \?/) { Post.first }
|
|
59
|
+
#
|
|
60
|
+
# If the +:include_schema+ option is provided, any queries (including schema related)
|
|
61
|
+
# that match the matcher are considered.
|
|
62
|
+
#
|
|
63
|
+
# assert_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
|
|
64
|
+
#
|
|
65
|
+
def assert_queries_match(match, count: nil, include_schema: false, &block)
|
|
66
|
+
ActiveRecord::Base.lease_connection.materialize_transactions
|
|
67
|
+
|
|
68
|
+
counter = SQLCounter.new
|
|
69
|
+
ActiveSupport::Notifications.subscribed(counter, "sql.active_record") do
|
|
70
|
+
result = _assert_nothing_raised_or_warn("assert_queries_match", &block)
|
|
71
|
+
queries = include_schema ? counter.log_all : counter.log
|
|
72
|
+
matched_queries = queries.select { |query| match === query }
|
|
73
|
+
|
|
74
|
+
if count
|
|
75
|
+
assert_equal count, matched_queries.size, "#{matched_queries.size} instead of #{count} queries were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
|
76
|
+
else
|
|
77
|
+
assert_operator matched_queries.size, :>=, 1, "1 or more queries expected, but none were executed.#{queries.empty? ? '' : "\nQueries:\n#{queries.join("\n")}"}"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Asserts that no SQL queries matching the pattern are executed in the given block.
|
|
85
|
+
#
|
|
86
|
+
# assert_no_queries_match(/SELECT/i) { post.comments }
|
|
87
|
+
#
|
|
88
|
+
# If the +:include_schema+ option is provided, any queries (including schema related)
|
|
89
|
+
# that match the matcher are counted.
|
|
90
|
+
#
|
|
91
|
+
# assert_no_queries_match(/FROM pg_attribute/i, include_schema: true) { Post.columns }
|
|
92
|
+
#
|
|
93
|
+
def assert_no_queries_match(match, include_schema: false, &block)
|
|
94
|
+
assert_queries_match(match, count: 0, include_schema: include_schema, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class SQLCounter # :nodoc:
|
|
98
|
+
attr_reader :log_full, :log_all
|
|
99
|
+
|
|
100
|
+
def initialize
|
|
101
|
+
@log_full = []
|
|
102
|
+
@log_all = []
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def log
|
|
106
|
+
@log_full.map(&:first)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def call(*, payload)
|
|
110
|
+
return if payload[:cached]
|
|
111
|
+
|
|
112
|
+
sql = payload[:sql]
|
|
113
|
+
@log_all << sql
|
|
114
|
+
|
|
115
|
+
unless payload[:name] == "SCHEMA"
|
|
116
|
+
bound_values = (payload[:binds] || []).map do |value|
|
|
117
|
+
value = value.value_for_database if value.respond_to?(:value_for_database)
|
|
118
|
+
value
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
@log_full << [sql, bound_values]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -77,7 +77,7 @@ module ActiveRecord
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def current_time_from_proper_timezone
|
|
80
|
-
|
|
80
|
+
with_connection { |c| c.default_timezone == :utc ? Time.now.utc : Time.now }
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
protected
|
|
@@ -162,15 +162,17 @@ module ActiveRecord
|
|
|
162
162
|
|
|
163
163
|
def max_updated_column_timestamp
|
|
164
164
|
timestamp_attributes_for_update_in_model
|
|
165
|
-
.filter_map { |attr| self[attr]
|
|
165
|
+
.filter_map { |attr| (v = self[attr]) && (v.is_a?(::Time) ? v : v.to_time) }
|
|
166
166
|
.max
|
|
167
167
|
end
|
|
168
168
|
|
|
169
169
|
# Clear attributes and changed_attributes
|
|
170
170
|
def clear_timestamp_attributes
|
|
171
171
|
all_timestamp_attributes_in_model.each do |attribute_name|
|
|
172
|
-
self[attribute_name]
|
|
173
|
-
|
|
172
|
+
if self[attribute_name]
|
|
173
|
+
self[attribute_name] = nil
|
|
174
|
+
clear_attribute_change(attribute_name)
|
|
175
|
+
end
|
|
174
176
|
end
|
|
175
177
|
end
|
|
176
178
|
end
|
|
@@ -35,6 +35,24 @@ module ActiveRecord
|
|
|
35
35
|
end
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
+
module RelationMethods
|
|
39
|
+
# Finds a record using a given +token+ for a predefined +purpose+. Returns
|
|
40
|
+
# +nil+ if the token is invalid or the record was not found.
|
|
41
|
+
def find_by_token_for(purpose, token)
|
|
42
|
+
raise UnknownPrimaryKey.new(self) unless model.primary_key
|
|
43
|
+
model.token_definitions.fetch(purpose).resolve_token(token) { |id| find_by(model.primary_key => [id]) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Finds a record using a given +token+ for a predefined +purpose+. Raises
|
|
47
|
+
# ActiveSupport::MessageVerifier::InvalidSignature if the token is invalid
|
|
48
|
+
# (e.g. expired, bad format, etc). Raises ActiveRecord::RecordNotFound if
|
|
49
|
+
# the token is valid but the record was not found.
|
|
50
|
+
def find_by_token_for!(purpose, token)
|
|
51
|
+
model.token_definitions.fetch(purpose).resolve_token(token) { |id| find(id) } ||
|
|
52
|
+
(raise ActiveSupport::MessageVerifier::InvalidSignature)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
38
56
|
module ClassMethods
|
|
39
57
|
# Defines the behavior of tokens generated for a specific +purpose+.
|
|
40
58
|
# A token can be generated by calling TokenFor#generate_token_for on a
|
|
@@ -85,20 +103,12 @@ module ActiveRecord
|
|
|
85
103
|
self.token_definitions = token_definitions.merge(purpose => TokenDefinition.new(self, purpose, expires_in, block))
|
|
86
104
|
end
|
|
87
105
|
|
|
88
|
-
|
|
89
|
-
|
|
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) }
|
|
106
|
+
def find_by_token_for(purpose, token) # :nodoc:
|
|
107
|
+
all.find_by_token_for(purpose, token)
|
|
93
108
|
end
|
|
94
109
|
|
|
95
|
-
|
|
96
|
-
|
|
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)
|
|
110
|
+
def find_by_token_for!(purpose, token) # :nodoc:
|
|
111
|
+
all.find_by_token_for!(purpose, token)
|
|
102
112
|
end
|
|
103
113
|
end
|
|
104
114
|
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/core_ext/digest"
|
|
4
|
+
|
|
5
|
+
module ActiveRecord
|
|
6
|
+
# Class specifies the interface to interact with the current transaction state.
|
|
7
|
+
#
|
|
8
|
+
# It can either map to an actual transaction/savepoint, or represent the
|
|
9
|
+
# absence of a transaction.
|
|
10
|
+
#
|
|
11
|
+
# == State
|
|
12
|
+
#
|
|
13
|
+
# We say that a transaction is _finalized_ when it wraps a real transaction
|
|
14
|
+
# that has been either committed or rolled back.
|
|
15
|
+
#
|
|
16
|
+
# A transaction is _open_ if it wraps a real transaction that is not finalized.
|
|
17
|
+
#
|
|
18
|
+
# On the other hand, a transaction is _closed_ when it is not open. That is,
|
|
19
|
+
# when it represents absence of transaction, or it wraps a real but finalized
|
|
20
|
+
# one.
|
|
21
|
+
#
|
|
22
|
+
# You can check whether a transaction is open or closed with the +open?+ and
|
|
23
|
+
# +closed?+ predicates:
|
|
24
|
+
#
|
|
25
|
+
# if Article.current_transaction.open?
|
|
26
|
+
# # We are inside a real and not finalized transaction.
|
|
27
|
+
# end
|
|
28
|
+
#
|
|
29
|
+
# Closed transactions are +blank?+ too.
|
|
30
|
+
#
|
|
31
|
+
# == Callbacks
|
|
32
|
+
#
|
|
33
|
+
# After updating the database state, you may sometimes need to perform some extra work, or reflect these
|
|
34
|
+
# changes in a remote system like clearing or updating a cache:
|
|
35
|
+
#
|
|
36
|
+
# def publish_article(article)
|
|
37
|
+
# article.update!(published: true)
|
|
38
|
+
# NotificationService.article_published(article)
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# The above code works but has one important flaw, which is that it no longer works properly if called inside
|
|
42
|
+
# a transaction, as it will interact with the remote system before the changes are persisted:
|
|
43
|
+
#
|
|
44
|
+
# Article.transaction do
|
|
45
|
+
# article = create_article(article)
|
|
46
|
+
# publish_article(article)
|
|
47
|
+
# end
|
|
48
|
+
#
|
|
49
|
+
# The callbacks offered by ActiveRecord::Transaction allow to rewriting this method in a way that is compatible
|
|
50
|
+
# with transactions:
|
|
51
|
+
#
|
|
52
|
+
# def publish_article(article)
|
|
53
|
+
# article.update!(published: true)
|
|
54
|
+
# Article.current_transaction.after_commit do
|
|
55
|
+
# NotificationService.article_published(article)
|
|
56
|
+
# end
|
|
57
|
+
# end
|
|
58
|
+
#
|
|
59
|
+
# In the above example, if +publish_article+ is called inside a transaction, the callback will be invoked
|
|
60
|
+
# after the transaction is successfully committed, and if called outside a transaction, the callback will be invoked
|
|
61
|
+
# immediately.
|
|
62
|
+
#
|
|
63
|
+
# == Caveats
|
|
64
|
+
#
|
|
65
|
+
# When using after_commit callbacks, it is important to note that if the callback raises an error, the transaction
|
|
66
|
+
# won't be rolled back as it was already committed. Relying solely on these to synchronize state between multiple
|
|
67
|
+
# systems may lead to consistency issues.
|
|
68
|
+
class Transaction
|
|
69
|
+
def initialize(internal_transaction) # :nodoc:
|
|
70
|
+
@internal_transaction = internal_transaction
|
|
71
|
+
@uuid = nil
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Registers a block to be called after the transaction is fully committed.
|
|
75
|
+
#
|
|
76
|
+
# If there is no currently open transactions, the block is called
|
|
77
|
+
# immediately, unless the transaction is finalized, in which case attempting
|
|
78
|
+
# to register the callback raises ActiveRecord::ActiveRecordError.
|
|
79
|
+
#
|
|
80
|
+
# If the transaction has a parent transaction, the callback is transferred to
|
|
81
|
+
# the parent when the current transaction commits, or dropped when the current transaction
|
|
82
|
+
# is rolled back. This operation is repeated until the outermost transaction is reached.
|
|
83
|
+
#
|
|
84
|
+
# If the callback raises an error, the transaction remains committed.
|
|
85
|
+
def after_commit(&block)
|
|
86
|
+
if @internal_transaction.nil?
|
|
87
|
+
yield
|
|
88
|
+
else
|
|
89
|
+
@internal_transaction.after_commit(&block)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Registers a block to be called after the transaction is rolled back.
|
|
94
|
+
#
|
|
95
|
+
# If there is no currently open transactions, the block is not called. But
|
|
96
|
+
# if the transaction is finalized, attempting to register the callback
|
|
97
|
+
# raises ActiveRecord::ActiveRecordError.
|
|
98
|
+
#
|
|
99
|
+
# If the transaction is successfully committed but has a parent
|
|
100
|
+
# transaction, the callback is automatically added to the parent transaction.
|
|
101
|
+
#
|
|
102
|
+
# If the entire chain of nested transactions are all successfully committed,
|
|
103
|
+
# the block is never called.
|
|
104
|
+
def after_rollback(&block)
|
|
105
|
+
@internal_transaction&.after_rollback(&block)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Returns true if the transaction exists and isn't finalized yet.
|
|
109
|
+
def open?
|
|
110
|
+
!closed?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns true if the transaction doesn't exist or is finalized.
|
|
114
|
+
def closed?
|
|
115
|
+
@internal_transaction.nil? || @internal_transaction.closed?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
alias_method :blank?, :closed?
|
|
119
|
+
|
|
120
|
+
# Returns a UUID for this transaction or +nil+ if no transaction is open.
|
|
121
|
+
def uuid
|
|
122
|
+
if @internal_transaction
|
|
123
|
+
@uuid ||= Digest::UUID.uuid_v4
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
NULL_TRANSACTION = new(nil).freeze
|
|
128
|
+
end
|
|
129
|
+
end
|