Isolator: Catching HTTP Calls and Job Enqueues Inside Database Transactions
Detect non-atomic interactions within DB transactions
At a glance
- What is it?
- Isolator is a Ruby gem that hooks into ActiveRecord transactions and raises an error when an HTTP request, mailer, or background job is fired before the transaction commits. It is a test and staging tool, not a production guard, and its usefulness depends on adapter configuration and how your app loads gems.
- Who is it for?
- Adopt Isolator if your test suite runs ActiveRecord transactions and you want a hard failure the moment a job or HTTP call escapes before commit. Do not adopt it if you rely on Que, GoodJob, or any adapter whose delivery is already deferred past commit, because the ActiveJob spy cannot tell the difference and will flag safe code.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Ruby, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The Problem Isolator Solves: Side Effects That Outlive a Rollback
A database transaction is a promise that either everything inside it commits or nothing does. Code that performs an HTTP request, sends an email, or enqueues a background job inside that block breaks the promise. If the transaction rolls back, the payment was still charged and the job still runs. The database has no record of either. The README opens with exactly this pattern: a User.transaction block that saves a user and then calls PaymentsService.charge!(user), which raises Isolator::HTTPError. The second example is subtler. A model with after_create :notify_author that calls CommentMailer.comment_created(self).deliver_later will raise Isolator::BackgroundJobError on Comment.create, because after_create fires inside the implicit transaction that wraps the insert. The fix the README points to is after_create_commit, or no callback at all. Isolator is aimed at Rails and ROM::SQL developers who already suspect this class of bug and want a test to prove it. It is not a runtime safety net. The README states plainly that Isolator is supposed to be used in tests and on staging.
How the Detection Works: Adapters, Spies, and the Danger Zone
Isolator watches two things at once. On the database side it tracks when a transaction opens and closes, entering what the README calls the danger zone. On the application side it wraps known side-effect entry points with adapter classes. The built-in list covers :http (built on top of Sniffer), :active_job, :sidekiq, :resque, :resque_scheduler, :sucker_punch, :mailer, :webmock, and :action_cable. When one of those adapters fires while a transaction is open, Isolator records an offense. The gem tries to detect the environment and loads only the adapters that apply, which is why load order matters. In a Rails app, adapters load after application initialization, so a Gemfile entry that requires isolator too early will silently miss things. The README also notes that Isolator does not distinguish framework-level adapters: the :active_job spy does not know which ActiveJob backend you use. That single design decision produces most of the false positives users hit. Two callbacks bracket the tracked region, before_isolate and after_isolate, and on_transaction_begin and on_transaction_end fire per transaction with event[:connection_id] and event[:depth]. Those hooks exist so you can attach your own logging or extension logic rather than fork the gem.
Getting It Running in a Rails Project
The README puts the gem in the development and test group: group :development, :test do gem "isolator" end. For staging, the recommended form is gem "isolator", require: false, followed by a manual require so you control the load order. In test environments, raise_exceptions defaults to true, so an offense becomes a failing test rather than a log line. The configuration block exposes logger, raise_exceptions, send_notifications, backtrace_filter, ignorer, disallow_per_thread_concurrent_transactions, and max_subtransactions_depth. The default backtrace_filter is a lambda that takes the top five lines, which is tunable if five frames are not enough to find the call site. Notifications go through uniform_notifier, which the README stresses must be added to the Gemfile separately. Adapters can be toggled at runtime: Isolator.adapters.http.disable! turns off HTTP spying, and Isolator.adapters.http.enable! turns it back on. The active_job adapter needs require "active_job/base" first, and if your queue backend is safe you disable it with Isolator.adapters.active_job.disable!. The README also flags an instrumentation conflict: if an APM is instrumenting net/http, you may need to force Sniffer into prepend mode, linking to issue 44 for the workaround.
Where Isolator Gets It Wrong: False Positives and Load Order
The ActiveJob blind spot is the biggest one. If you run Que, whose delivery is deferred until after commit, the :active_job adapter still flags the enqueue as an offense. The README says this directly: it does not take into account which AJ adapter you use, so you must disable the adapter to avoid false negatives. That is a trade-off, not a bug, but it means the adapter list is not a set-and-forget default. The second failure mode is ordering. Because Isolator detects the environment and includes only necessary adapters, requiring it before database_cleaner or before the ORM finishes loading can leave adapters unregistered and offenses undetected. Transactional tests are handled: the README lists Rails' use_transactional_tests and database_cleaner as supported, with the explicit instruction to require isolator after database_cleaner. Third, the README acknowledges false positives from other libraries patching the same behavior, which is what the ignorer and the .isolator_todo.yml file are for. The default ignorer is row-number based, so editing the file above an ignored line shifts the ignore to the wrong offense. Multiple database support is described as experimental since v0.7.0, with the README asking users to report issues, which is an admission that the coverage is not complete.
Compared with After-Commit Callbacks and Transactional Test Gems
The nearest alternative is not another linter but the after_commit callback itself, plus the after_commit_everywhere gem that Isolator's README recommends for code that must run only after a successful commit. The difference is direction. after_commit_everywhere changes the code so the side effect happens at the right time. Isolator leaves the code alone and fails the test when the side effect happens at the wrong time. They are complementary: Isolator finds the offense, after_commit_everywhere is one way to fix it. A second comparison is database_cleaner, which manages transaction state across tests but does not inspect what happens inside the transaction. Isolator depends on that transaction tracking rather than replacing it, which is why the require order between the two matters. A third point of contrast is a static analysis tool like RuboCop. RuboCop can flag a .deliver_later call by pattern, but it cannot know whether that call sits inside an open transaction at runtime. Isolator answers that question with actual state, at the cost of running inside your test suite and carrying the adapter caveats above.
Maintenance, Licence, and What the Release History Shows
The repository is MIT licensed, so the practical constraint is attribution and the usual warranty disclaimer, not copyleft obligations. Anyone embedding Isolator in a commercial Rails app should read the licence text rather than this summary, since this is not legal advice. The release cadence is slow and deliberate. v1.0.0 landed in January 2024, v1.1.0 in August 2024, and the last push to the default branch is dated September 2026, so the project is still receiving commits even though no release has followed v1.1.0 in the supplied material. The v1.0.0 line dropped support for older ActiveRecord; the README states ActiveRecord >= 6.0 is required, with ROM::SQL supported only when the Active Support instrumentation extension is loaded. That version floor is the main upgrade cost. If you are on Rails 5.x, you need an older Isolator release, and the README points you there rather than offering a compatibility shim. The gem has few moving parts: one configuration block, a set of adapters, and an optional todo file. There is no daemon, migration, or schema change to maintain, which keeps the ongoing cost close to zero once the adapters are tuned.
Who Should Adopt It and What to Check First
The strongest fit is a Rails application with an existing transactional test suite, a payment or email side effect that runs from a model callback, and a team willing to disable adapters that do not apply. The weakest fit is a codebase on Que or another commit-safe backend where the :active_job adapter would flag correct code, or a project without transactional tests, since the gem leans on that machinery. Before adopting, confirm the require order in your test helper: isolator must come after database_cleaner and after the ORM's adapters load. Confirm whether your APM instruments net/http, because that determines whether Sniffer needs prepend mode. Confirm the ActiveRecord version, since the current line requires 6.0 or later. Then run the suite once with raise_exceptions enabled and read the first offense's backtrace with the default five-line filter. If that trace does not point at a real call site, tune backtrace_filter before trusting the rest of the output. Isolator's value is narrow and concrete: it turns a silent ordering bug into a red test.
Editorial conclusion
Adopt Isolator if your test suite runs ActiveRecord transactions and you want a hard failure the moment a job or HTTP call escapes before commit. Do not adopt it if you rely on Que, GoodJob, or any adapter whose delivery is already deferred past commit, because the ActiveJob spy cannot tell the difference and will flag safe code. Before adding the gem, verify three things: that isolator is required after database_cleaner and after all adapters, that the active_job adapter is disabled if your queue backend is safe, and that backtrace_filter gives you a stack trace short enough to read. The payoff is a failing test, not a runtime guard.
Community notes