📕

RedmineプラグインのRedmine5(Rails6)向け対応

2021/11/26に公開
3

Redmine5にてRailsが6.1となることが決まっており、Redmineのtrunkでは既にRails6対応が入っています。

特に一番影響のありそうなZeitwerkへの対応が先日(2021-11-18)入りました。

これによって私がメンテしている2つのプラグインが動作しなくなりました。

この記事では、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.rbRedmineViewCustomize::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_prepareIssueに対してパッチ当てるようにしていましたが、これが全く呼び出されなくなりました。

((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

tohosakutohosaku

Redmine の zeitwerk パッチを書いたものです。色々と説明が足りず、さらにcss関連のデグレもあってご迷惑をおかけしました。
Rails.application.reloader.to_prepare の件は、

https://www.redmine.org/issues/36245

でも指摘がありました。その返答で書いた通り、従来各プラグインの 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 に対応方法を記載したいと思っています。

onozatyonozaty

コメントありがとうございます。

init.rb の中で reloader への登録を実行しても、reloadのたびに処理の登録がされるだけで、登録した処理そのものは実行されません。ですので、「 Rails.application.reloader.to_prepare のブロック内に書いていた処理を、 init.rb に直接書く」が対応策になります。

こちらでいろいろ解析した時には、init.rb自体の再ロードも走っていませんでした。
その時はプラグインのソース(lib配下)を変更していたのですが、もしかしたら、監視対象からプラグインのファイルが外れてしまっているのでは、、と思いました。
Redmine本体のファイルを変更した際は試していなかったので、後ほど確認してみたいと思います。

なお、init.rb は、オートローダーが有効になった環境で実行されるので、 require 'issue' なども不要となります。

ありがとうございます。
過去のRedmineとの互換性(Zeitwerk対応前のバージョンでも動くように)を考えて残していたのですが、よくよくみたら reuire 'issue' はそもそも元からいらなかったのかもしれません、、

zeitwerk対応はやはり影響が大きそうなので、本家の wiki に対応方法を記載したいと思っています。

trunkにあげたら入れていたプラグインが7割死んだといっていた方もいらっしゃったので、たしかに影響は大きそうです。本家のwikiに情報があると助かる人は多いと思います。

最後にですが、、Redmine本体での対応ありがとうございました。
Redmine利用者として、RedmineがRailsに追従してメンテナンスされていくことはとても助かっています。

onozatyonozaty

init.rb の中で reloader への登録を実行しても、reloadのたびに処理の登録がされるだけで、登録した処理そのものは実行されません。ですので、「 Rails.application.reloader.to_prepare のブロック内に書いていた処理を、 init.rb に直接書く」が対応策になります。

こちらでいろいろ解析した時には、init.rb自体の再ロードも走っていませんでした。
その時はプラグインのソース(lib配下)を変更していたのですが、もしかしたら、監視対象からプラグインのファイルが外れてしまっているのでは、、と思いました。
Redmine本体のファイルを変更した際は試していなかったので、後ほど確認してみたいと思います。

Redmine本体のファイルを変更した時はリロードされていました。
プラグインフォルダが監視対象から外れてしまっているように見えたので、Issueあげさせていただきました。