Sudden "uninitialized constant PurIssue" error on STI_preload while upgrading rails to 7.0.1

749 views Asked by At

I am in the process of upgrading a ~2 year old Rails app from 6.1 to 7.0.1

I have an STI setup where Pursuit is the main class and it has several other types as descendants:

# app/models/pursuit.rb
require 'sti_preload'

class Pursuit < ApplicationRecord
# ...
end

Descendant classes all look like this:

# app/models/pur_issue.rb
class PurIssue < Pursuit
# ...
end

--

# app/models/pur_goal.rb
class PurGoal < Pursuit
# ...
end

--

# app/models/pur_tracker.rb
class PurTracker < Pursuit
# ...
end

I have been using STI preload snippet as recommended by the Ruby Guides, and all worked well under Rails 6.0 and 6.1.

But now that I am upgrading to Rails 7.0.1, I suddenly get an "Uninitialized Constant" error for only the PurIssue subclass whereas all the other subclasses load fine:

Showing /Users/me/gits/rffvp/app/views/pursuits/_stats.html.slim where line #1 raised:

uninitialized constant PurIssue

Extracted source (around line #33):

33 types_in_db.each do |type|
34   logger.debug("Preloading STI type #{type}")
35   type.constantize
36 end
37 logger.debug("Types in database #{types_in_db}")


Trace of template inclusion: #<ActionView::Template app/views/layouts/_footer.html.slim locals=[]>, #<ActionView::Template app/views/layouts/application.html.slim locals=[]>

Rails.root: /Users/me/gits/rffvp
Application Trace | Framework Trace | Full Trace
lib/sti_preload.rb:33:in `block in preload_sti'
lib/sti_preload.rb:31:in `each'
lib/sti_preload.rb:31:in `preload_sti'
lib/sti_preload.rb:13:in `descendants'
app/models/pursuit.rb:71:in `<class:Pursuit>'
app/models/pursuit.rb:54:in `<main>'
app/models/pur_issue.rb:52:in `<main>'
app/views/pursuits/_stats.html.slim:1
app/views/layouts/_footer.html.slim:14
app/views/layouts/application.html.slim:13

I can't seem to figure out why PurIssue will not load anymore whereas all the other subclasses will. This is breaking my entire app since PurIssues are the most important data points.

Can someone point me to a Rails configuration change between 6.0, 6.1 and 7.0 that might cause this different behavior?

# lib/sti_preload.rb
module StiPreload
  unless Rails.application.config.eager_load
    extend ActiveSupport::Concern

    included do
      cattr_accessor :preloaded, instance_accessor: false
    end

    class_methods do
      def descendants
        preload_sti unless preloaded
        super
      end

      # Constantizes all types present in the database. There might be more on
      # disk, but that does not matter in practice as far as the STI API is
      # concerned.
      #
      # Assumes store_full_sti_class is true, the default.
      def preload_sti
        types_in_db = \
          base_class
          .unscoped
          .select(inheritance_column)
          .distinct
          .pluck(inheritance_column)
          .compact

        types_in_db.each do |type|
          logger.debug("Preloading STI type #{type}")
          type.constantize
        end
        logger.debug("Types in database #{types_in_db}")

        self.preloaded = true
      end
    end
  end
end

Zeitwerk shows the same error by the way:

$ rails zeitwerk:check        
Hold on, I am eager loading the application.
rails aborted!
NameError: uninitialized constant PurIssue
/Users/me/gits/rffvp/lib/sti_preload.rb:33:in `block in preload_sti'
/Users/me/gits/rffvp/lib/sti_preload.rb:31:in `each'
/Users/me/gits/rffvp/lib/sti_preload.rb:31:in `preload_sti'
/Users/me/gits/rffvp/lib/sti_preload.rb:13:in `descendants'
/Users/me/gits/rffvp/app/models/concerns/validateable.rb:10:in `block in <module:Validateable>'
/Users/me/gits/rffvp/app/models/pursuit.rb:68:in `include'
/Users/me/gits/rffvp/app/models/pursuit.rb:68:in `<class:Pursuit>'
/Users/me/gits/rffvp/app/models/pursuit.rb:54:in `<main>'
/Users/me/gits/rffvp/app/models/pur_issue.rb:1:in `<main>'
Tasks: TOP => zeitwerk:check
(See full trace by running task with --trace)
1

There are 1 answers

6
Dave W On

I saw the same thing trying to do an upgrade to rails 7. I believe it originates with this change: https://github.com/rails/rails/commit/ffae3bd8d69f9ed1ae185e960d7a38ec17118a4d

Effectively the change to invoke Class.descendants directly in the internal association callback methods exposes a more longstanding implicit issue with invoking Class.descendants at all during the autoload, as the StiPreload implementation may result in a problem where it circularly attempts to constantize the original class being autoloaded. I've added an issue in the Rails repo here https://github.com/rails/rails/issues/44252