I just upgraded an Engine from Rails 5 to Rails 7. This error started appearing at Rails 6.1.7.6, but I thought perhaps it might've been fixed in Rails 7.
Here's the error I get when I run rspec
An error occurred while loading ./spec/awesome_engine/services/awesome_engine/pdf_exporter/termination_spec.rb.
Failure/Error: Rails.application.initialize!
FrozenError:
can't modify frozen Array: ["/Users/bobbert/.gem/ruby/2.7.6/gems/actiontext-7.0.7.2/app/helpers", "/Users/bobbert/.gem/ruby/2.7.6/gems/actiontext-7.0.7.2/app/models"]
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/engine.rb:575:in `unshift'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/engine.rb:575:in `block in <class:Engine>'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:32:in `instance_exec'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:32:in `run'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:61:in `block in run_initializers'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:50:in `each'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:50:in `tsort_each_child'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:50:in `each'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:50:in `tsort_each_child'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/initializable.rb:60:in `run_initializers'
# /Users/bobbert/.gem/ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/application.rb:372:in `initialize!'
# ./spec/dummy/config/environment.rb:5:in `<top (required)>'
# /Users/bobbert/.gem/ruby/2.7.6/gems/zeitwerk-2.6.11/lib/zeitwerk/kernel.rb:38:in `require'
# /Users/bobbert/.gem/ruby/2.7.6/gems/zeitwerk-2.6.11/lib/zeitwerk/kernel.rb:38:in `require'
# ./spec/spec_helper.rb:5:in `<top (required)>'
# /Users/bobbert/.gem/ruby/2.7.6/gems/zeitwerk-2.6.11/lib/zeitwerk/kernel.rb:38:in `require'
# /Users/bobbert/.gem/ruby/2.7.6/gems/zeitwerk-2.6.11/lib/zeitwerk/kernel.rb:38:in `require'
# ./spec/awesome_engine/services/awesome_engine/pdf_exporter/termination_spec.rb:1:in `<top (required)>'
...
Finished in 0.00005 seconds (files took 10.79 seconds to load)
0 examples, 0 failures, 133 errors occurred outside of examples
It occurs multiple times when trying different specs, and it always stems from the first line in the spec, then spec_helper.rb:5, and finally environment.rb:5.
First line of every spec is:
require 'spec_helper'
spec_helper.rb, Line 5
require File.expand_path("../dummy/config/environment.rb", __FILE__)
environment.rb, Line 5
Rails.application.initialize!
And this is the Rails code that's throwing the error (ruby/2.7.6/gems/railties-7.0.7.2/lib/rails/engine.rb:575):
573 initializer :set_autoload_paths, before: :bootstrap_hook do
574 ActiveSupport::Dependencies.autoload_paths.unshift(*_all_autoload_paths)
575 ActiveSupport::Dependencies.autoload_once_paths.unshift(*_all_autoload_once_paths)
576
577 config.autoload_paths.freeze
578 config.autoload_once_paths.freeze
579 end
I've been following various Rails guides on the changes to autoloading, including:
- https://guides.rubyonrails.org/upgrading_ruby_on_rails.html#autoloading-during-initialization
- https://rubyonrails.org/2021/9/3/autoloading-in-rails-7-get-ready
- https://guides.rubyonrails.org/v7.0/autoloading_and_reloading_constants.html#use-case-1-during-boot-load-reloadable-code
I've also tried the suggestions in other Stackoverflow questions:
- Rails : RuntimeError - can't modify frozen Array when running rspec in rails
- can't modify frozen array (TypeError) - config/application.rb:42:in `<<':
I've been at this now for several hours and am not progressing. Does anyone have any clue what's happening here and/or how to fix this issue?
UPDATE: 2023 AUGUST 28
So, I've decided to debug and step through the code.
There are 573 initializers in total. I placed a breakpoint on initializer :set_autoload_paths in engine.rb Line 574 to keep track of how many times this initializer is called. Here's what I found:
Rails::Application.initialize! L372
Rails::Initializable::Initalizer.run_initializers L61
initializer(name: :set_autoload_paths) #initializer 103 of 573
initializer(name: :set_autoload_paths) #initializer 119 of 573
initializer(name: :set_autoload_paths) #initializer 140 of 573
initializer(name: :set_autoload_paths) #initializer 158 of 573
initializer(name: :set_autoload_paths) #initializer 171 of 573
...
At this point, it's obvious that it's being called multiple times. So I decided analyze the initializers array to see just how many times it's being called and who is calling it. Here's what I found:
:set_autoload_paths=>{:count=>34, :contexts=>[#<ActionView::Railtie>, #<ActiveStorage::Engine>, #<ActionCable::Engine>, #<ActionMailbox::Engine>, #<ActionText::Engine>, #<StateMachine::RailsEngine>, #<Select2::Rails::Engine>, #<Doccex::Engine>, #<SmartListing::Engine>, #<Kaminari::Engine>, #<Devise::Engine>, #<DeviseInvitable::Engine>, #<Bootstrap::Rails::Engine>, #<Bootstrap::Switch::Rails::Engine>, #<Cocoon::Engine>, #<FontAwesome::Rails::Engine>, #<Remotipart::Rails::Engine>, #<I18n::JS::Engine>, #<Jquery::Rails::Engine>, #<Jquery::Ui::Rails::Engine>, #<JsRoutes::Engine>, #<DropzonejsRails::Engine>, #<TinyMCE::Rails::Engine>, #<BootstrapDatepickerRails::Rails::Engine>, #<Bootstrap3Datetimepicker::Rails::Engine>, #<Momentjs::Rails::Engine>, #<Uri::Js::Rails::Engine>, #<Sidekiq::Rails>, #<ActsAsTaggableOn::Engine>, #<Jscolor::Rails::Engine>, #<Tribute::Engine>, #<Doorkeeper::Engine>, #<AwesomeEngine::Engine>, #<Dummy::Application>]}
It's being called 34 times by various engines, several of which are from the Rails framework but the majority from 3rd party libraries that are included in the engine's gemspec.