`FrozenError: can't modify frozen Array` in Rails 7 Engine when running `rspec`

895 views Asked by At

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:

I've also tried the suggestions in other Stackoverflow questions:

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.

0

There are 0 answers