🌃

祝RC1!! Rails7.0 でRedmine を動かしてみた

2021/12/09に公開

この記事は Redmine Advent Calendar 2021 の9日目の記事になります。前日の記事は、tohosakuの「プラグイン を Redmine 4.* でも 5.0 でも動かすためには」でした。二日連続でおつきあい下さい。

Rails 7.0 RC1 がやってきた

2日前の早朝[1]、Ruby on Rails の公式ブログにRails 7.0 RC1: New JavaScript Answers, At-Work Encryption, Query Origin Logging, Zeitwerk Exclusivelyという記事が投稿されました。"Rails 7.0 RC1" 、確かにそう書かれています。

Rails7.0 は、およそ三ヶ月前、9/16に異例のalpha版が発表された状態でした。つまりこちらとしては、次は betaだと思ってました。beta2回、RC2回、最後のRC2が修正されずにリリースとかそんな感じだと。それが突然 "Release Candidate"!!

しかも昨日も書きましたが記事は以下のように結ばれています。

We hope to get this work in the hands of everyone before Christmas this year 🎄🎁❤️.

つまり、この Advent Calendar が終了するころには出ているかもしれません。Rails7。

実は先月半ばにとある理由から、Rails7.0-alpha2上で Redmine を動かすため試行錯誤していました。その時点では「どうせ、またガラッと変わるだろうから」と動いた branch を放置していたわけですが、事態の急転に驚き、branchをRedmineの最新のmasterにrebaseして rails test してみました。

結果、以前は見なかった新たな DEPRECATION WARNING が表示されるようになったものの、rails s で Redmine が起動するところまでは確認しました。先月の対応の記録と Redmine 修正中に便利だと思ったツールの使い方などを紹介していきたいと思います。

過程はともかく現在の対応状況を見たい、という奇特な方は、github の fork をごらんください。

https://github.com/tohosaku/redmine/tree/rails7.0-rc1

alpha2 の時点で対応した WARNINGや変更

機械的な置き換えで対応できたもの

ActiveRecord::Base.default_timezone= is deprecated and will be removed in
Rails 7.1.
Use ActiveRecord.default_timezone= instead.

Rails 7.1 will return Content-Type header without modification. If you want just
the MIME type, please use #media_type instead.

Implicit conversion of User into String by
ActiveSupport::SafeBuffer is deprecated and will be removed in Rails 7.1. You must
explicitly cast it to a String.

このあたり、警告は出ますが対処法も親切に明示されているので機械的に置きかえました。

Rails公式でも「謎」とされていた仕様の修正

本家のプルリク "Deprecate ActiveSupport::TimeWithZone.name #41835" で

The reasoning for this is unclear and it can cause confusion for developers

とか

we don't know why this was added

とか言われている機能を Redmine も使っていました。上記のリンクの中では、XML出力に対応するための機能では?と言われています。Redmine ではチケットの様々な属性を数値、文字列、時間など型に応じて指定フォーマットで変換する部分に使われていました。

エラーを元に検索する中で日本語の記事にもなっているのを発見しました。
https://techracho.bpsinc.jp/hachi8833/2021_04_12/106832#1-2

Rails公式が使わなくなった gem を Redmine が使っている

Redmine が使用している actionpack-xml_parserroadie-rails という2つの gem は bundle update の時点で依存関係のエラーを起こしました。github 上の最新の master や fork に修正したものがあったので、Gemfile で github を参照するように修正しました。

手元で動かす分にはこれで良いでしょうが、Redmine公式では対応方法の検討が必要でしょう。

難物だったもの

https://github.com/rails/rails/pull/42945

今年8月にマージされたパフォーマンス向上のための修正で、通常のRailsアプリには影響なさそうですが、Redmineの API には影響甚大な変更でした。
Redmine では、xml または json 形式の API のリクエストを受け付けると、app/views/以下で .api.rsb という拡張子のテンプレートを検索して独自の処理をほどこし、json や xml を返します。issues.json が欲しい、とリクエストすると app/views/issues/index.api.rsb が呼びだされます。html で issues/ をリクエストすると、app/views/issues/index.html.erb が使われるのと同じような感じです。

ここで、「json、xml 共通で .api という拡張子へのリクエストとみなす」という部分も「そのときのテンプレートとして .rsb というものを使用する」というのも Redmine独自の仕様で、Rails本体にモンキーパッチを当てることで実現しています。[2]

ところが、Rails公式では応答を返すときのテンプレート探索で、あらかじめ登録された mimetype を使ってより厳密にテンプレートを絞りこむようになってしまいました。この余計な候補を探さない、というのがパフォーマンス向上の手段だったようです。

「.api という拡張子を仮に mimetype に仕込む」とか色々考えたんですが、結局、モンキーパッチを当てる部分を増やすことで対応しました。

このプルリクがマージされる過程では、特に「困る人が出るかもしれない」とかいう議論もなく、Rails7.0の Changelog でも全く触れられていません。この部分では、Redmine というのはよほど独特なことをしているんだな、と思いました。

対応の中で得たコードリーディングの小技

上記のテンプレート探索関連の修正対応では、Redmine というよりも Rails本体がどう動くのか、というのを追いかけるのに時間がかかりました。
ここで役に立ったのが、rubyのデバッガでした。[3]

https://github.com/ruby/debug

デバッガは、通常は自分の書いたコードにdebuggerとか書きこむ形でブレークポイントを設定するものとして紹介されています。今回は、Railsの動きがあまりにも分からず、トレースに表示された Rails のコードのパス名をコピーし、デバッガのcli画面でbコマンドでブレイクポイントを設定して、処理の流れやその時点での変数を調査する、ということを繰り返しました。

しかし、これも Rails 起動のたびに毎回ブレークポイントを設定するのが非常に面倒で、何か良い方法がないか、と ruby/debugドキュメント を読み直してみました。すると

You can specify the initial script with rdbg -x initial_script (like gdb's -x option).

という記述がありました。つまり

rdbrc.txt
# b /usr/local/bundle/gems/actionpack-7.0.0.alpha2/lib/action_controller/metal/mime_responds.rb:276
b /usr/local/bundle/gems/actionview-7.0.0.alpha2/lib/action_view/rendering.rb:129

./rdbrc.txtの中にブレークポイントのファイル名、行数を書いておいて、

rdbg -x ./rdbgrc.txt -c -- rails server

とすると、rails 起動時点ですでにブレークポイントが設定されている!!しかも、ファイルの中ではコメントアウトもできる。これは公式に何の記述もありませんが[4]、できたら良いなと思いつつやってみて上手く行ったときは感動しました。

これで、ブレークポイントの条件を細かく変えるときも前の条件をコメントの形で残して後から比較できるし、コマンドファイルにコメントで「調べたいこと」や「調査の結果」「日時」とかをどんどん追加していけば、それが一種の航海日誌(ログ)になるわけです。

入門記事などで、コードに挿入する感じのデバッガの使い方ばかりを読んでいた身としては、「こういう使い方誰かに教えてほしかった」という気分です。

おまけ

最初の方で、「とある理由で rails7 alpha2 を……」と書きましたが、なぜこんなことをしようとしたか、について一言。
Rails7 と並んで、おそらく今後の Rails開発に大きな影響を与えるであろう Hotwire。そのプルリクの中で、気になる機能がありました[5]

https://github.com/hotwired/turbo/pull/398

Hotwire の一部である Turbo Frames の機能なんですが、簡単に言うと、ページの間の移動のような url の変更をともなうブラウザのナビゲーションであっても、ページ全体をリライトすることなく、一部のみを再描画することが可能ということ。

つまり、javascriptをいろいろ書いたりすることなしにRedmine のチケット一覧で検索したり、ページを移動したりする場合でもチケット一覧だけが書き換えられてリロードを発生させずに済む。というわけです。RedmineはどうもUIが、とか言われずにすむかもしれない!! これを試してみたかった。ただ、その後、色々忙しくなってこっちは全く手を付けていないのでした。

脚注
  1. 日本時間ですが ↩︎

  2. Redmineのforumでも「.rsbって何やねん」というツッコミを見たことがあります。 ↩︎

  3. 9月の RubyKaigiの発表で存在を知りました ↩︎

  4. "like gdb's -x option" とあるので、慣れた人には常識なんでしょうね、コレ。 ↩︎

  5. Turbo 7.1.0 でマージされリリースされています ↩︎

Discussion