Skip to content

Commit 51f8126

Browse files
authored
Merge pull request rails#52080 from rails/fxn/start_transaction_event
Define the new start_transaction.active_record event
2 parents 0733ab5 + f64a413 commit 51f8126

File tree

3 files changed

+110
-6
lines changed

3 files changed

+110
-6
lines changed

activerecord/lib/active_record/connection_adapters/abstract/transaction.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,9 @@ def start
9191
raise InstrumentationAlreadyStartedError.new("Called start on an already started transaction") if @started
9292
@started = true
9393

94-
@payload = @base_payload.dup
94+
ActiveSupport::Notifications.instrument("start_transaction.active_record", @base_payload)
95+
96+
@payload = @base_payload.dup # We dup because the payload for a given event is mutated later to add the outcome.
9597
@handle = ActiveSupport::Notifications.instrumenter.build_handle("transaction.active_record", @payload)
9698
@handle.start
9799
end

activerecord/test/cases/transaction_instrumentation_test.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,62 @@ class TransactionInstrumentationTest < ActiveRecord::TestCase
77
self.use_transactional_tests = false
88
fixtures :topics
99

10+
def test_start_transaction_is_triggered_when_the_transaction_is_materialized
11+
transactions = []
12+
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
13+
assert event.payload[:connection]
14+
transactions << event.payload[:transaction]
15+
end
16+
17+
Topic.transaction do |transaction|
18+
assert_empty transactions # A transaction call, per se, does not trigger the event.
19+
topics(:first).touch
20+
assert_equal [transaction], transactions
21+
end
22+
ensure
23+
ActiveSupport::Notifications.unsubscribe(subscriber)
24+
end
25+
26+
def test_start_transaction_is_not_triggered_for_ordinary_nested_calls
27+
transactions = []
28+
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
29+
assert event.payload[:connection]
30+
transactions << event.payload[:transaction]
31+
end
32+
33+
Topic.transaction do |t1|
34+
topics(:first).touch
35+
assert_equal [t1], transactions
36+
37+
Topic.transaction do |_t2|
38+
topics(:first).touch
39+
assert_equal [t1], transactions
40+
end
41+
end
42+
ensure
43+
ActiveSupport::Notifications.unsubscribe(subscriber)
44+
end
45+
46+
def test_start_transaction_is_triggered_for_requires_new
47+
transactions = []
48+
subscriber = ActiveSupport::Notifications.subscribe("start_transaction.active_record") do |event|
49+
assert event.payload[:connection]
50+
transactions << event.payload[:transaction]
51+
end
52+
53+
Topic.transaction do |t1|
54+
topics(:first).touch
55+
assert_equal [t1], transactions
56+
57+
Topic.transaction(requires_new: true) do |t2|
58+
topics(:first).touch
59+
assert_equal [t1, t2], transactions
60+
end
61+
end
62+
ensure
63+
ActiveSupport::Notifications.unsubscribe(subscriber)
64+
end
65+
1066
def test_transaction_instrumentation_on_commit
1167
topic = topics(:fifth)
1268

guides/source/active_support_instrumentation.md

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,20 +410,66 @@ This event is only emitted when [`config.active_record.action_on_strict_loading_
410410
}
411411
```
412412

413+
#### `start_transaction.active_record`
414+
415+
This event is emitted when a transaction has been started.
416+
417+
| Key | Value |
418+
| -------------------- | ---------------------------------------------------- |
419+
| `:transaction` | Transaction object |
420+
| `:connection` | Connection object |
421+
422+
Please, note that Active Record does not create the actual database transaction
423+
until needed:
424+
425+
```ruby
426+
ActiveRecord::Base.transaction do
427+
# We are inside the block, but no event has been triggered yet.
428+
429+
# The following line makes Active Record start the transaction.
430+
User.count # Event fired here.
431+
end
432+
```
433+
434+
Remember that ordinary nested calls do not create new transactions:
435+
436+
```ruby
437+
ActiveRecord::Base.transaction do |t1|
438+
User.count # Fires an event for t1.
439+
ActiveRecord::Base.transaction do |t2|
440+
# The next line fires no event for t2, because the only
441+
# real database transaction in this example is t1.
442+
User.first.touch
443+
end
444+
end
445+
```
446+
447+
However, if `requires_new: true` is passed, you get an event for the nested
448+
transaction too. This might be a savepoint under the hood:
449+
450+
```ruby
451+
ActiveRecord::Base.transaction do |t1|
452+
User.count # Fires an event for t1.
453+
ActiveRecord::Base.transaction(requires_new: true) do |t2|
454+
User.first.touch # Fires an event for t2.
455+
end
456+
end
457+
```
458+
413459
#### `transaction.active_record`
414460

415-
This event is emmited for every transaction to the database.
461+
This event is emitted when a database transaction finishes. The state of the
462+
transaction can be found in the `:outcome` key.
416463

417464
| Key | Value |
418465
| -------------------- | ---------------------------------------------------- |
419466
| `:transaction` | Transaction object |
420467
| `:outcome` | `:commit`, `:rollback`, `:restart`, or `:incomplete` |
421468
| `:connection` | Connection object |
422469

423-
Please note that at this point the transaction has been finished, and its state
424-
is in the `:outcome` key. In practice, you cannot do much with the transaction
425-
object, but it may still be helpful for tracing database activity. For example,
426-
by tracking `transaction.uuid`.
470+
In practice, you cannot do much with the transaction object, but it may still be
471+
helpful for tracing database activity. For example, by tracking
472+
`transaction.uuid`.
427473

428474
### Action Mailer
429475

0 commit comments

Comments
 (0)