Ruby Freaks Lounge

第38回Redmineプラグイン開発(3)

これまでのおさらい

前回第36回⁠、前々回第34回では実際の開発を通してRedmineのプラグインについて様々なことを解説してきました。

今回は開発からは離れてこれまで紹介できなかった内容などを解説していきます。

落ち穂拾い

これまで紹介していなかった中で重要な項目をいくつかピックアップして解説していきます。今回紹介するのは以下の3つです。

  1. 国際化(多言語対応)
  2. Routes
  3. フック

国際化(多言語対応)

Redmineは標準で様々な言語に対応していますが、プラグインも同様に対応させる場合にはメッセージを言語ファイルに記述する必要があります。言語ファイルは PLUGIN_ROOT/config/locales ディレクトリ以下に言語毎に作成します。ファイルはYAML形式で記述し、日本語の場合は ja.yml 、英語の場合は en.yml と言う名前になります。そのほかの言語がどんなファイル名になるかはREDMINE_ROOT/config/localesディレクトリ以下を参照してください。

ではContinuousDeploymentプラグインの一部を国際化してみましょう。まずは言語ファイルです。

config/locales/en.yml
en:
  button_exec_deploy: Deploy!
  text_confirm_deploy: Are you sure you want to deploy?
config/locales/ja.yml
ja:
  button_exec_deploy: デプロイ!
  text_confirm_deploy: デプロイします。よろしいですか?

アプリ側からメッセージを読み込むには ⁠l⁠⁠ というメソッドの引数に言語ファイルで定義したラベルを指定します。

app/views/deployments/index.html.erb
<%= button_to l(:button_exec_deploy), { :action => "deploy", :project_id => params[:project_id] }, :confirm => l(:text_confirm_deploy) %>

若干の手間はかかりますが、簡単ですね。公開するプラグインを作成する場合、国際化は必ずやっておいた方が良いでしょう。

Routes

これまでいくつかコントローラを作成してきましたが、Routesには追加していないため、URLが /deployments?project_id=ruby-freaks のように見栄えが悪いものになっていました。

Redmineでは PLUGIN_ROOT/config/routes.rb が起動時に読み込まれます。このroutes.rb内でRoutes.drawメソッドを呼び出すことでプラグインのコントローラをRoutesに追加することができます。

ContinuousDeploymentプラグインで試してみましょう。

config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.with_options :controller => 'deployments' do |deployments|
    deployments.connect 'projects/:project_id/deployments', :action => 'index'
    deployments.connect 'projects/:project_id/deployments/:id', :action => 'show', :id => /\d+/
    deployments.connect 'projects/:project_id/deployments/:action/:id'
  end
end

これで再起動すると、デプロイ画面のURLが /deployments?project_id=ruby-freaks から /projects/ruby-freaks/deployments に変わります。URLが変わることでいかにもプラグインな感じがなくなり、ビルトインの機能らしく見えるようになりました。

Routesの詳細についてはRedmineではなくRailsのドキュメントをご参照ください。また、どのようなURLにするかの参考には REDMINE_ROOT/config/routes.rb を参照する事をオススメします。

また、ServerStatusプラグインの例も示しておきます。プロジェクトに関連しないプラグインの場合はほとんどの場合、resource(s)メソッドの引数にコントローラを指定するだけで問題ないはずです。

config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.resources :servers
end

フック

これまでとはちょっと毛色が変わる話になりますが、Redmineには多数のフックポイントが用意されており、それを利用することでRedmine本体に手を加えることなくビルトインの機能や画面を拡張することが可能です。どのようなフックがあるかは公式サイトのWikiに記載されています。ただし、これは若干古いことに注意してください。最新のリストを確認したい場合はREDMINE_ROOTから以下のコマンドを実行します。

rake redmine:plugins:hook_list

今回はServerStatusプラグインの拡張として、チケットのコメント欄に「stopped #<server_name>」と言った文字列があった場合に該当のサーバの監視状態を停止に変更する、と言う機能をフックを利用して実装してみます。利用するのはチケットの更新後に呼ばれる controller_issues_edit_after_save と言うフックです。実際にコードを見た方が想像が沸きやすいと思いますので、まずはコードをご覧ください。ファイルはPLUGIN_ROOTのlib 以下にissue_hook.rbと言う名前で保存します。

lib/issue_hooks.rb

class IssueHook < Redmine::Hook::Listener # ①
  def controller_issues_edit_after_save(context) # ②
    journal = context[:journal] # ③
    return if journal.nil? || !journal.new_record?
    if journal.notes =~ /stopped\s+#([^\s]+)/ # ④
      if server = Server.find_by_name($1)
        server.update_attributes!(:status => Server::Status::STOPPED)
      end
    end
  end
end

まずフックは Redmine::Hook::Listener と言うクラスを継承する必要があります※1⁠。この継承したクラスにフックの名前のメソッドを実装します⁠。引数の context にはフックした場所に応じて、チケットなどの必要になりそうなオブジェクトがハッシュで格納されています。実際には、Redmineからは以下のようなコードでフックがコールされています。最後のハッシュがそのままcontextになるわけです。

call_hook(:controller_issues_edit_after_save, { :params => params, :issue => @issue, :time_entry => @time_entry, :journal => journal})

ここまで分かれば後は簡単です。context からチケットのコメントの実体となる journalオブジェクトを取得し⁠、そのコメント内に「stopped #<server_name>」と言う文字列があればサーバのステータスを変更します⁠。

作成したフックは init.rb 内でrequireすれば有効になります。

init.rb
require 'issue_hooks'

なぜrequireするだけでよいのか不思議に思われる方もいるかもしれません。 Redmineではフックへの登録はRedmine::Hook::Listener#inheritedメソッドで行われており、クラスを継承したタイミングで有効になるように実装されています。inheritedメソッドの上手な活用例ですね。

フックは実装こそシンプルですが、非常に強力な機能です。作成したプラグインをフックでビルトインの機能と連動させればより便利なプラグインを作成することが可能です。

社内用プラグインのススメ

さて、最後に開発の話題からはちょっと離れて、社内用プラグインのススメ、と言うお話をしたいと思います。と言いつつ前置きとしてまずはExcelの話から。

ExcelとBTS

Excelはもう古いと言われているしBTSに興味はあるが、まだ実際の業務ではExcelを使用している、と言う方は数多くいらっしゃるのではないでしょうか。

Excelには不便な点が多々ありますが、業務をベースに必要な項目やステータスなどを一から構築できる柔軟性と、デスクトップアプリである事による操作性の良さや手軽さもあり、未だに多くのプロジェクトで使われているのが実情だと思います。特に、ある程度厳密な管理を要求される案件では業務フローがBTSに合わないことも多く、その点が導入にあたっての大きな障害になる事が多いようです。

これに対してTracとRedmineの最近のバージョンではチケットのワークフローをカスタマイズできるようになるなど、業務に合わせてシステムを変更することができるようになってきています。ただし、それでも現実には既存のシステムとの連携などで運用でカバーしなければならない業務、と言うのは必ず出てくることでしょう。

システムと業務をつなぐプラグイン

そうした状況において、私はプラグインというのはシステムと業務を繋げる橋渡しのような位置付けを担えるのではないか、と考えています。これまで見てきたように、Redmineのプラグインは非常に自由度が高く、多数用意されているフックによりコアな部分に手を加えることも容易です。開発もRailsのアプリを作るような感覚で実装していくことができるため、それほど学習コストや実装コストもかからないことでしょう。

Redmineを導入したいが今のプロジェクトとはここが合わない…などと悩まれている方は、⁠ないものは作ってしまえ」の精神で、ぜひ社内向けのプラグイン開発を検討してみてはいかがでしょうか。

おわりに

さて、3回に渡ってRedmineのプラグイン開発について解説してきました。

Redmineはプラグインなども含め、上手に運用することができれば、今までのどのプロジェクト管理ツールよりも快適に運用することができるツールだと思います。

本記事がRedmineによって円滑なプロジェクト運営を目指す皆さんへの一助になれば幸いです。

おすすめ記事

記事・ニュース一覧