Redmine プラグインチュートリアル for V4.x系


※ 本記事は [Redmine Advent Calendar 2020] (https://adventar.org/calendars/5392)の 21 日目の記事です。

## 初めに
Redmineは、豊富なプラグインによる機能拡張が魅力ですが、ほしい機能のプラグインがないこともあります。そんなときはプラグインの自作に挑戦してみたくなりますよね。
でも、前知識無しでのプラグイン作成は、RubyOnRailsやJavaScriptなどなど、さまざまな知識が必要で、なかなかハードルが高いです。
とりわけ困るのが、Webで見つかるプラグイン作成の入門情報が結構古いものが多いということです。
以下は、代表的なプラグイン作成の入門解説サイトです。

[プラグイン チュートリアル - Redmineガイド](http://guide.redmine.jp/Plugin_Tutorial/)
最終更新：2014/08/17
対応バージョン： Redmine 2.x

[プラグイン開発ガイド - r-labs](https://www.r-labs.org/projects/r-labs/wiki/%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E9%96%8B%E7%99%BA%E3%82%AC%E3%82%A4%E3%83%89)
最終更新： 2013/03/09
対応バージョン： Redmine 2.1〜2.3

[第34回 Redmineプラグイン開発（1）：Ruby Freaks Lounge｜gihyo.jp … 技術評論社](https://gihyo.jp/dev/serial/01/ruby/0034)
最終更新： 2010/03/09
対応バージョン： Redmine 0.9.3

Redmineのバージョンによって、各種コマンドや設定方法がかなり変わっているため、上記のサイトのそのままの手順では、うまく行かないことが多いです。

今回は、[プラグイン チュートリアル - Redmineガイド](http://guide.redmine.jp/Plugin_Tutorial/) について、Redmine バージョン 4.1.1 に対応した差分と、ちょっとした補足を解説したいと思います。

### 確認環境

* Ruby 2.6.6
* Rails 5.2.4.4
* Redmine 4.1.1
* 作成したプラグイン https://github.com/rbtgr/redmine_plugin_tutorial_polls

## プラグインチュートリアル 英語版(原文)について

先述の通り、[プラグイン チュートリアル - Redmineガイド](http://guide.redmine.jp/Plugin_Tutorial/) は、最終更新：2014/08/17 、対応バージョン： Redmine 2.x となっています。ですが、原文 [Plugin Tutorial - Redmine](https://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial) は、最終更新：2020/04/06 対応バージョン： Redmine 4.1.1 となっています。基本的にこちらと見比べながら進めれば良さげです。

以下、各章での、差分のある箇所について記載します。

### 新しいプラグインの作成

#### プラグインの新規作成コマンド

```bash
# Redmine 2.x
ruby script/rails generate redmine_plugin <plugin_name>
ruby script/rails generate redmine_plugin Polls
# Redmine 3.x ~
bundle exec rails generate redmine_plugin <plugin_name>
bundle exec rails generate redmine_plugin Polls
```

### モデルの作成


#### init.rb
生成されるファイルの内容に変化があります。

```rb:plugins/polls/init.rb
# Redmine 2.x
Redmine::Plugin.register :polls do
  name 'Polls plugin'
  author 'John Smith'
  description 'A plugin for managing polls'
  version '0.0.1'
end

# Redmine 3.x ~
Redmine::Plugin.register :polls do
  name 'Polls plugin'
  author 'Author name'
  description 'This is a plugin for Redmine'
  version '0.0.1'
  url 'http://example.com/path/to/plugin'
  author_url 'http://example.com/about'
end
```

#### モデル作成コマンド
```bash
# Redmine 2.x
ruby script/rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]

ruby script/rails generate redmine_plugin_model polls poll question:string yes:integer no:integer

# Redmine 3.x ~
bundle exec rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]

bundle exec rails generate redmine_plugin_model polls poll question:string yes:integer no:integer

# 生成されるマイグレーションファイ名も変わっています
# Redmine 2.x   : 001_create_polls.rb
# Redmine 3.x ~ : xxxxxxxxxxxx_create_polls.rb
```

#### マイグレーション実行コマンド
```bash
# Redmine 2.x
rake redmine:plugins:migrate

# Redmine 3.x ~
bundle exec rake redmine:plugins:migrate
```

#### Rails Console 実行コマンド

```bash
# Redmine 2.x
ruby script/rails console

# Redmine 3.x ~
bundle exec rails console
```


### コントローラの作成

#### コントローラ作成コマンド
```bash
# Redmine 2.x
ruby script/rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]

ruby script/rails generate redmine_plugin_controller Polls polls index vote

# Redmine 3.x ~
bundle exec rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]

bundle exec rails generate redmine_plugin_controller Polls polls index vote
```

#### polls_controller.rb
```rb:plugins/polls/app/controllers/polls_controller.rb
class PollsController < ApplicationController
  # Redmine 3.x ~ では 'unloadable' は不要。
  # unloadable

  def index
    @polls = Poll.all
  end

  def vote
    poll = Poll.find(params[:id])
    poll.vote(params[:answer])
    if poll.save
      flash[:notice] = 'Vote saved.'
    end
# Redmine 2.x
      redirect_to :action => 'index'
# Redmine 3.x ~
      redirect_to polls_path(project_id: params[:project_id])
  end
end
```

#### plugins/polls/app/views/polls/index.html.erb

```erb:plugins/polls/app/views/polls/index.html.erb
<h2>Polls</h2>

<% @polls.each do |poll| %>
  <p>
  <%= poll.question %>?

<!-- Redmine 2.x -->
  <%= link_to 'Yes', { :action => 'vote', :id => poll[:id], :answer => 'yes' }, :method => :post %> (<%= poll.yes %>) /
  <%= link_to 'No', { :action => 'vote', :id => poll[:id], :answer => 'no' }, :method => :post %> (<%= poll.no %>)

<!-- Redmine 3.x ~ -->
  <%= link_to 'Yes', { action: 'vote', id: poll[:id], answer: 'yes', project_id: @project }, method: :post %> <%= poll.yes %> /
  <%= link_to 'No', { action: 'vote', id: poll[:id], answer: 'no', project_id: @project }, method: :post %> <%= poll.no %>

  </p>
<% end %>
```

### アプリケーションメニューの拡張

`:application_menu` でメニューが追加される画面が変わっているようです。

* Redmine 2.x
http://localhost:3000

* Redmine 3.x ~
http://localhost:3000/projects

### プロジェクトメニューの拡張

#### PollsController
```rb:plugins/polls/app/controllers/polls_controller.rb
def index
  @project = Project.find(params[:project_id])
# Redmine 2.x
  @polls = Poll.find(:all) # @project.polls

# Redmine 3.x ~
  @polls = Poll.all # @project.polls
end
```

### 権限の追加
#### PollsControllerの変更

```rb:plugins/polls/app/controllers/polls_controller.rb
# Redmine 2.x
  unloadable
  before_filter :find_project, :authorize, :only => :index

# Redmine 3.x ~
  before_action :find_project, :authorize, only: :index
```

### プラグインの設定画面の追加

#### プラグインのSettingハッシュ

* Redmine 2.x
このプラグインの場合、 Setting.plugin_redmine_polls でハッシュにアクセスできます。

* Redmine 3.x ~
このプラグインの場合、 Setting.plugin_polls でハッシュにアクセスできます。

以降同様に、`Setting.plugin_redmine_polls` => `Setting.plugin_polls `

#### プラグイン設定画面
明記はされていないですが、
plugins/polls/app/views/settings/_poll_settings.erb
を作成して、以下の内容を記載する。(`<td>`が抜けてます)

```erb:plugins/polls/app/views/settings/_poll_settings.erb
<table>
  <tbody>
    <tr>
      <th>Notification Default Address</th>
      <td><input type="text" id="settings_notification_default"
                 value="<%= settings['notification_default'] %>"
           name="settings[notification_default]" >
      </td>
    </tr>
  </tbody>
</table>

```

### テストの実行

#### polls_controller_test.rb の実行方法

```bash
# Redmine 2.x
ruby plugins\polls\test\functionals\polls_controller_test.rb

# Redmine 3.x ~ (原文)
RAILS_ENV=test bundle exec rake plugins/polls/test/functionals/polls_controller_test.rb

# 原文では functionals となっているが、生成されるファイルはfunctional
# あと、rake test TEST=ファイル名としないとならないはず・・・
RAILS_ENV=test bundle exec rake test TEST=plugins/polls/test/functional/polls_controller_test.rb
```

## 終わりに

ここまでで一通りのチュートリアルの内容は一通り完了です。
ですが、これだけでは少し物足りないですよね。
ということで後編、[Redmine プラグインチュートリアル for V4.x系 国際化対応とテスト追加](https://qiita.com/rbtgr/items/4fa8656bd0b8a8a18a96) に続きます。