Home > Software engineering >  How to properly extend ApplicationRecord in Rails 6 with Zeitwerk
How to properly extend ApplicationRecord in Rails 6 with Zeitwerk

Time:10-01

Consider a Rails 6 application that has app/models/application_record.rb. This Rails 6 application is using Zeitwerk loader.

class ApplicationRecord
end

If I want to add functionality to ApplicationRecord via a module:

# app/models/concerns/fancy_methods.rb
module FancyMethods
  def fancy_pants
    puts "I'm wearing fancy pants"
  end
end

and do the following:

class ApplicationRecord
  include FancyMethods
end

I will get a deprecation warning or error:

DEPRECATION WARNING: Initialization autoloaded the constant FancyMethods.

Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.

Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload FancyMethods, for example,
the expected changes wont be reflected in that stale Module object.

This autoloaded constant has been unloaded.

Please, check the "Autoloading and Reloading Constants" guide for solutions.
 (called from <top (required)> at /Users/peter/work/recognize/config/environment.rb:5)

I've read lots of articles including the Rails autoload docs, but nothing really addresses this minimal but common case of extending ApplicationRecord. Yes, I could wrap ApplicationRecord in a .to_prepare block like:

Rails.configuration.to_prepare do
  class ApplicationRecord < ActiveRecord::Base
    include FancyMethods
  end
end

But this seems like a code smell and could cause other unexpected problems now or down the line.

CodePudding user response:

Figured it out! The issue was there was an explicit require in an initializer that loaded ApplicationRecord.

# config/initializers/setup_other_fancy_thing.rb
require 'application_record'

module OtherFancyThing
  def also_fancy
    puts 'also fancy'
  end
end

ApplicationRecord.send(:include, OtherFancyThing)

The way I debugged this was that I:

  1. Created a new Rails app of the same version and could not reproduce the error
  2. I copied the default application.rb and development.rb and still got the error
  3. Moved the entire config/initializers directory to a temp directory and it made the warning go away! So, I knew it had to be one of the initializers. From there it was just a matter of dividing and conquering until I found the offending initializer.
  • Related