I also have not found the canonical solution to this problem, but, in the context of Rails: Auto-reload gem files used in dummy app, have found a workaround:
Suppose, the gem folder is
~/rails/foo_gem
and the rails-6-app folder is:
~/rails/bar_app
To reload the gem code in the app on file-system changes, I needed to do three steps:
- Unregister the zeitwerk loader defined in
foo_gem.rb
that handles loading the different gem files.
- Define a new zeitwerk loader in the
development.rb
of the app that is configured with enable_reloading
.
- Setup a file-system watcher and trigger a reload when a gem file changes.
Zeitwerk::Loader
in foo_gem.rb
# ~/rails/foo_gem/lib/foo_gem.rb
# require 'foo_gem/bar` # Did not work. Instead:
# (a) use zeitwerk:
require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.push_dir File.join(__dir__)
loader.tag = "foo_gem"
loader.setup
# or (b) use autoload:
module FooGem
autoload :Bar, "foo_gem/bar"
end
Note:
- In the past, I've just loaded all ruby files of the gem with
require
from a kind of index file called just like the gem, here: foo_gem.rb
. This does not work here, because zeitwerk appears to ignore files that have previously been loaded with require
. Instead I needed to create a separate zeitwerk loader for the gem.
- This loader has no
enable_reloading
because otherwise, reloading would be enabled for this gem whenever using the gem, not just while developing the gem.
- I have given the loader a
tag
, which allows to find this loader later in the Zeitwerk::Registry
in order to un-register it.
- Instead of using zeitwerk in
foo_gem.rb
, one could also use autoload
there like the devise gem does. This is the best way if you want to support rails versions earlier than 6 because zeitwerk requires rails 6+. Using autoload
here also makes step 1 in the next section unnecessary.
Zeitwerk::Loader
in development.rb
of the app
# ~/rails/bar_app/config/environments/development.rb
# 1. Unregister the zeitwerk loader defined in foo_gem.rb that handles loading
# the different gem files.
#
Zeitwerk::Registry.loaders.detect { |l| l.tag == "foo_gem" }.unregister
# 2. Define a new zeitwerk loader in the development.rb of the app
# that is configured with enable_reloading.
#
gem_root_path = Pathname.new(Gem.loaded_specs["foo_gem"].full_gem_path)
gem_loader = Zeitwerk::Loader.new
gem_loader.push_dir gem_root_path.join("lib")
gem_loader.enable_reloading
gem_loader.setup
# 3. Setup a file-system watcher and trigger a reload when a gem file changes.
#
Listen.to gem_root_path.join("lib"), only: /\.rb$/ do
gem_loader.reload
end.start
Note:
- Zeitwerk does not allow two loaders managing the same files. Therefore, I need to unregister the previously defined loader tagged
"foo_gem"
.
- The new loader used in the app has
enable_reloading
. Therefore, when using the app with rails server
, rails console
, or when running the specs, the gem files can be reloaded.
- The gem files are not automatically reloaded by zeitwerk. One needs a file-system watcher to trigger the reload on file-system changes. I did not manage to get the
ActiveSupport::FileUpdateChecker
working. Instead, I've used the listen gem as file-system watcher.
With this setup, when using the rails server
, the rails console
, or the specs of the bar_app, gem files of the foo_gem
are reloaded after being edited, which means that one does no longer need to restart the rails server
to pick up the changes.
However, I do not find this workaround very convenient.