第28回/開発モードでプラグインのコードを修正しても反映されない問題
プラグインのコントローラで適当なインスタンス変数を定義しておく
同じくプラグインのビューで、その変数を表示する
アプリを稼動させたまま、コントローラ側の変数の値を修正
ブラウザをリロードしても値の修正が反映されない。
redmine 本体の issue.rb 等で同じことをやるときちんと変更が反映される。
開発モードでのソースコードリロードの仕組みはどうやって実現しているのか?
code:config/environments/development.rb
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
上記で設定したクラスがインスタンス化されて特定のディレクトリを監視している。
rails のコードを読むと、以下の場所でインスタンス化されている。
定義されている場所は、Rails::Application::Finisher 全ての初期化を終えた後、最期に実行されている。
code:ruby
if config.reloading_enabled?
if config.reload_classes_only_on_change
reloader = config.file_watcher.new(*watchable_args, &callback)
reloaders << reloader
# Prepend this callback to have autoloaded constants cleared before
# any other possible reloading, in case they need to autoload fresh
# constants.
app.reloader.to_run(prepend: true) do
# In addition to changes detected by the file watcher, if routes
# or i18n have been updated we also need to clear constants,
# callback has to clear autoloaded constants after any update.
class_unload! do
reloader.execute
end
end
else
app.reloader.to_complete do
class_unload!(&callback)
end
end
else
ActiveSupport::DescendantsTracker.disable_clear!
end
watchable_args が監視対象を定義しているこれは Rails::Application で定義されている
code:ruby
def watchable_args # :nodoc:
files, dirs = config.watchable_files.dup, config.watchable_dirs.dup
ActiveSupport::Dependencies.autoload_paths.each do |path|
end
end
ここで戻り値 [flies, dirs] に対して autoload_paths のディレクトリが順次追加されているが、その上の部分で config.watchable_dirs が dirs の初期値となっている。
config.watcherble_dirs の定義は以下の通り。監視対象のディレクトリと監視対象となる拡張子の配列。
code:rails/railties/lib/rails/railtie/configuration.rb
# Add directories that should be watched for change.
# The key of the hashes should be directories and the values should
# be an array of extensions to match in each directory.
def watchable_dirs
@@watchable_dirs ||= {}
end
Finisher は、全ての initializer が実行された後に実行されるので、initializer 実行時に watchable_dirs を変更すれば監視対象に追加してくれるはず。
code:diff
--- a/lib/redmine/plugin_loader.rb
+++ b/lib/redmine/plugin_loader.rb
@@ -128,6 +128,7 @@ module Redmine
engine_cfg.paths.add 'lib', eager_load: true
engine_cfg.eager_load_paths.each do |dir|
Rails.autoloaders.main.push_dir dir
+ Rails.application.config.watchable_dirsdir = :rb end
end
end
特に指摘されていないが、同じ方法で Zeitwerk にパスを渡している lib/redmine 以下のクラス群も変更が反映されていなかったはず。下記の通り修正が必要なはず。
code:diff
--- a/config/initializers/zeitwerk.rb
+++ b/config/initializers/zeitwerk.rb
@@ -2,6 +2,7 @@
lib = Rails.root.join('lib/redmine')
Rails.autoloaders.main.push_dir lib, namespace: Redmine
+Rails.application.config.watchable_dirslib = :rb 上記再現手順を実行して変更が反映されることを確認した。
確認したこと
plugins 以下のコードを Zeitwerk の監視対象とすることと開発時にクラスのリロード対象とする処理は個別に設定する必要がある。rails では、デフォルトで autoload_paths に追加されたディレクトリは自動的に zeitwerk の監視対象とリロード対象の両方に設定してくれる。しかし、Redmine のプラグインはアプリケーションインスタンス生成後にinitializerでパスを追加しているので、これらの設定は独力で個別に行う必要があった。リロード対象となるディレクトリを追加する方法は rails側で用意されていた。
code:txt
After some research, I've found out that the directory settings monitored by Zeitwerk and monitored by the Reloader must be done respectively.
(In the code of Redmine itself, these settings done automatically by Rails.)
The attached patch will fix the problem.