あまり推奨されるようなことではないけれど、Redmineの機能や表示を変えたい時に、本体を変更しなければ実現できないことがある。
これらの方法については、クラスメソッドではないメソッドを alias_method_chain
を使って変更する方法が良く紹介されている。
ところが、まれにクラスメソッドや特異メソッドと呼ばれる、selfの付いたメソッドを変更したくなる時があり、意外とその方法がすぐには見つけられなかったので、今回書いておこうと思った。
ただ、この方法がRubyやRails的に最適なのかが分かっていないのが泣きどころ。バッドノウハウになることも覚悟している。
具体的にやりたいこと
Redmineのガントチャートにおいて、バージョンごとのチケットは開始日によってソートされる。今回やりたいのは、このソート順を開始日ではなくチケット名にすること。
というのも、要望を出してきたプロジェクトでは、チケットにプレフィックスを付けて工程を表していて、開始日でソートされると工程順が崩れるのでイヤだと。
このソート順は、Redmineを見ていくと、 redmine/lib/redmine/helpers/gantt.rb
の sort_issue_logic
メソッドが担っていることが分かった。
このメソッドは、 self.sort_issue_logic
と定義されていて、プラグインからの書き換え方法が分からなかったのが発端。
実現方法
プラグインの lib 以下に置いたファイルが読み込まれるように、プラグインの init.rb で読み込みを追加。
Dir[File.expand_path('../lib/sort_subject_patch', __FILE__) << '/*.rb'].each do |file|
require_dependency file
end
で、書き換えている場所。名前汚染を避けるため、moduleで囲ってる。
また、 define_singleton_method
を使って、クラスメソッドを書き換えている。
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
まとめ
今回、本体のしかもクラスメソッドを書き換えてみて、プラグインで実現できる柔軟性に感心するとともに、こんなに気軽に書き換えられる怖さも感じた。
プラグインで外に出していて、かつ設定でオリジナルも呼び出せるようにしたとはいえ、バージョンアップによる変更や他のプラグインとの衝突時に不具合解析を追うのが難しそう。
どうしても実現したい時に、最小スコープでやるようにして、乱用しないように気をつける必要がある。