Reputation: 26363
I am developing a Rails 6 app and a Gem in parallel.
In the past, I used the require_reloader Gem so that Rails would reload the Gem when any files changed in the Gem's local directory.
With Zeitwerk becoming the new loader in Rails 6, this Gem doesn't seem to work anymore.
So my question: What is the canonical way to develop a Gem and a Rails 6 app in parallel so that changes made to Gem files are automatically visible in Rails?
Upvotes: 5
Views: 1160
Reputation: 6137
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:
foo_gem.rb
that handles loading the different gem files.development.rb
of the app that is configured with enable_reloading
.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:
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.enable_reloading
because otherwise, reloading would be enabled for this gem whenever using the gem, not just while developing the gem.tag
, which allows to find this loader later in the Zeitwerk::Registry
in order to un-register it.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:
"foo_gem"
.enable_reloading
. Therefore, when using the app with rails server
, rails console
, or when running the specs, the gem files can be reloaded.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.
Upvotes: 2