Redmine プラグインで controller を load すると別のプラグインに影響がある

Module#prepend での本体のヘルパーを上書き

従来 Redmineプラグインで本体の動作を変えたい場合は alias_method_chain を使いましょうということになっていた。
Redmine 4 系から Rails 5.x ベースとなって alias_method_chain が廃止されてからは Module#prepend を使いましょうということになった。

prepend を使えば既存の動作を上書きすることができるようになる。

# 本体のモジュール
module ProjectsHelper
  def foo
    'foo'
  end
end

# プラグインで追加するパッチ
module ProjectsHelperPatch
  def foo
    super + 'bar'
  end
end

# プラグイン内でこれを実行
ProjectsHelper.prepend(ProjectsHelperPatch)

# 本体のコントローラ
class ProjectsController
  include ProjectsHelper
end

# ProjectsHelperPatch#foo が呼ばれる。 super は ProjectsHelper#foo を呼ぶ。
puts ProjectsController.new.foo # foobar

ロードする順番を変えてみる

順番を変えてみる。

# 本体のモジュール
module ProjectsHelper
  def foo
    'foo'
  end
end

# 本体のコントローラ(prepend よりも前にもってきた)
class ProjectsController
  include ProjectsHelper
end

# プラグインで追加するパッチ
module ProjectsHelperPatch
  def foo
    super + 'bar'
  end
end

# プラグイン内でこれを実行
ProjectsHelper.prepend(ProjectsHelperPatch)

# ProjectsHelperPatch#foo が呼ばれず、 ProjectsHelper#foo が直接呼ばれる
puts ProjectsController.new.foo # foo

ProjectsController の定義を prepend よりも先に移動した。
ProjectsController には ProjectsHelperPatch が差し込まれる前の ProjectsHelper を include していることになる。
結果 ProjectsHelperPatch#foo は呼び出されず ProjectsHelper#foo が直接呼び出されるので ProjectsHelperPatch によって動作を上書きできていない。

RedmineRedmine プラグインのロード順

Redmine プラグインRedmine 本体コードの app/ 以下よりも先にロードされる。(config/initializers/30-redmine.rb) なので、プラグインのなかで prepend すれば app/ 以下のコードを上書きできる。

プラグインプラグインのフォルダ名順にロードされる。

プラグインで Controller をロードすると後続で helper を prepend しても有効にならない

プラグインでこういうコードを書いたとする。

ProjectsController.prepend(ProjectsControllerPatch)  # (1)

別のプラグインで ProjectsController が include している ProjectsHelper を prepend していたとする。

ProjectsHelper.prepend(ProjectsHelperPatch) # (2)

(2) の prepend は (1) より先か後かで有効になったりならなかったりする。
(1) が先に実行された場合はこの時点で include される helper には (2) は prepend されていないので ProjectsController には (2) の動作が付加されない。

フォルダ名順にロードされるので、フォルダ名によって動いたり動かなかったりする。
(2) を開発していてロードされない原因が関係ない (1) のプラグインのせいだと気づくのは無理があるので非常にやっかい。

まとめ

Redmine プラグインで Controller をロードしてはいけない。
わたしです → https://github.com/suer/redmine_recent_project_accesses/commit/24c5d3939385d8e1ce966a750d12749b9d7902b9#diff-242d0b4ac43f99fbb7c9023c1aca7166R4