Open-Source Wikis

/

Ruby on Rails

/

How to contribute

/

Debugging

rails/rails

Debugging

How to find what's going on when something in the Rails source goes wrong.

Logs

Every component has a log subscriber and a structured event subscriber:

  • */lib/<component>/log_subscriber.rb
  • */lib/<component>/structured_event_subscriber.rb

These translate ActiveSupport::Notifications events into log lines. When you're debugging behavior in an application, raising the log level (config.log_level = :debug) and watching the appropriate subscriber's output is usually the fastest signal.

In a dev shell, ActiveSupport::Notifications.subscribe is a quick way to attach an ad-hoc listener:

ActiveSupport::Notifications.subscribe("sql.active_record") do |*args|
  ev = ActiveSupport::Notifications::Event.new(*args)
  puts "#{ev.duration.round(1)}ms - #{ev.payload[:sql]}"
end

When the framework boot fails

Boot order is documented in railties/lib/rails/application.rb (the long comment at the top of the file). The phases are:

  1. before_configuration callbacks
  2. Load config/environments/<env>.rb
  3. before_initialize callbacks
  4. Run all railtie initializers (in dependency order)
  5. Run application initializers
  6. Build middleware stack
  7. to_prepare callbacks
  8. before_eager_load + eager_load!
  9. after_initialize callbacks

A common failure is an initializer that runs before its dependency is available. The before and after options on initializer ... declarations control ordering — see railties/lib/rails/initializable.rb.

To see the order initializers will run, in a Rails app:

bin/rails initializers

(Implemented in railties/lib/rails/commands/initializers/initializers_command.rb.)

Reproducing a bug from an issue

The repo ships executable bug-report templates under guides/bug_report_templates/. The most useful are:

  • active_record_main.rb — boots SQLite + Active Record + a tiny Minitest example. Edit, then run with ruby active_record_main.rb.
  • action_controller_main.rb — boots Action Pack + Action Controller against Rack::Test.
  • generic_main.rb — for issues that don't need a specific framework.
  • executable_template.rb — generates a complete on-disk Rails app via rails new and runs commands against it.

These are how bug reports are expected to come in, and they're the fastest way to bisect a failure to a specific commit.

Bisecting

Because the repo has tags going back to 1.0, git bisect works well:

git bisect start
git bisect bad main
git bisect good v8.0.0
git bisect run bundle exec ruby guides/bug_report_templates/active_record_main.rb

Combine with the bug report template above to get an automated bisect.

Common pitfalls

Test isolation

Some tests modify global state (autoload paths, Rails.application, time). They live in dedicated isolation directories and are run via rake test:isolated. If a test passes alone but fails in a suite, it's often a missing isolation marker.

Reloader and code reloading

Changes in development reload via ActionDispatch::Reloader (actionpack/lib/action_dispatch/middleware/reloader.rb) which delegates to ActiveSupport::Reloader. If you're seeing stale class references in a dev session, check that:

  • The file is in an autoload path (config.autoload_paths).
  • The constant name matches the file path (Zeitwerk is strict).
  • You're not holding a reference across a request.

Connection pools

Active Record connections are per-thread. Forking workers (Puma, Unicorn) need to discard parent connections — ActiveSupport.on_load(:active_record) { ActiveRecord::Base.connection_handler.clear_active_connections! } style hooks live in activerecord/lib/active_record/railtie.rb.

Autoload vs eager_load

In dev, only the constants you reference are loaded. In production with config.eager_load = true, everything is loaded at boot. A bug that only shows up in production may be an eager-load failure — try bin/rails zeitwerk:check (in an app) or set config.eager_load = true locally.

Debugging tools

  • debug gem — added to the default Gemfile for new apps. The framework itself uses binding.irb or byebug calls in tests rarely; prefer puts debugging or attaching ActiveSupport::Notifications subscribers.
  • Rails.error.report(exception)ActiveSupport::ErrorReporter is the framework-blessed place to send exceptions you've handled but want to record.
  • backtrace_cleanerRails.backtrace_cleaner strips noisy framework frames from backtraces. To see the full thing, call caller directly or temporarily disable the cleaner (Rails::BacktraceCleaner is in railties/lib/rails/backtrace_cleaner.rb).

When tests pass locally but fail on CI

  • Adapter difference. Active Record CI runs against all four adapters; check the failing adapter explicitly:
    cd activerecord && bundle exec rake test:postgresql
  • Time zone or locale. CI runs in UTC. Wrap time-sensitive assertions in freeze_time or travel_to.
  • Parallel races. If a test depends on a global, parallelization may interleave it with a sibling. Mark it as not parallelizable.
  • Frozen string literals. New code must include # frozen_string_literal: true. RuboCop will complain locally if it doesn't.

For test infrastructure details see testing.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Debugging – Ruby on Rails wiki | Factory