1

在开发 gem 时,我经常使用一个需要 gem 的虚拟 rails 应用程序,以便在我进行时尝试 gem 更改。此外,我使用相同的虚拟应用程序进行集成测试。

通常,我有宝石

~/rails/foo_gem

以及相关的虚拟应用程序:

~/rails/foo_gem/spec/dummy_app

使用zeitwerk代码加载器,我如何配置虚拟应用程序不仅可以在更改时重新加载虚拟应用程序 ruby​​ 文件,还可以获取对 gem 文件的更改?否则,在开发 gem 时,我必须为 gem 文件的每次更改重新加载 dummy-app rails 服务器。

# ~/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.

不工作: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")

我尝试将 gem 添加到自动加载路径,但代码不会在文件系统更改时重新加载。

没用:Zeitwerk::Loaderenable_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

添加单独的 zeitwerk 加载程序没有帮助;据我所知,加载程序没有文件系统观察程序;所以需要调用gem_loader.reload才能重新加载 gem 类。

没用:Zeitwerk::Loader#reloadrequire

如果 gem 中需要 gem 的文件,例如

# ~/rails/foo_gem/lib/foo_gem.rb

require 'foo_gem/bar`

然后该文件~/rails/foo_gem/lib/foo_gem/bar.rbZeitwerk::Loader. 调用gem_loader.reload不会重新加载此文件。

没有用:Zeitwerk::Loader#reload使用 zeitwerk 加载器来加载 gem 文件

如果 gem 的文件不是手动需要的,而是使用不同的 zeitwerk 加载器,例如

# ~/rails/foo_gem/lib/foo_gem.rb

require "zeitwerk"
loader = Zeitwerk::Loader.new
loader.push_dir(__dir__)
loader.setup

然后目录~/rails/foo_gem/lib由两个独立的 zeitwerk 加载器管理:loaderinfoo_gem.rbgem_loaderin development.rb。这显然是 zeitwerk 不允许的,它抱怨Zeitwerk::Error

loader ...想要管理目录~/rails/foo_gem/lib,该目录已经由...管理

不工作: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

我尝试使用ActiveSupport::FileUpdateChecker来监视更改,但是,至少在我的 dockerized 设置中,重新加载代码的块永远不会执行。

4

1 回答 1

0

要在文件系统更改时重新加载 gem 代码,我需要执行三个步骤:

  1. foo_gem.rb取消注册处理加载不同 gem 文件的 zeitwerk 加载程序。
  2. development.rb配置有enable_reloading.
  3. 设置文件系统观察程序并在 gem 文件更改时触发重新加载。

我确信有一个更简单,更清洁的解决方案。随时将其作为单独的答案发布。我将在此处发布我的解决方案,但请考虑将其视为一种解决方法。

Zeitwerk::Loaderfoo_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

笔记:

  • 过去,我只是require从一种类似于 gem 的索引文件中加载 gem 的所有 ruby​​ 文件,这里:foo_gem.rb. 这在这里不起作用,因为 zeitwerk 似乎忽略了以前加载的文件require。相反,我需要为 gem 创建一个单独的 zeitwerk 加载器。
  • 这个加载器没有enable_reloading,因为否则,无论何时使用 gem,都会为这个 gem 启用重新加载,而不仅仅是在开发 gem 时。
  • 我给了加载器 a tag,它允许稍后找到这个加载器Zeitwerk::Registry以便取消注册它。
  • 除了在 zeitwerk 中使用之外foo_gem.rb,还可以像 devise gem 那样autoload使用那里。如果想要支持早于 6 的 rails 版本,这是最好的方法,因为 zeitwerk 需要 rails 6+。在此处使用也使下一节中的步骤 1 变得不必要。autoload

Zeitwerk::Loader in development.rbthe dummy app

# ~/rails/foo_gem/spec/dummy_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 dummy app
#    that is configured with enable_reloading.
#
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.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

笔记:

  • Zeitwerk 不允许两个加载程序管理相同的文件。因此,我需要注销之前定义的 loader 标记"foo_gem"
  • 虚拟应用程序中使用的新加载程序具有enable_reloading. 因此,当使用带有rails server,的虚拟应用程序rails console或运行规范时,可以重新加载 gem 文件。
  • zeitwerk 不会自动重新加载 gem 文件。需要一个文件系统观察器来触发文件系统更改的重新加载。我没有设法得到ActiveSupport::FileUpdateChecker工作。相反,我使用了listen gem 作为文件系统观察器。

使用此设置,当使用虚拟应用程序的rails serverrails console或使用虚拟应用程序的集成测试时,gem 文件在编辑后会重新加载,这意味着不再需要重新启动rails server来获取更改。

参考

于 2022-02-05T19:21:33.617 回答