LoginSignup
4

More than 5 years have passed since last update.

posted at

updated at

Redmine本体のクラスメソッドを変えるプラグインを作る

あまり推奨されるようなことではないけれど、Redmineの機能や表示を変えたい時に、本体を変更しなければ実現できないことがある。

これらの方法については、クラスメソッドではないメソッドを alias_method_chain を使って変更する方法が良く紹介されている。

ところが、まれにクラスメソッドや特異メソッドと呼ばれる、selfの付いたメソッドを変更したくなる時があり、意外とその方法がすぐには見つけられなかったので、今回書いておこうと思った。

ただ、この方法がRubyやRails的に最適なのかが分かっていないのが泣きどころ。バッドノウハウになることも覚悟している。

具体的にやりたいこと

Redmineのガントチャートにおいて、バージョンごとのチケットは開始日によってソートされる。今回やりたいのは、このソート順を開始日ではなくチケット名にすること。

というのも、要望を出してきたプロジェクトでは、チケットにプレフィックスを付けて工程を表していて、開始日でソートされると工程順が崩れるのでイヤだと。

このソート順は、Redmineを見ていくと、 redmine/lib/redmine/helpers/gantt.rbsort_issue_logic メソッドが担っていることが分かった。
このメソッドは、 self.sort_issue_logic と定義されていて、プラグインからの書き換え方法が分からなかったのが発端。

実現方法

プラグインの lib 以下に置いたファイルが読み込まれるように、プラグインの init.rb で読み込みを追加。

init.rb
Dir[File.expand_path('../lib/sort_subject_patch', __FILE__) << '/*.rb'].each do |file|
  require_dependency file
end

で、書き換えている場所。名前汚染を避けるため、moduleで囲ってる。
また、 define_singleton_method を使って、クラスメソッドを書き換えている。

lib/sort_subject_patch/gantt_patch.rb
module GanttSortSubject
  module GanttPatch
    # includedを使うためにActiveSupportのConcernをextend
    extend ActiveSupport::Concern

    # 書き換えている場所
    included do
      # プラグインの設定によって、オリジナルも呼べるように退避
      original = Redmine::Helpers::Gantt.method(:sort_issue_logic)

      # define_singleton_methodを使うと、クラスメソッドを置き換えられる
      define_singleton_method :sort_issue_logic do |issue|
        # プラグイン設定がONの場合は、名前でソートに変更
        if '1' == Setting.plugin_redmine_plugin_gantt_sort_subject['sort_subject_enabled'].to_s
          issue_subjects = []
          current_issue = issue
          begin
            issue_subjects.unshift(current_issue.subject)
            current_issue = current_issue.parent
          end while (current_issue)
          issue_subjects
        # プラグイン設定がOFFの場合は、退避しておいたオリジナルを呼ぶ
        else
          original.call(issue)
        end
      end
    end

  end
end

# オリジナルのGanttクラスに差し込み
ActionDispatch::Reloader.to_prepare do
  unless Redmine::Helpers::Gantt.included_modules.include? GanttSortSubject::GanttPatch
    Redmine::Helpers::Gantt.send(:include, GanttSortSubject::GanttPatch)
  end
end

まとめ

今回、本体のしかもクラスメソッドを書き換えてみて、プラグインで実現できる柔軟性に感心するとともに、こんなに気軽に書き換えられる怖さも感じた。

プラグインで外に出していて、かつ設定でオリジナルも呼び出せるようにしたとはいえ、バージョンアップによる変更や他のプラグインとの衝突時に不具合解析を追うのが難しそう。

どうしても実現したい時に、最小スコープでやるようにして、乱用しないように気をつける必要がある。

参考

今回作ったプラグイン - Gantt Sort Subject plugin

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4