TransactionGuard is a Rails / ActiveRecord Ruby gem that detects external side effects inside database transactions — including HTTP requests, email delivery, and background job enqueueing.
Database transactions can roll back database changes, but they cannot automatically roll back those external operations. TransactionGuard helps catch these mismatches during development and testing.
For example:
User.transaction do
user = User.create!(name: "Yashika")
SomeExternalApi.create_user(user)
endIf the transaction later rolls back, the external API request cannot automatically be rolled back with it.
Add the gem to your application's Gemfile:
gem "transaction_guard"Then run:
bundle installFor local development, you can use the gem directly from a local path:
gem "transaction_guard", path: "../transaction_guard"TransactionGuard supports three modes:
:warn— report external side effects:raise— raise an error when a side effect is detected:off— disable detection
The default mode is :warn.
In Rails applications, the included Railtie sets:
:warnin development and test:offin production
Override this in an initializer:
# config/initializers/transaction_guard.rb
TransactionGuard.configure do |config|
config.mode = :warn
endThis is the default:
TransactionGuard.configure do |config|
config.mode = :warn
endWhen an external side effect is detected inside a transaction, TransactionGuard reports a warning including the operation and caller location.
Use :raise when you want to prevent the transaction from continuing after an external side effect is detected:
TransactionGuard.configure do |config|
config.mode = :raise
endAn invalid configuration value raises an ArgumentError:
TransactionGuard.configure do |config|
config.mode = :invalid
endDetection can be disabled:
TransactionGuard.configure do |config|
config.mode = :off
endTransactionGuard detects HTTP requests made through Net::HTTP while an ActiveRecord transaction is open.
Example:
User.transaction do
Net::HTTP.get(URI("https://example.com"))
endTransactionGuard reports the external HTTP operation.
The detector covers common Net::HTTP methods including:
get
post
put
patch
delete
head
optionsClients that build on Net::HTTP (for example some Faraday adapters) may also be detected. Direct Faraday, HTTParty, httpx, and similar clients are not hooked in 0.1.0.
TransactionGuard detects email delivery performed inside an ActiveRecord transaction.
For example:
User.transaction do
user = User.create!
TestMailer.welcome(user).deliver_now
endIt also detects:
User.transaction do
TestMailer.welcome(user).deliver_later
enddeliver_later is reported as an email side effect rather than generating an additional warning for the internal ActiveJob enqueue.
TransactionGuard detects ActiveJob operations performed inside transactions.
User.transaction do
user = User.create!
WelcomeJob.perform_later(user.id)
endThis is reported as a job enqueue operation.
User.transaction do
WelcomeJob.perform_now
endThis is reported as job execution.
Sidekiq, Resque, and other non-ActiveJob APIs are not detected in 0.1.0.
Consider:
User.transaction do
user = User.create!
WelcomeJob.perform_later(user.id)
raise ActiveRecord::Rollback
endThe database record is rolled back, but the background job may already have been enqueued.
The job could therefore execute with an ID that no longer exists.
Similar problems can occur with HTTP requests and email delivery.
When an external side effect depends on a successful database transaction, move the operation until after the transaction commits.
For example:
user = User.create!
User.transaction do
user.update!(status: "active")
end
WelcomeJob.perform_later(user.id)You can also use after_commit (or enqueue ActiveJob only after a successful commit). That fixes the ordering problem: the side effect no longer runs if the transaction rolls back.
after_commit alone is not fully crash-safe. If the process dies after the commit succeeds but before the callback runs, the email or job may never go out, with no automatic retry. When delivery must be guaranteed, prefer a transactional outbox (record the intent in the same DB transaction) plus a worker or reconciliation job that sends from the outbox, or another reliable event-publishing approach.
Patterns to consider:
- Run the side effect after the transaction (as in the example above)
after_commit— good for ordering; not durable across process failure by itself- ActiveJob triggered after a successful commit
- Transactional outbox + worker / reconciliation
- Reliable event publishing
TransactionGuard does not automatically move, delay, retry, or otherwise modify external operations. It reports the potentially unsafe operation so the application can decide how to handle it.
Clone the repository and install dependencies:
git clone https://github.com/yashika279/transaction_guard.git
cd transaction_guard
bundle installRun the test suite:
bundle exec rspecRun RuboCop:
bundle exec rubocopBuild the gem locally:
bundle exec gem build transaction_guard.gemspecBug reports, feature ideas, feedback, and pull requests are welcome.
- Open an issue for bugs, features, or feedback.
masteris protected — fork the repo, push your branch, and open a PR againstmaster.
See CONTRIBUTING.md for setup, the fork workflow, and the PR checklist. Please make sure tests and RuboCop pass before submitting a pull request.
See SECURITY.md for how to report vulnerabilities.
TransactionGuard is available as open source under the MIT License.