プロジェクト

全般

プロフィール

Redmineプラグインの書き方の作法について

Takumi Endo さんが約11年前に追加

はじめまして、遠藤と申します。

公開されている色々なプラグインを参考に、Redmineのプラグインを開発しているのですが、既存helperを拡張する際の作法について、アドバイスいただけないでしょうか。

修正前のコード

CustomFieldsHelperにあるcustom_field_tagメソッドを拡張するために、lib/hoge/custom_fields_helper_patch.rbを作成しました。
大筋は下記のようなコードで、custom_field_tag_with_hogeメソッドの中で、aaaメソッドを呼んでいます。

require_dependency 'custom_fields_helper'

module Hoge
  module CustomFieldshelperPatch
    def self.included(base)
      base.send(:include, InstanceMethods)
      base.class_eval do
        alias_method_chain :custom_field_tag, :hoge
      end
    end

    module InstanceMethods
      def custom_field_tag_with_hoge(name, custom_value)
        s = custom_field_tag_without_hoge(name, custom_value)
        s << aaa
        s
      end

      def aaa
        "aaa" 
      end
    end
  end
end

init.rbに以下を記述して、作成したpatchを適用します。

Rails.configuration.to_prepare do
  unless CustomFieldsHelper.included_modules.include?(Hoge::CustomFieldsHelperPatch)
    CustomFieldsHelper.send(:include, Hoge::CustomFieldsHelperPatch)
  end
end

症状

その後Redmineを再起動して、チケットの新規登録画面(CustomFieldsHelper#custom_field_tagが呼ばれる画面)を開くと、undefined methodエラーがでて、500エラーになってしまいます。

ActionView::Template::Error (undefined method `aaa' for #<#<Class:0xb032090>:0xb078518>):

実施した修正

Takanoさんの作られたBannerプラグインを参考にいろいろ試行錯誤してみたところ、以下の修正を加えると、エラーが解消されました。

1. init.rb に以下を追加

require 'hoge/custom_fields_helper_patch'

2. lib/hoge/custom_fields_helper_patch.rb の最後に以下を追加

CustomFieldsHelper.send(:include, Hoge::CustomFieldsHelperPatch)

疑問点

  • 修正前のコードでなぜundefined methodエラーが出てしまうのか
  • patchやinit.rbの記述の作法として、どのように記述するのが一番無難なのか(プラグインにより書き方は様々でしたが、修正前と同じ形式で記述しているものが多かったです)

といった点に関して、色々なプラグインを見ても自己解決できなかったので、アドバイスいただけると助かります。

なお、Redmineのバージョンは開発最新版(2.2.2.devel)です。

長文失礼しました。よろしくお願いいたします。


返答 (2)

RE: Redmineプラグインの書き方の作法について - Akiko Takano さんが約11年前に追加

遠藤さん、こんにちは。遅くなってしまい申し訳ありません...。

私は見よう見まねで&チュートリアルを頼りに、少しずつ書いていった感じなので、的確なお答えになるかどうか分かりませんが...。
(こちらのメンバーの方や、rubyの識者さんから訂正やご指摘があるかと思いますが、ひとまず自分の理解の確認用にお返事させていただきます)

patchやinit.rbの記述の作法として、どのように記述するのが一番無難なのか(プラグインにより書き方は様々でしたが、修正前と同じ形式で記述しているものが多かったです)

Redmineのプラグインというよりは、Railsのプラグインのお作法という方が良いのかと思います。

  • script/generate plugin プラグイン名 でプラグインディレクトリを作成。
  • app/vendor/plugins/プラグイン名/lib/#{your_plugin_name}.rb”に自分のコードを書く。
  • app/vendor/plugins/プラグイン名/init.rb で先ほど書いたコードをrequireする。

プラグイン専用のModel/View/Controllerクラスについては、init.rbにrequire (require_dependency)する必要はありません。

クラスを拡張するモジュール(パッチ)や、ホックを利用するプラグインの場合は、lib/ 以下のファイルをrequireする必要があります。

遠藤さんのプラグインは、既存のRedmineのヘルパーを拡張するコードなので、init.rb で明示的にrequireしないといけない形になります。

CustomFieldsHelper.send(:include, Hoge::CustomFieldsHelperPatch)

こちらはおまじないです。

Redmine本来の、CustomFieldsHelperクラスに、直接CustomFieldsHelperPatchをincludeする代わりに、プラグインの方から上記のように指定することで、モジュールで定義しているメソッドを追加することが出来るようになります。

ということで、お話を整理してみます。

修正前のコードでなぜundefined methodエラーが出てしまうのか

init.rbでのrequire (Railsの場合はrequire_dependencyのほうが多いみたい)が無かったのが原因かと思います。

プラグインにより書き方は様々でしたが、修正前と同じ形式で記述しているものが多かったです

基本的には、遠藤さんのように、Redmine本来のクラスを、後付けで拡張するパッチを各場合は、init.rb でrequireするのがお作法になります。
(独自のMVCのクラスをapp/以下に定義する場合は、requireしなくて大丈夫です)

init.rbに取り込まないといけないパッチのファイルがたくさんある場合などは、インデックスに相当するファイルだけをrequireさせておき、あとはインデックスのファイルに実際にrequireするファイルをたくさん列挙するという方法もあります。

こちらのソースがとても参考になるかと思います。

CustomFieldsHelper.send(:include, Hoge::CustomFieldsHelperPatch)

については、init.rb でも、パッチのコード自体でも大丈夫かと思います。

お返事を書いている中で、自分のソースについても、『あ、この書き方ダメだった』『このファイル要らない...』というのが色々出てきて、お恥ずかしい限りです。

間違いのご指摘も大歓迎です。

RE: Redmineプラグインの書き方の作法について - Takumi Endo さんが約11年前に追加

Takanoさん、こんにちは。
お忙しい中ご丁寧に回答していただき、本当にありがとうございます!

基本的には、遠藤さんのように、Redmine本来のクラスを、後付けで拡張するパッチを各場合は、init.rb でrequireするのがお作法になります。

上記の部分に関して、クラスを拡張するモジュール(パッチ)を適用したい場合に、init.rbの中で

CustomFieldsHelper.send(:include, Hoge::CustomFieldsHelperPatch)

が呼ばれていれば、requireが不要なものだと思ってました(ホックだけは、requireしていたのですが)。

この辺りのおまじない(メタプログラミング?)の理解が曖昧なままコードを書いてしまっていたので、いただいたアドバイスをよく吟味して、理解を深めていこうと思います。

    (1-2/2)