When developing a gem, I often use a dummy rails app that requires the gem in order to try out gem changes as I go. Also, I use the same dummy app for integration tests.
Usually, I have the gem in
~/rails/foo_gem
and the associated dummy app in:
~/rails/foo_gem/spec/dummy_app
With the zeitwerk code loader, how do I configure the dummy app to not only reload dummy-app ruby files on change, but also to pick up the changes to the gem files? Otherwise, I would have to reload the dummy-app rails server for every change to the gem files while developing the gem.
# ~/rails/foo_gem/spec/dummy_app/config/environments/development.rb
config.cache_classes = false
config.eager_load = false
# TODO: Add ~/rails/foo_gem/lib to the list
# of watched and auto-reloaded directories.
Did not work: config.autoload_paths
# ~/rails/foo_gem/spec/dummy_app/config/environments/development.rb
gem_root_path = Pathname.new(File.expand_path(Rails.root.join("../..")))
config.autoload_paths << gem_root_path.join("lib")
I've tried to add the gem to the autoload paths, but the code is not reloaded on file-system changes.
Did not work: Zeitwerk::Loader
with enable_reloading
# ~/rails/foo_gem/spec/dummy_app/config/environments/development.rb
gem_root_path = Pathname.new(File.expand_path(Rails.root.join("../..")))
gem_loader = Zeitwerk::Loader.new
gem_loader.push_dir gem_root_path.join("lib")
gem_loader.enable_reloading
gem_loader.log!
gem_loader.setup
Adding a separate zeitwerk loader does not help; the loader does not come with a file-system watcher as far as I have understood it; so one needs to call gem_loader.reload
in order to reload the gem classes.
Did not work: Zeitwerk::Loader#reload
with require
If the gem's files are required within the gem, e.g.
# ~/rails/foo_gem/lib/foo_gem.rb
require 'foo_gem/bar`
then the file ~/rails/foo_gem/lib/foo_gem/bar.rb
is ignored by the Zeitwerk::Loader
. Calling gem_loader.reload
does not reload this file.
Did not work: Zeitwerk::Loader#reload
with zeitwerk loader for gem files
If the gem's files are not required manually, but instead a different zeitwerk loader is used, e.g.
# ~/rails/foo_gem/lib/foo_gem.rb
require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.push_dir(__dir__)
loader.setup
then the directory ~/rails/foo_gem/lib
is managed by two separate zeitwerk loaders: the loader
in foo_gem.rb
and the gem_loader
in development.rb
. This is apparently not allowed by zeitwerk, which complains with a Zeitwerk::Error
:
loader ... wants to manage directory ~/rails/foo_gem/lib, which is already managed by ...
Did not work: ActiveSupport::FileUpdateChecker
# ~/rails/foo_gem/spec/dummy_app/config/environments/development.rb
gem_root_path = Pathname.new(File.expand_path(Rails.root.join("../..")))
gem_loader = Zeitwerk::Loader.new
gem_loader.push_dir gem_root_path.join("lib")
gem_loader.enable_reloading
gem_loader.log!
gem_loader.setup
gem_files = gem_root_path.glob("lib/**/*.rb")
gem_update_checker = ActiveSupport::FileUpdateChecker.new(gem_files) do
gem_loader.reload # This line is never executed
end
ActiveSupport::Reloader.to_prepare do
gem_update_checker.execute_if_updated
end
I've tried to use the ActiveSupport::FileUpdateChecker
to watch for changes, but, at least with my dockerized setup, the block to reload the code is never executed.