torutkのブログ

ソフトウェア・エンジニアのブログ

Redmineプラグイン作成でHookを使ってみる(続)

昨日(Redmineプラグイン作成でHookを使ってみる - torutkのブログ)は、Redmineが用意したフックの機構を使って、チケット一覧表示画面の下に任意の表示を差し込んでみました。ただし、表示は固定文字列のみでした。Ruby on Railsでは、拡張子.erbのファイルでHTMLのテンプレートを用意し実行時に決まる値を反映することなどが可能です。昨日は固定文字列だけ書いて、動的な記述を課題として積み残していたerbファイルの記述方法を少し追ってみます。

プラグインのapp/views/issues/_issuesIndexBottom.html.erb をいじる

Redmine本体には、拡張子が.html.erbとなっている多数のファイルが存在します。これらを参考とします。
チケット一覧表示では、/app/views/issues/index.html.erbが用意されています。この中身を参照しながら、動的な表示の方法を探ってみました。

このindex.html.erbを見ていると、@で始まる変数の参照がいくつか見られます。

@project
@query
@issues
@issue_pages
@issue_count
@sort_criteria

Ruby on Railsでは、HTMLのテンプレートファイル名は、<アクション名>.html.erbとする規約のようです。Redmineのチケット一覧表示はアクションがindexなので、index.html.erbとなっていると思われます。テンプレートはふつうにHTMLの記述が可能ですが、実行時に決まる動的な値は、アクション(コントローラ)のインスタンス変数に値を詰め、テンプレートからインスタンス変数を参照して値を適用します。そのインスタンス変数が上述の@で始まる変数です。

そこで、どんなインスタンス変数が存在するのかを確認するため、/app/controller/issues_controller.rbをサラッと流し読みしてみます。

class IssuesController < ApplicationController
  :(略)
  def index
    :
        @issue_count = @query.issue_count
        @issue_pages = Paginator.new @issue_count, @limit, params['page']
        @issues = @query.issues(:include => [:assigned_to, :tracker, :priority, :category, :fixed_version], ..
        @issue_count_by_group = @query.issue_count_by_group

といったインスタンス変数が定義されています。
Rubyでは、変数宣言はなく、先頭が「@」で始まる変数をインスタンス変数として扱います。

では、昨日作成したView hooksとして差し込むテンプレートを少し修正してみます。

<h3>HOOKED</h3>
<p>issue count is <%= @issue_count %></p>

修正を保存し、チケット一覧表示ページにアクセスします。development モードだからか、WEBrickの再起動をしなくても反映されました。

すると

HOOKED
issue count is 26

と表示されました。

値ではなく、オブジェクトへのメソッド呼び出しの結果を表示したい場合も、

<h3>HOOKED</h3>
<p>issues displayed are <%= @issues.length %></p>

とメソッド呼び出しの記述が可能です。(lengthは、配列の要素数。ちなみにこの@issues.lengthは表示している件数が返りました)

フック(View hooks)とは

Redmine本体であらかじめ拡張用に用意された表示部位がView hooksで、チケット一覧表示の場合は、一覧表示の下にview_issues_index_bottomの名前でフックが設けられています。

フックにコールバックをしかけておくと、表示に際してコールバックが呼ばれ、そこに追加の表示を入れ込むことができます。コールバックは、Redmine::Hook::ViewListenerクラスを継承したクラスとして定義し、init.rbにrequire_dependency文で定義を読み込ませます。

コールバックの処理は、表示系の場合 render_on ヘルパーでフック名と表示テンプレート(パーシャル?)を指定しておくと、そのテンプレートによって生成された表示がフックの部位に差し込まれます。

表示テンプレートで参照できるのは、コントローラのインスタンス変数となるので、コントローラが想定している範囲のデータを使って動的な表示が実現できます。逆に、それ以上のことをやりたいとしたら、フックではなく別の手段を使うことになるのかと思います。

おまけ

コントローラフック

チケット表示関係のコントローラフックを探してみると(grep -r call_hook * | grep control)、次の名前が関係していそうです。

  • :controller_issues_new_before_save
  • :controller_issues_new_after_save
  • :controller_issues_bulk_edit_before_save
  • :controller_issues_edit_before_save
  • :controller_issues_edit_after_save

チケット変更の前後で処理を差し込むことができそうです。
ただ、ここには一覧表示に差し込むフックはないようです。

モデルフック

Redmine全体で2つのようです。

  • model_changeset_scan_commit_for_issue_ids_pre_issue_update
  • model_project_copy_before_save
ヘルパーフック

Redmine全体で1つ?

  • helper_issues_show_detail_after_setting