0
votes

I am implementing a singleton class/module in Rails 6 application using Zeitwerk loader.

# app/lib/mynamespace/mymodel.rb

module Mynamespace
  module Mymodel
    class << self
      attr_accessor :client
    end

    def self.client
      @client ||= "default_value"
    end

    def self.client=(client)
      @client = client
    end
end

Singleton class is initialized in

# config/initializers/mymodel.rb

Mynamespace::Mymodel.client = "my_custom_value"
# Mynamespace::Mymodel.client - this returns correct value

Then when I use the singleton class in a controller

# app/controllers/mycontroller.rb

client = Mynamespace::Mymodel.client

it returns an empty object as it was not initialized: client == "default_value" but should be "my_custom_value".

Log shows errors

DEPRECATION WARNING: Initialization autoloaded the constants Mynamespace::Mymodel

Autoloading during initialization is going to be an error condition in future versions of Rails.

How to properly configure a singleton class while using Zeitwerk ?

1

1 Answers

1
votes

I believe the issue here is that the way Zeitwerk loads your code, it's first loading Gems from your Gemfile, then running initializers, then loading your application code, so trying to run Mynamespace::MyModel.client, means it has to stop what it's doing and load app/lib/mynamespace/mymodel.rb to load that constant, to execute client= on it.

This also means that if you change the Mynamespace::MyModel code, Rails will not be able to hot-reload the constant, because initializers don't get re-run, introducing a circular dependency lock (have you ever seen an error like "module MyModel removed from tree but still active!" or have to use require_dependency before using some code that should be autoloaded but isn't?). Zeitwerk attempts to fix that class of issues.

Move that code out of config/initializers, and into config/application.rb, and it will still be run on boot.