これまでのおさらい
前回(第36回),前々回(第34回)では実際の開発を通してRedmineのプラグインについて様々なことを解説してきました。
今回は開発からは離れてこれまで紹介できなかった内容などを解説していきます。
落ち穂拾い
これまで紹介していなかった中で重要な項目をいくつかピックアップして解説していきます。今回紹介するのは以下の3つです。
- 国際化(多言語対応)
- Routes
- フック
国際化(多言語対応)
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メソッドの上手な活用例ですね。
フックは実装こそシンプルですが,非常に強力な機能です。作成したプラグインをフックでビルトインの機能と連動させればより便利なプラグインを作成することが可能です。
- ※1
- ビューの場合は Redmine::Hook::ViewListener クラスを継承します。ビューのフックについてはRedmine自体に手を入れずに見た目を変更する方法で詳しく解説されています。

