詳しくわかってない人が詳しくわかってないなりに、複数のプラグインを導入する場合のプラグイン開発において、ハマったポイントとかを交え、勘所としてまとめておく。
developmentモードで操作しない?
公開されているプラグインには、「productionモードでしか動作しない」と明記されているものも少なくない。
この辺りは色々とググると出てくるので省略するけど、結論として、自作するプラグイン(のコントローラ)にはunloadableを書いておくようにするのがよい。
ついでにいうと、プラグインで既存のコントローラを拡張する場合にも書いておいたほうがいい…のかな…(下記)
module FooControllerPatch #:nodoc: def self.included(base) base.extend(ClassMethods) base.class_eval do unloadable end end (snip) end
ただ、「productionモードでしか動作しない」ことを明記している他のプラグインが、上記原因だけで説明できるものなのかといえば、そうでもないっぽい。う〜ん…。実際ためしてみると動いてるようなものもあるし。
新規にメソッドを追加して呼び出したらundefined methodエラー
自作のプラグイン(foo)で、CustomFieldsHelperを拡張して、既存のメソッドCustomFieldsHelper#show_value(custom_value)の置き換えと、新しいメソッドの追加をした場合に、undefined methodエラーが起こるようになった。
plugins/foo/lib/foo_custom_fields_helper_patch.rb
module FooCustomFieldsHelperPatch #:nodoc: def self.included(base) base.send(:include, InstanceMethods) base.class_eval do unloadable alias_method_chain :show_value, :foo end end module InstanceMethods def show_value_with_foo(custom_value) hoge(custom_value) end def hoge(custom_value) (snip) end end end
この時に、新しいチケットを登録すると、undefined methodエラーが起きる。ちなみに、CustomFieldsHelper#show_valueは、カスタムフィールドの登録値を画面表示用に変換して返すメソッドである。
新しいチケット登録後はチケットの詳細画面が表示されるので、問題があれば、チケット一覧からチケット詳細画面に遷移した場合にも起きるはずだが、エラーは起きず、正しく表示された。
結局、エラーが起こっている箇所は、メール送信の時のviewのレンダリングだった。Redmineで使っているメール送信のためのモデルは以下のように書かれている。
app/models/mailer.rb:18
class Mailer < ActionMailer::Base layout 'mailer' helper :application helper :issues helper :custom_fields
ここでCustomFieldsHelperが使われるのがミソかなと。現象から逆算すると、プラグインでCustomFieldsHelperが拡張される前に、Mailerがロードされており、その時点でMailerは拡張前のCustomFieldsHelperをインクルードしている、ってことなのかなぁ。でもなんか腑に落ちない。
適切な修正方法は不明…。エラーでチケットが登録できないのは致命的なので、とりあえずはrescueでエラーを拾っておくようにして回避した。
プラグインのロード順によってバリデーションの機嫌が異なる
カスタムフィールドの書式を追加するプラグインを作っていて、既存のプラグインを真似て作っても、常に「書式は一覧にありません。」エラーが出てしまい、カスタムフィールドの登録ができなくなってしまった問題も起きた。
このエラーメッセージは、カスタムフィールドクラスで定義されている以下のバリデーションに違反してしまっているため。
app/models/custom_field.rb:28
validates_inclusion_of :field_format, :in => Redmine::CustomFieldFormat.available_formats
この問題で厄介だったのは、カスタムフィールドの書式を追加するプラグインを複数作った場合、きちんと登録できるものと登録できないものが存在したこと。print debugすると、Redmine::CustomFieldFormat.available_formatsには新しく作った書式が含まれていた。
他のプラグインとの競合が原因だった。他のプラグインをアンインストールしたり、自作のプラグイン以外のプラグイン名に"z_"みたいなサフィックスをつけて、自作のプラグインよりも後にロードされるようにしたところ、すべてのカスタムフィールドの書式で問題なく登録できたので。
あるいは自作プラグインのinit.rbで無理やりCustomFieldクラスをアンロードして、再ロードする、みたいなことをやっても問題なく登録できるようになった。
つまり、他のプラグインがCustomFieldクラスやCustomFieldFormatクラスをロードしたタイミングで、書式(Redmine::CustomFieldFormat.available_formats)が確定してしまっているみたい。
alias_method_chainで無限ループエラー
これは原因が簡単で複数のプラグインで同じメソッドをalias_method_chainで置き換えていた場合に、機能名が衝突していたというもの。
プラグインfooのFooPatchモジュールとプラグインbarのBarPatchモジュールで、同じクラスに対して
alias_method_chain :do_something, :xxx
みたいに書いてしまうと、簡単にこの問題は起こるので注意。
拡張モジュールの名前が衝突すると一つしか有効にならない
既存のクラス(Foo)を拡張する場合、大体以下の様な記述をプラグインのinit.rbに書く。
Rails.configuration.to_prepare do unless Foo.included_modules.include? FooPatch Foo.send(:include, FooPatch) end end
ここで複数のプラグインでFooPatchという同じモジュール名を使っていた場合はどうなるか。先にロードされたプラグインのパッチのみが有効になる(予想通り?)。
なので、パッチの名前は衝突しないように工夫が必要。実はこれはViewの名前にも同じことが言える。Viewの場合は、最後にロードされたプラグインのViewが優先される。
まとめ
- 迷ったらデバッグメッセージを埋め込んで確認。
- なんか原因が分からなかったら、他のプラグインをアンインストール(pluginsディレクトリから移動して再起動すればOK)してみて、同様の問題が起こるかどうかも確かめてみる。
- alias_method_chainの機能名はユニークにする。
- viewのファイル名はユニークにする(既存のviewを上書きする場合は除く)
- 各種拡張用モジュールの名前はユニークにする。
ここでいう「ユニーク」は、これから他のプラグインを入れたりした場合にも、絶対に被らないようなもの。てっとり早いのは、やはり、プラグイン名(やその省略形)を含めることだと思う。実際、公開されているプラグインはそうなっているものがほとんど。
Javaのクラスローダ周りもそうだけど、問題が表面的なところでなくて、少し深いところになると、原因調査とか解決が難しいなぁ…。