RedmineプラグインのRedmine5(Rails6)向け対応
Redmine5にてRailsが6.1となることが決まっており、Redmineのtrunkでは既にRails6対応が入っています。
特に一番影響のありそうなZeitwerkへの対応が先日(2021-11-18)入りました。
これによって私がメンテしている2つのプラグインが動作しなくなりました。
- onozaty/redmine-view-customize: View customize plugin for Redmine
- onozaty/redmine_issue_assign_notice: It is a plugin of Redmine that notifies Slack, Rocket.Chat, Teams, etc. that the issue assignee changed.
この記事では、Redmine5に向けて修正した内容をまとめていきます。
なお、問題の原因が、Rails6の変更によるものなのか、Redmine本体の変更によるものなのか、Ruby 3.0 によるものなのかといったところまでの確認は出来ていません。
確認した際の各種バージョンは下記の通りです。
- Redmine: trunk r21289
- Ruby: 3.0.0-p0
- Rails: 6.1.4.1
修正は、Redmine5でしか動かないものではなく、今までのバージョンでも動くような形での修正を行っていきます。
現時点(2021-11-23)ではRedmine5のリリース予定日は決まっていないため、今後別の対応が必要になった場合には、本記事を更新していく予定です。
参考情報
今回の対応を行うにあたり、Redmine本体でのZeitwerkへの対応チケットが参考になりました。
また、Zeitwerkについては、下記が参考になりました。
対応内容
require や require_dependency で lib 配下からの指定が cannot load such file となる
プラグインフォルダ配下のlib
フォルダにあるファイルは、lib
フォルダからのパスを指定することでロードできていましたが、Redmine5ではロードできません。
例えば下記のような記述でlib/view_customize/view_hook.rb
をロードしようとすると
require_dependency 'view_customize/view_hook'
cannot load such file となります。
cannot load such file -- view_customize/view_hook (LoadError)
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/kernel.rb:35:in `require'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/activesupport-6.1.4.1/lib/active_support/dependencies/zeitwerk_integration.rb:51:in `require_dependency'
/var/lib/redmine/plugins/view_customize/after_init.rb:1:in `<top (required)>'
/var/lib/redmine/plugins/view_customize/init.rb:22:in `require_relative'
/var/lib/redmine/plugins/view_customize/init.rb:22:in `<top (required)>'
/var/lib/redmine/lib/redmine/plugin_loader.rb:31:in `load'
/var/lib/redmine/lib/redmine/plugin_loader.rb:31:in `run_initializer'
/var/lib/redmine/lib/redmine/plugin_loader.rb:106:in `each'
/var/lib/redmine/lib/redmine/plugin_loader.rb:106:in `block in load'
...
require_dependency
でもrequire
のどちらでもダメです。
絶対パスで指定する形に変更することで対応しました。また、require_dependency
である必要は既に無いようなのでrequire
に変えました。
require File.expand_path('../lib/view_customize/view_hook', __FILE__)
モジュール名とディレクトリ名がアンマッチだと uninitialized constant が発生する
lib/view_customize/view_hook.rb
で RedmineViewCustomize::ViewHook
というクラスを定義していたところ、下記のようなエラーとなりました。
Error: The application encountered the following error: uninitialized constant ViewCustomize::ViewHook (NameError)
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:95:in `const_get'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:95:in `cget'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:232:in `block (2 levels) in eager_load'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:26:in `block in ls'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:18:in `each_child'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader/helpers.rb:18:in `ls'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:227:in `block in eager_load'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:212:in `synchronize'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:212:in `eager_load'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:312:in `each'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/zeitwerk-2.5.1/lib/zeitwerk/loader.rb:312:in `eager_load_all'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/railties-6.1.4.1/lib/rails/application/finisher.rb:133:in `block in <module:Finisher>'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/railties-6.1.4.1/lib/rails/initializable.rb:32:in `instance_exec'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/railties-6.1.4.1/lib/rails/initializable.rb:32:in `run'
/var/lib/redmine/vendor/bundle/ruby/3.0.0/gems/railties-6.1.4.1/lib/rails/initializable.rb:61:in `block in run_initializers'
...
ディレクトリ名とモジュール名がアンマッチだと発生するようなので、ディレクトリ名をredmine_view_customize
に変えてlib/redmine_view_customize/view_hook.rb
とすることで回避しました。
クラス名とファイル名のアンマッチでも起きるようなので要注意です。
この問題はdevelopmentモードでは発生せず、productionモードで発生していました。
ActiveSupport::Reloader.to_prepare で登録したコールバックが呼び出されない
developmentモードを考慮してActiveSupport::Reloader.to_prepare
でIssue
に対してパッチ当てるようにしていましたが、これが全く呼び出されなくなりました。
((Rails.version > "5")? ActiveSupport::Reloader : ActionDispatch::Callbacks).to_prepare do
require_dependency 'issue'
unless Issue.included_modules.include? RedmineIssueAssignNotice::IssuePatch
Issue.send(:include, RedmineIssueAssignNotice::IssuePatch)
end
end
アプリケーション初期化時にも呼び出されないため、productionモードでも1回も呼び出されませんでした。
ActiveSupport::Reloader.to_prepare
が動作しない理由はわからず、Zeitwerkに関するドキュメント見ると Rails.application.reloader.to_prepare
での初期化方法が書いてあったので、こちらも試してみましたが結果は変わりませんでした。
developmentモードでのリロード時の考慮はそんな重要なことではないので、いったん下記のように1回のみ実行する形に変更して回避しました。
require 'issue'
unless Issue.included_modules.include? RedmineIssueAssignNotice::IssuePatch
Issue.send(:include, RedmineIssueAssignNotice::IssuePatch)
end
追記(2021-12-02)
コメントにて「Rails.application.reloader.to_prepare
のブロック内に書いていた処理を、 init.rb
に直接書く」が対応策と教えていただきました。
下記のチケットのやりとりが参考になります。
Redmine本体側の問題
プラグインのassetsが正しくコピーされない
Redmineのtrunkの r21289 の時点では、プラグインのassetsが正しくコピーされないといった問題があります。
r21295 にて既に修正されている問題です。
エラーにはなりませんが、CSSや画像が読み込まれず、レイアウトが崩れる場合があるのでご注意ください。
developmentモードでプラグイン配下のファイルを変更してもリロードされない (追記 2021-12-02)
Redmineのtrunkの r21295 の時点では、developmentモードでプラグイン配下のファイルを変更してもリロードされないといった問題があります。
Discussion
Redmine の zeitwerk パッチを書いたものです。色々と説明が足りず、さらにcss関連のデグレもあってご迷惑をおかけしました。
Rails.application.reloader.to_prepare
の件は、でも指摘がありました。その返答で書いた通り、従来各プラグインの
init.rb
をinitialize時に実行していたのを、Rails.application.reloader.to_prepare
の中で実行するように変更したのが不具合の原因です。init.rb
の中で reloader への登録を実行しても、reloadのたびに処理の登録がされるだけで、登録した処理そのものは実行されません。ですので、「Rails.application.reloader.to_prepare
のブロック内に書いていた処理を、init.rb
に直接書く」が対応策になります。なお、
init.rb
は、オートローダーが有効になった環境で実行されるので、require 'issue'
なども不要となります。zeitwerk対応はやはり影響が大きそうなので、本家の wiki に対応方法を記載したいと思っています。コメントありがとうございます。
こちらでいろいろ解析した時には、init.rb自体の再ロードも走っていませんでした。
その時はプラグインのソース(lib配下)を変更していたのですが、もしかしたら、監視対象からプラグインのファイルが外れてしまっているのでは、、と思いました。
Redmine本体のファイルを変更した際は試していなかったので、後ほど確認してみたいと思います。
ありがとうございます。
過去のRedmineとの互換性(Zeitwerk対応前のバージョンでも動くように)を考えて残していたのですが、よくよくみたら reuire 'issue' はそもそも元からいらなかったのかもしれません、、
trunkにあげたら入れていたプラグインが7割死んだといっていた方もいらっしゃったので、たしかに影響は大きそうです。本家のwikiに情報があると助かる人は多いと思います。
最後にですが、、Redmine本体での対応ありがとうございました。
Redmine利用者として、RedmineがRailsに追従してメンテナンスされていくことはとても助かっています。
Redmine本体のファイルを変更した時はリロードされていました。
プラグインフォルダが監視対象から外れてしまっているように見えたので、Issueあげさせていただきました。