Repository: sidekiq/sidekiq
Pull request #6669: Conditionally load ActiveJob and Rails integrations
Author: login: seuros
Created: 2025-04-03T18:08:32Z | Merged: 2025-04-11T19:05:47Z

Remove the assumption that Rails will be installed from the meta gem or granular with ActiveJob.

- A script could be using ActiveJob in standalone and won't need Rails initialization
- A Rails application could have Sidekiq and not use the ActiveJob wrapper to reduce dependencies

This change wraps relevant code in `if defined?` checks to ensure Sidekiq only loads what's needed based on the actual environment dependencies.

Fixes #6668 

Linked issue #6668: Confused about `uninitialized constant Sidekiq::ActiveJob` in 7.3.9
Ruby version: 3.4.1
Rails version: 8.0.2
Sidekiq / Pro / Enterprise version(s): 7.3.9

Like seemingly many people (https://github.com/sidekiq/sidekiq/issues/6618, https://github.com/sidekiq/sidekiq/issues/6612, https://github.com/sidekiq/sidekiq/issues/6642, https://github.com/sidekiq/sidekiq/issues/6622) I'm having trouble with upgrading to version 7.3.9.

Reading through those issue it kinda sounds like in a Rails app that has followed the normal installation and setup instructions that things should Just Work, but that doesn't seem to be the case for me.

My app seems to already be doing all of the suggested fixes in those threads. Specifically:

* `gem 'rails'` is listed before `gem 'sidekiq'` in the `Gemfile`
* `require 'rails/all'` is before `Bundler.require(*Rails.groups)` in `config/application.rb`
* `require 'sidekiq'` is in `config/initializers/sidekiq.rb`
* Adding `require 'sidekiq/rails'` to the initializer doesn't make a difference

The two symptoms of the problem that I'm seeing are:

1. `puma` won't start.

```
$ bundle exec puma -C config/puma.rb
[24724] Puma starting in cluster mode...
[24724] * Puma version: 6.6.0 ("Return to Forever")
[24724] * Ruby version: ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [arm64-darwin23]
[24724] *  Min threads: 3
[24724] *  Max threads: 3
[24724] *  Environment: development
[24724] *   Master PID: 24724
[24724] *      Workers: 2
[24724] *     Restarts: (✔) hot (✖) phased
[24724] * Preloading application
[24724] ! Unable to load application: NameError: uninitialized constant Sidekiq::ActiveJob
bundler: failed to load command: puma (/Users/jgreen/.asdf/installs/ruby/3.4.1/bin/puma)
/Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/sidekiq-7.3.9/lib/active_job/queue_adapters/sidekiq_adapter.rb:71:in '<class:SidekiqAdapter>': uninitialized constant Sidekiq::ActiveJob (NameError)

      class JobWrapper < Sidekiq::ActiveJob::Wrapper
                                ^^^^^^^^^^^
Did you mean?  ActiveModel
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/sidekiq-7.3.9/lib/active_job/queue_adapters/sidekiq_adapter.rb:13:in '<module:QueueAdapters>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/sidekiq-7.3.9/lib/active_job/queue_adapters/sidekiq_adapter.rb:4:in '<module:ActiveJob>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/sidekiq-7.3.9/lib/active_job/queue_adapters/sidekiq_adapter.rb:3:in '<top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundled_gems.rb:82:in 'Kernel.require'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundled_gems.rb:82:in 'block (2 levels) in Kernel#replace_require'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/zeitwerk-2.7.2/lib/zeitwerk/core_ext/kernel.rb:34:in 'Kernel#require'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/queue_adapters.rb:136:in 'Module#const_get'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/queue_adapters.rb:136:in 'ActiveJob::QueueAdapters.lookup'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/queue_adapter.rb:52:in 'ActiveJob::QueueAdapter::ClassMethods#queue_adapter='
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/railtie.rb:81:in 'block (3 levels) in <class:Railtie>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/railtie.rb:76:in 'Hash#each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/railtie.rb:76:in 'block (2 levels) in <class:Railtie>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:97:in 'Module#class_eval'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:97:in 'block in ActiveSupport::LazyLoadHooks#execute_hook'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:87:in 'ActiveSupport::LazyLoadHooks#with_execution_control'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:92:in 'ActiveSupport::LazyLoadHooks#execute_hook'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:62:in 'block in ActiveSupport::LazyLoadHooks#on_load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:61:in 'Array#each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activesupport-8.0.2/lib/active_support/lazy_load_hooks.rb:61:in 'ActiveSupport::LazyLoadHooks#on_load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/activejob-8.0.2/lib/active_job/railtie.rb:68:in 'block in <class:Railtie>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/railties-8.0.2/lib/rails/initializable.rb:32:in 'BasicObject#instance_exec'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/railties-8.0.2/lib/rails/initializable.rb:32:in 'Rails::Initializable::Initializer#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/railties-8.0.2/lib/rails/initializable.rb:61:in 'block in Rails::Initializable#run_initializers'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:231:in 'block in TSort.tsort_each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:353:in 'block (2 levels) in TSort.each_strongly_connected_component'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:434:in 'TSort.each_strongly_connected_component_from'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:352:in 'block in TSort.each_strongly_connected_component'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:350:in 'Rails::Initializable::Collection#each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:350:in 'Method#call'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:350:in 'TSort.each_strongly_connected_component'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:229:in 'TSort.tsort_each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/tsort.rb:208:in 'TSort#tsort_each'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/railties-8.0.2/lib/rails/initializable.rb:60:in 'Rails::Initializable#run_initializers'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/railties-8.0.2/lib/rails/application.rb:440:in 'Rails::Application#initialize!'
        from /Users/jgreen/projects/shopify-crowdfunder/config/environment.rb:5:in '<top (required)>'
        from config.ru:3:in 'Kernel#require_relative'
        from config.ru:3:in 'block (2 levels) in <top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/rack-3.1.12/lib/rack/builder.rb:108:in 'Kernel#eval'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/rack-3.1.12/lib/rack/builder.rb:108:in 'Rack::Builder.new_from_string'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/rack-3.1.12/lib/rack/builder.rb:97:in 'Rack::Builder.load_file'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/rack-3.1.12/lib/rack/builder.rb:67:in 'Rack::Builder.parse_file'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/configuration.rb:386:in 'Puma::Configuration#load_rackup'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/configuration.rb:297:in 'Puma::Configuration#app'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/runner.rb:169:in 'Puma::Runner#load_and_bind'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/cluster.rb:373:in 'Puma::Cluster#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/launcher.rb:203:in 'Puma::Launcher#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/lib/puma/cli.rb:75:in 'Puma::CLI#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/puma-6.6.0/bin/puma:10:in '<top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/bin/puma:25:in 'Kernel#load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/bin/puma:25:in '<top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli/exec.rb:59:in 'Kernel.load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli/exec.rb:59:in 'Bundler::CLI::Exec#kernel_load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli/exec.rb:23:in 'Bundler::CLI::Exec#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli.rb:452:in 'Bundler::CLI#exec'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/vendor/thor/lib/thor/command.rb:28:in 'Bundler::Thor::Command#run'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/vendor/thor/lib/thor/invocation.rb:127:in 'Bundler::Thor::Invocation#invoke_command'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/vendor/thor/lib/thor.rb:538:in 'Bundler::Thor.dispatch'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli.rb:35:in 'Bundler::CLI.dispatch'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/vendor/thor/lib/thor/base.rb:584:in 'Bundler::Thor::Base::ClassMethods#start'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/cli.rb:29:in 'Bundler::CLI.start'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/bundler-2.6.2/exe/bundle:28:in 'block in <top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/3.4.0/bundler/friendly_errors.rb:117:in 'Bundler.with_friendly_errors'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/lib/ruby/gems/3.4.0/gems/bundler-2.6.2/exe/bundle:20:in '<top (required)>'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/bin/bundle:25:in 'Kernel#load'
        from /Users/jgreen/.asdf/installs/ruby/3.4.1/bin/bundle:25:in '<main>'
```

2. `rspec` can't get past the loading phase:

```
$ bundle exec rspec spec/controllers/css_controller_spec.rb

An error occurred while loading ./spec/controllers/css_controller_spec.rb.
Failure/Error: require File.expand_path('../../config/environment', __FILE__)

NameError:
  uninitialized constant Sidekiq::ActiveJob
# ./config/environment.rb:5:in '<top (required)>'
# ./spec/rails_helper.rb:3:in '<top (required)>'
# ./spec/controllers/css_controller_spec.rb:1:in '<top (required)>'
No examples found.


Finished in 0.00003 seconds (files took 1.27 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
```

Have I overlooked something in one of those threads?
Linked issue comments: user: login: seuros
created_at: 2025-04-03T17:06:34Z
body: You have activejob in the Gemfile.lock ?
> grep activejob Gemfile.lock
author_association: COLLABORATOR
user: login: mperham
created_at: 2025-04-03T17:06:36Z
body: I've been struggling to understand the root cause of this issue.

0. Sidekiq does not depend on Rails so we need to conditionally load our Rails support.
1. Rails tries very hard to lazy load subsystems. Active Job is not eagerly loaded so we can't detect it ahead of time.
2. When you call `require "sidekiq"`, the code has to determine if we are running within a Rails app. It does this by looking for `::Rails::Engine`. If that constant exists, we eager load our Rails support.

https://github.com/sidekiq/sidekiq/blob/74552ef43e59c8a9764a1317e17e7036caa3cd5c/lib/sidekiq.rb#L149

So it sounds like Sidekiq is being required before Rails has loaded its Engine class. Perhaps the root issue is that I'm detecting Rails incorrectly. What's a more reliable way to detect Rails? I don't check for `::Rails` because other people have complained that they are using apps which create a ::Rails constant to appease some usage (like using `Rails.env.production?` in a Sinatra app). Maybe `::Rails::Application`?
author_association: COLLABORATOR
user: login: seuros
created_at: 2025-04-03T17:07:28Z
body: @mperham  i will handle this .
author_association: COLLABORATOR
user: login: jagthedrummer
created_at: 2025-04-03T17:09:21Z
body: Further comparing my config to the docs I figured out the puma problem. I'm using embedded sidekiq and I had a `require "sidekiq"` line above the `preload_app!` line for some reason. Removing that line allows puma to start.

> You have activejob in the Gemfile.lock ?

Yes, `activejob` is listed at several places in Gemfile.lock.
author_association: NONE
user: login: mperham
created_at: 2025-04-03T17:13:21Z
body: @seuros All yours. I'll lurk and listen.
author_association: COLLABORATOR
user: login: jagthedrummer
created_at: 2025-04-03T17:17:08Z
body: For `rspec` I've found that I can work around the problem if I manually `require 'sidekiq/rails'` at the same place that I `require 'sidekiq/testing'` in `spec/spec_helper.rb`. But it sounds like ideally that shouldn't be needed.

```ruby
# spec/spec_helper.rb

require 'sidekiq/rails'
require 'sidekiq/testing'
Sidekiq::Testing.fake!

RSpec.configure do |config|
  #...
end
```
author_association: NONE
user: login: seuros
created_at: 2025-04-03T18:15:37Z
body: You don't load the full app in your spec_helper ?
author_association: COLLABORATOR
user: login: jagthedrummer
created_at: 2025-04-03T19:55:54Z
body: @seuros the recommendations for `rspec` seem to be that you load the full app in `rails_helper.rb` but not in `spec_helper.rb` which is loaded by `rails_helper.rb`. Maybe that's a contributing factor if this is primarily an `rspec` issue.
author_association: NONE
user: login: jagthedrummer
created_at: 2025-04-03T19:59:21Z
body: Actually now that I'm reading those files more closely I'm not sure that's it at all. None of my tests `require 'spec_helper'` they all `require 'rails_helper'` and the top of `rails_helper.rb` looks like this. So the app is loaded before requiring anything related to Sidekiq.

```ruby

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'

ActiveRecord::Migration.maintain_test_schema!

Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

RSpec.configure do |config|
  #...
end
```
author_association: NONE
user: login: jagthedrummer
created_at: 2025-04-03T20:48:07Z
body: Well, it turns out that if I move `require 'sidekiq/testing'` out of `spec_helper` and into `rails_helper` that it works without needing to also `require 'sidekiq/rails'`. I have no idea why that would be. So, this configuration seems to be working fine:

```ruby
# spec/rails_helper.rb

# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
# Prevent database truncation if the environment is production
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'

require 'sidekiq/testing'
Sidekiq::Testing.fake!

ActiveRecord::Migration.maintain_test_schema!

Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }

RSpec.configure do |config|
  #...
end
```

```ruby
# spec/spec_helper.rb

RSpec.configure do |config|
  #...
end
```

Maybe the "fix" is to have the testing docs explicitly say where you should `require 'sidekiq/testing'` for rspec? Though it does seem that something important has changed about loading since it was working in `spec_helper` in version 7.3.8.

If there's any additional info I can provide or tests I can run to pinpoint the problem I'd be happy to help.
author_association: NONE
user: login: seuros
created_at: 2025-04-03T21:12:01Z
body: Can you try the PR I submitted please? 
author_association: COLLABORATOR
user: login: jagthedrummer
created_at: 2025-04-03T21:46:54Z
body: Oh, I had missed that PR!

OK, I did this in my existing `sidekiq` repo:

```
git remote add seuros git@github.com:seuros/sidekiq.git
git fetch seuros
git checkout -b seuros/frameworks --track seuros/frameworks
```

Then in my `Gemfile` I pointed to it:

```
gem 'sidekiq', path: '../sidekiq'
```

Then running `bundle install` upgraded `sidekiq` from 7.3.9 to 8.0.2 and **downgraded** a related gem:

```
$ bundle
Fetching gem metadata from https://gem.fury.io/jagthedrummer/..
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Using sidekiq 8.0.2 (was 7.3.9) from source at `../sidekiq` and installing its executables
Fetching sidekiq-unique-jobs 5.0.2 (was 8.0.10)
Installing sidekiq-unique-jobs 5.0.2 (was 8.0.10)
Bundle complete! 45 Gemfile dependencies, 183 gems now installed.
```

Then I moved `require 'sidekiq/testing'` out of `rails_helper` and back into `spec_helper`.

When I first tried to run a test it complained about something related to the downgraded gem. So I disabled everything having to do with that gem.

Then when I tried to run a test it complained about `uninitialized constant Sidekiq::Web`

```
$ bundle exec rspec spec/controllers/css_controller_spec.rb
F

Failures:

  1) CssController GET #presale returns http success
     Failure/Error:
       expect{
         get :presale
       }.to raise_error("This is an old route that shouldn't be used anymore. Investigate this!")

       expected Exception with "This is an old route that shouldn't be used anymore. Investigate this!", got #<NameError: uninitialized constant Sidekiq::Web> with backtrace:
         # ./config/routes.rb:52:in 'block in <top (required)>'
         # ./config/routes.rb:1:in '<top (required)>'
         # ./spec/controllers/css_controller_spec.rb:8:in 'block (4 levels) in <top (required)>'
         # ./spec/controllers/css_controller_spec.rb:9:in 'block (3 levels) in <top (required)>'
     # ./spec/controllers/css_controller_spec.rb:9:in 'block (3 levels) in <top (required)>'

Finished in 0.09026 seconds (files took 1.71 seconds to load)
1 example, 1 failure

Failed examples:

rspec ./spec/controllers/css_controller_spec.rb:6 # CssController GET #presale returns http success
```

If I comment out all of the `Sidekiq::Web` stuff from `config/routes.rb` then it works.

```
$ bundle exec rspec spec/controllers/css_controller_spec.rb
.

Finished in 0.07818 seconds (files took 1.73 seconds to load)
1 example, 0 failures
```

I'm not sure if it fixed one problem and caused another, or if the problems related to `Sidekiq::Web` are just a side effect of it upgrading me to Sidekiq 8 from 7.

Is there anything else that I can do to dig further?
author_association: NONE
user: login: seuros
created_at: 2025-04-03T22:02:44Z
body: Perfect, that confirm that the ActiveJob configuration will not trigger .

As for the routes. I think it good practice to require the sidekiq/web before mounting the engine 

You need to add that in ./config/routes.rb:51

Now , i will fix the tests in the PR.

author_association: COLLABORATOR
user: login: Antronin
created_at: 2025-04-07T15:43:27Z
body: 
I ran into the same issue today.
When I ran ```bundle exec puma -C config/puma.rb``` the error message popped up and rails did not start. However, simply running ```bundle exec rails s``` perfectly started the application. The problem is that my systemd script relies on ```puma``` binstub.

I mitigated the problem by modifying ```config/application.rb```, adding ```require 'sidekiq/rails'``` after ```require 'rails/all'```.
So my ```config/application.rb``` looks like this:

```
# frozen_string_literal: true

require_relative 'boot'

require 'rails/all'
require 'sidekiq/rails' # pacth for sidekiq issue https://github.com/sidekiq/sidekiq/issues/6668

# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
Bundler.require(*Rails.groups)

module MyApp
...
```
author_association: NONE

Diff:
diff --git a/lib/active_job/queue_adapters/sidekiq_adapter.rb b/lib/active_job/queue_adapters/sidekiq_adapter.rb
index 2b558a7a3f..2a643bee9e 100644
--- a/lib/active_job/queue_adapters/sidekiq_adapter.rb
+++ b/lib/active_job/queue_adapters/sidekiq_adapter.rb
@@ -1,75 +1,111 @@
 # frozen_string_literal: true
 
-module ActiveJob
-  module QueueAdapters
-    # Explicitly remove the implementation existing in older Rails.
-    remove_const(:SidekiqAdapter) if const_defined?(:SidekiqAdapter)
+begin
+  gem "activejob", ">= 7.0"
+  require "active_job"
 
-    # Sidekiq adapter for Active Job
-    #
-    # To use Sidekiq set the queue_adapter config to +:sidekiq+.
-    #
-    #   Rails.application.config.active_job.queue_adapter = :sidekiq
-    class SidekiqAdapter
-      # Defines whether enqueuing should happen implicitly to after commit when called
-      # from inside a transaction.
+  module Sidekiq
+    module ActiveJob
       # @api private
-      def enqueue_after_transaction_commit?
-        true
-      end
+      class Wrapper
+        include Sidekiq::Job
 
-      # @api private
-      def enqueue(job)
-        job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
-          wrapped: job.class,
-          queue: job.queue_name
-        ).perform_async(job.serialize)
+        def perform(job_data)
+          ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
+        end
       end
+    end
+  end
 
-      # @api private
-      def enqueue_at(job, timestamp)
-        job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
-          wrapped: job.class,
-          queue: job.queue_name
-        ).perform_at(timestamp, job.serialize)
-      end
+  unless ActiveJob::Base.respond_to?(:sidekiq_options)
+    # By including the Options module, we allow AJs to directly control sidekiq features
+    # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
+    # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
+    # manually retried, don't automatically die, etc.
+    #
+    #   class SomeJob < ActiveJob::Base
+    #     queue_as :default
+    #     sidekiq_options retry: 3, backtrace: 10
+    #     def perform
+    #     end
+    #   end
+    ActiveJob::Base.include Sidekiq::Job::Options
+  end
 
-      # @api private
-      def enqueue_all(jobs)
-        enqueued_count = 0
-        jobs.group_by(&:class).each do |job_class, same_class_jobs|
-          same_class_jobs.group_by(&:queue_name).each do |queue, same_class_and_queue_jobs|
-            immediate_jobs, scheduled_jobs = same_class_and_queue_jobs.partition { |job| job.scheduled_at.nil? }
+  # Patch the ActiveJob module
+  module ActiveJob
+    module QueueAdapters
+      # Explicitly remove the implementation existing in older Rails.
+      remove_const(:SidekiqAdapter) if const_defined?(:SidekiqAdapter)
 
-            if immediate_jobs.any?
-              jids = Sidekiq::Client.push_bulk(
-                "class" => Sidekiq::ActiveJob::Wrapper,
-                "wrapped" => job_class,
-                "queue" => queue,
-                "args" => immediate_jobs.map { |job| [job.serialize] }
-              )
-              enqueued_count += jids.compact.size
-            end
+      # Sidekiq adapter for Active Job
+      #
+      # To use Sidekiq set the queue_adapter config to +:sidekiq+.
+      #
+      #   Rails.application.config.active_job.queue_adapter = :sidekiq
+      class SidekiqAdapter
+        # Defines whether enqueuing should happen implicitly to after commit when called
+        # from inside a transaction.
+        # @api private
+        def enqueue_after_transaction_commit?
+          true
+        end
+
+        # @api private
+        def enqueue(job)
+          job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
+            wrapped: job.class,
+            queue: job.queue_name
+          ).perform_async(job.serialize)
+        end
+
+        # @api private
+        def enqueue_at(job, timestamp)
+          job.provider_job_id = Sidekiq::ActiveJob::Wrapper.set(
+            wrapped: job.class,
+            queue: job.queue_name
+          ).perform_at(timestamp, job.serialize)
+        end
 
-            if scheduled_jobs.any?
-              jids = Sidekiq::Client.push_bulk(
-                "class" => Sidekiq::ActiveJob::Wrapper,
-                "wrapped" => job_class,
-                "queue" => queue,
-                "args" => scheduled_jobs.map { |job| [job.serialize] },
-                "at" => scheduled_jobs.map { |job| job.scheduled_at&.to_f }
-              )
-              enqueued_count += jids.compact.size
+        # @api private
+        def enqueue_all(jobs)
+          enqueued_count = 0
+          jobs.group_by(&:class).each do |job_class, same_class_jobs|
+            same_class_jobs.group_by(&:queue_name).each do |queue, same_class_and_queue_jobs|
+              immediate_jobs, scheduled_jobs = same_class_and_queue_jobs.partition { |job| job.scheduled_at.nil? }
+
+              if immediate_jobs.any?
+                jids = Sidekiq::Client.push_bulk(
+                  "class" => Sidekiq::ActiveJob::Wrapper,
+                  "wrapped" => job_class,
+                  "queue" => queue,
+                  "args" => immediate_jobs.map { |job| [job.serialize] }
+                )
+                enqueued_count += jids.compact.size
+              end
+
+              if scheduled_jobs.any?
+                jids = Sidekiq::Client.push_bulk(
+                  "class" => Sidekiq::ActiveJob::Wrapper,
+                  "wrapped" => job_class,
+                  "queue" => queue,
+                  "args" => scheduled_jobs.map { |job| [job.serialize] },
+                  "at" => scheduled_jobs.map { |job| job.scheduled_at&.to_f }
+                )
+                enqueued_count += jids.compact.size
+              end
             end
           end
+          enqueued_count
         end
-        enqueued_count
-      end
 
-      # Defines a class alias for backwards compatibility with enqueued Active Job jobs.
-      # @api private
-      class JobWrapper < Sidekiq::ActiveJob::Wrapper
+        # Defines a class alias for backwards compatibility with enqueued Active Job jobs.
+        # @api private
+        class JobWrapper < Sidekiq::ActiveJob::Wrapper
+        end
       end
     end
   end
+rescue Gem::LoadError
+  # ActiveJob not available or version requirement not met
 end
diff --git a/lib/sidekiq.rb b/lib/sidekiq.rb
index f2f7e58e44..c4d06d0998 100644
--- a/lib/sidekiq.rb
+++ b/lib/sidekiq.rb
@@ -146,4 +146,4 @@ def self.configure_client
   class Shutdown < Interrupt; end
 end
 
-require "sidekiq/rails" if defined?(::Rails::Engine)
+require "sidekiq/rails"
diff --git a/lib/sidekiq/rails.rb b/lib/sidekiq/rails.rb
index 6b0d99f1c1..b02a1a4b78 100644
--- a/lib/sidekiq/rails.rb
+++ b/lib/sidekiq/rails.rb
@@ -1,84 +1,62 @@
 # frozen_string_literal: true
 
 require "sidekiq/job"
-require "rails"
+require_relative "../active_job/queue_adapters/sidekiq_adapter"
 
 module Sidekiq
-  module ActiveJob
-    # @api private
-    class Wrapper
-      include Sidekiq::Job
-
-      def perform(job_data)
-        ::ActiveJob::Base.execute(job_data.merge("provider_job_id" => jid))
-      end
-    end
-  end
-
-  class Rails < ::Rails::Engine
-    class Reloader
-      def initialize(app = ::Rails.application)
-        @app = app
-      end
-
-      def call
-        params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
-        @app.reloader.wrap(**params) do
-          yield
+  begin
+    gem "railties", ">= 7.0"
+    require "rails"
+
+    class Rails < ::Rails::Engine
+      class Reloader
+        def initialize(app = ::Rails.application)
+          @app = app
         end
-      end
 
-      def inspect
-        "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
-      end
+        def call
+          params = (::Rails::VERSION::STRING >= "7.1") ? {source: "job.sidekiq"} : {}
+          @app.reloader.wrap(**params) do
+            yield
+          end
+        end
 
-      def to_hash
-        {app: @app.class.name}
-      end
-    end
+        def inspect
+          "#<Sidekiq::Rails::Reloader @app=#{@app.class.name}>"
+        end
 
-    # By including the Options module, we allow AJs to directly control sidekiq features
-    # via the *sidekiq_options* class method and, for instance, not use AJ's retry system.
-    # AJ retries don't show up in the Sidekiq UI Retries tab, don't save any error data, can't be
-    # manually retried, don't automatically die, etc.
-    #
-    #   class SomeJob < ActiveJob::Base
-    #     queue_as :default
-    #     sidekiq_options retry: 3, backtrace: 10
-    #     def perform
-    #     end
-    #   end
-    initializer "sidekiq.active_job_integration" do
-      ActiveSupport.on_load(:active_job) do
-        require_relative "../active_job/queue_adapters/sidekiq_adapter"
-        include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
+        def to_hash
+          {app: @app.class.name}
+        end
       end
-    end
 
-    initializer "sidekiq.backtrace_cleaner" do
-      Sidekiq.configure_server do |config|
-        config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
+      initializer "sidekiq.backtrace_cleaner" do
+        Sidekiq.configure_server do |config|
+          config[:backtrace_cleaner] = ->(backtrace) { ::Rails.backtrace_cleaner.clean(backtrace) }
+        end
       end
-    end
-
-    # This hook happens after all initializers are run, just before returning
-    # from config/environment.rb back to sidekiq/cli.rb.
-    #
-    # None of this matters on the client-side, only within the Sidekiq process itself.
-    config.after_initialize do
-      Sidekiq.configure_server do |config|
-        config[:reloader] = Sidekiq::Rails::Reloader.new
 
-        # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
-        # it will appear in the Sidekiq console with all of the job context.
-        unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
-          if ::Rails.logger.respond_to?(:broadcast_to)
-            ::Rails.logger.broadcast_to(config.logger)
-          else
-            ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
+      # This hook happens after all initializers are run, just before returning
+      # from config/environment.rb back to sidekiq/cli.rb.
+      #
+      # None of this matters on the client-side, only within the Sidekiq process itself.
+      config.after_initialize do
+        Sidekiq.configure_server do |config|
+          config[:reloader] = Sidekiq::Rails::Reloader.new
+
+          # This is the integration code necessary so that if a job uses `Rails.logger.info "Hello"`,
+          # it will appear in the Sidekiq console with all of the job context.
+          unless ::Rails.logger == config.logger || ::ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, $stdout)
+            if ::Rails.logger.respond_to?(:broadcast_to)
+              ::Rails.logger.broadcast_to(config.logger)
+            else
+              ::Rails.logger.extend(::ActiveSupport::Logger.broadcast(config.logger))
+            end
           end
         end
       end
     end
+  rescue Gem::LoadError
+    # Rails not available or version requirement not met
   end
 end


Reviews:
user: login: seuros
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-03T18:13:17Z
user: login: mperham
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-04T16:08:47Z

Review comments:
diff_hunk: @@ -146,4 +146,4 @@ def self.configure_client
   class Shutdown < Interrupt; end
 end
 
-require "sidekiq/rails" if defined?(::Rails::Engine)
+require "sidekiq/rails"
path: lib/sidekiq.rb
user: login: seuros
body: This will be a NOP if both AJ and Rails are not loaded before sidekiq.
created_at: 2025-04-03T18:09:40Z
author_association: COLLABORATOR
diff_hunk: @@ -1,42 +1,9 @@
 # frozen_string_literal: true
 
 require "sidekiq/job"
-require "rails"
path: lib/sidekiq/rails.rb
user: login: seuros
body: i removed this require since Railtie was already defining Rails::Engine
created_at: 2025-04-03T18:10:49Z
author_association: COLLABORATOR
diff_hunk: @@ -48,34 +15,65 @@ def to_hash
     #     def perform
     #     end
     #   end
-    initializer "sidekiq.active_job_integration" do
-      ActiveSupport.on_load(:active_job) do
-        require_relative "../active_job/queue_adapters/sidekiq_adapter"
-        include ::Sidekiq::Job::Options unless respond_to?(:sidekiq_options)
+    require_relative "../active_job/queue_adapters/sidekiq_adapter"
+    ::ActiveJob::Base.include ::Sidekiq::Job::Options unless ::ActiveJob::Base.respond_to?(:sidekiq_options)
path: lib/sidekiq/rails.rb
user: login: seuros
body: This was removed from the initialiser to as it AJ specific.
created_at: 2025-04-03T18:13:08Z
author_association: COLLABORATOR

PR comments:
user: login: mperham
created_at: 2025-04-03T18:24:50Z
body: Can you reproduce the issue locally? Did you test that this fixes the issue?
author_association: COLLABORATOR
user: login: jagthedrummer
created_at: 2025-04-03T21:49:43Z
body: A bunch of tests here are failing with the same error from #6668.

```
uninitialized constant Sidekiq::ActiveJob
```
author_association: NONE
user: login: seuros
created_at: 2025-04-03T23:18:28Z
body: @mperham I've arranged the code to work with the cases we discussed.
The implementation now has two key improvements:

For ActiveJob integration:

If the application is bundled with activejob (≥ 7.0), Sidekiq will require it and patch the adapter
This happens in the sidekiq_adapter file with its own version checking

For Rails integration:

If rails(≥ 7.0) is detected, Sidekiq will require rails to define the initializers.

The gem method provides automatic version requirement checking.
author_association: COLLABORATOR
user: login: mperham
created_at: 2025-04-04T16:03:54Z
body: Is there any Rails core member who can review this? I want to understand if there is a "right" way to conditionally load code.

I wonder if we should always load `Sidekiq::ActiveJob::Wrapper`, even if not running in Rails. It's a tiny class with no dependencies. Would that fix the issue?
author_association: COLLABORATOR
user: login: seuros
created_at: 2025-04-04T17:14:58Z
body: I was part of the effort that build AJ.

The changed wrapper adds features not normally in ActiveJob. 

The original wrapper didn't let you send options to Sidekiq. We focused on the opensource version, not the Pro with unique jobs ect. Back then, Sidekiq Enterprise didn't exist yet. 

Later, Sidekiq Pro users had to use both /jobs and /workers folders for different job types, which was annoying because job code was in two places or mixed with different syntax.


In this [gist](https://gist.github.com/seuros/dec6e0b3c0df682666dc9ef565b93b94) , you can see a job can run without using Railties. Since Railtie isn't detected, you must set up ActiveJob:Base directly to use sidekiq.

If you use this PR branch, you can use all ActiveJob features, including Sidekiq Pro/Enterprise options. 

But with older versions before this PR, Sidekiq options aren't added unless you include the Railtie gem or another gem that needs it.


In Other hand, the sidekiq adapter in rails is deprecated and will be remove . @fatkodima has a PR opened.
author_association: COLLABORATOR
user: login: mperham
created_at: 2025-04-04T23:39:52Z
body: I think I understand and I like the direction. Thanks for the explanation. 😎
author_association: COLLABORATOR
user: login: seuros
created_at: 2025-04-11T18:35:18Z
body: @mperham should we merge ?
author_association: COLLABORATOR