Redmine3.3でIssueExtensionsを動かす

# Redmine2からRedmine3にアップグレード
Redmine3ではIssueExtensionsプラグインが動作しないため試行錯誤してなんとか動くようになった。
なお、RubyもRaisも全くしらないので全て信用しないで頂きたい。

```
Environment:
  Redmine version                3.3.0.stable.15663
  Ruby version                   2.2.5-p319 (2016-04-26) [x86_64-linux]
  Rails version                  4.2.6
  Environment                    production
  Database adapter               Mysql2
```

# ベースとするIssueExtensions
http://www.r-labs.org/projects/issueext を使っていたが
https://github.com/planio-gmbh/edmine_issue_extensions にしてみる。

# 初期設定

## Redmine2からRedmine3へのマイグレーションでエラー
メッセージを残し忘れていたが以下のようなエラーだったかと…

```text
An error occurred while loading the routes definition of redmine_issue_extensions plugin (/var/lib/redmine-3.3/plugins/redmine_issue_extensions/config/routes.rb): You should not use the `match` method in your router without specifying an HTTP method.
If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.
If you want to expose your action to GET, use `get` in the router:
  Instead of: match "controller#action"
  Do: get "controller#action".
```
```diff:redmine_issue_extensions/config/routes.rb
*** 17,22 ****
  
  RedmineApp::Application.routes.draw do
    match 'issue_extensions/:action', :controller => 'issue_extensions', :via => [:get, :post]
!   match 'issue_extensions_settings/:action', :controller => 'issue_extensions_settings'
    match 'issue_relations/:action', :controller => 'issue_relations', :via => [:get, :post]
  end
--- 17,22 ----
  
  RedmineApp::Application.routes.draw do
    match 'issue_extensions/:action', :controller => 'issue_extensions', :via => [:get, :post]
!   match 'issue_extensions_settings/:action', :controller => 'issue_extensions_settings', :via => [:get, :post, :put, :patch]
    match 'issue_relations/:action', :controller => 'issue_relations', :via => [:get, :post]
  end
```

# プロジェクトの設定画面が開かない

```text
ActiveRecord::RecordNotFound (Couldn't find all IssueExtensionsStatusFlows with 'id': (first, {:conditions=>["project_id = ?", 2]}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/app/models/issue_extensions_status_flow.rb:21:in `find_or_create'
  plugins/redmine_issue_extensions/app/views/issue_extensions_settings/_show.html.erb:21:in `_plugins_redmine_issue_extensions_app_views_issue_extensions_settings__show_html_erb__2241146892683185007_70191963673200'
```
```diff:redmine_issue_extensions/app/models/issue_extensions_status_flow.rb
*** 18,24 ****
    belongs_to :project
  
    def self.find_or_create project_id, user_id
!     status_flow = IssueExtensionsStatusFlow.find :first, :conditions => ['project_id = ?', project_id]
      unless status_flow
        status_flow = IssueExtensionsStatusFlow.new
        status_flow.project_id = project_id
--- 18,24 ----
    belongs_to :project
  
    def self.find_or_create project_id, user_id
!     status_flow = IssueExtensionsStatusFlow.find_by :project_id => project_id
      unless status_flow
        status_flow = IssueExtensionsStatusFlow.new
        status_flow.project_id = project_id
```

```text
ActiveRecord::RecordNotFound (Couldn't find all IssueStatuses with 'id': (all, {:order=>"position"}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/app/views/issue_extensions_settings/_show.html.erb:22:in `_plugins_redmine_issue_extensions_app_views_issue_extensions_settings__show_html_erb___3067774532135620242_70290714364540'
```

```diff:redmine_issue_extensions/app/views/issue_extensions_settings/_show.html.erb  
*** 19,25 ****
  <div id="issue_extension_settings">
  <%
  @issue_extension_status_flow = IssueExtensionsStatusFlow.find_or_create(@project.id, User.current.id)
! @statuses = IssueStatus.find(:all, :order => 'position') %>
  <%= labelled_form_for :setting, @issue_extension_status_flow,
                               :url => {:controller => 'issue_extensions_settings',
                                 :action => 'update', :id => @project, :tab => 'issue_extensions',
--- 19,25 ----
  <div id="issue_extension_settings">
  <%
  @issue_extension_status_flow = IssueExtensionsStatusFlow.find_or_create(@project.id, User.current.id)
! @statuses = IssueStatus.order(:position) %>
  <%= labelled_form_for :setting, @issue_extension_status_flow,
                               :url => {:controller => 'issue_extensions_settings',
                                 :action => 'update', :id => @project, :tab => 'issue_extensions',
```

# IssueExtensionsの設定画面での更新でエラー

```text
ActiveModel::ForbiddenAttributesError (ActiveModel::ForbiddenAttributesError):
  plugins/redmine_issue_extensions/app/controllers/issue_extensions_settings_controller.rb:25:in `update'
```

```diff:redmine_issue_extensions/app/controllers/issue_extensions_settings_controller.rb
*** 21,26 ****
--- 21,27 ----
    before_filter :find_project, :find_user, :authorize
  
    def update
+     params.permit!
      @issue_extension_status_flow = IssueExtensionsStatusFlow.find_or_create(@project.id, @user.id)
      @issue_extension_status_flow.attributes = params[:setting]
      @issue_extension_status_flow.updated_by = @user.id
```

# チケットの編集(保存)でのエラー

```text
ActiveRecord::RecordNotFound (Couldn't find all IssueExtensionsStatusFlows with 'id': (first, {:conditions=>["project_id = ?", 5]}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:61:in `issue_status_assigned'
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:31:in `controller_issues_edit_before_save'
```

```diff:redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb
*** 58,64 ****
    # チケットに担当者が設定されている && 状態が新規の場合、担当に変更する
    def issue_status_assigned context
      issue = context[:issue]
!     issue_status = IssueExtensionsStatusFlow.find :first, :conditions => ['project_id = ?', issue[:project_id].to_i]
  
      if issue_status.old_status_id == issue[:status_id]
          issue[:status_id] = issue_status.new_status_id
--- 58,64 ----
    # チケットに担当者が設定されている && 状態が新規の場合、担当に変更する
    def issue_status_assigned context
      issue = context[:issue]
!     issue_status = IssueExtensionsStatusFlow.find_by :project_id => issue[:project_id].to_i
  
      if issue_status.old_status_id == issue[:status_id]
          issue[:status_id] = issue_status.new_status_id
```

```text
ActiveRecord::RecordNotFound (Couldn't find all IssueStatuses with 'id': (all, {:conditions=>["is_closed = (?)", true]}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:72:in `issue_status_closed'
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:32:in `controller_issues_edit_before_save'
```

```diff:redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb
*** 69,75 ****
    # チケットがクローズされている場合、進捗を100%に変更する
    def issue_status_closed context
      issue = context[:issue]
!     issue_status_closed = IssueStatus.find :all, :conditions => ["is_closed = (?)", true]
  
      issue_status_closed.each {|closed|
        if closed.id == issue[:status_id].to_i && issue[:done_ratio] != 100
--- 69,75 ----
    # チケットがクローズされている場合、進捗を100%に変更する
    def issue_status_closed context
      issue = context[:issue]
!     issue_status_closed = IssueStatus.where("is_closed = true")
  
      issue_status_closed.each {|closed|
        if closed.id == issue[:status_id].to_i && issue[:done_ratio] != 100
```

```text
ActiveRecord::RecordNotFound (Couldn't find all Watchers with 'id': (first, {:conditions=>false}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:87:in `issue_added_watcher'
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:33:in `controller_issues_edit_before_save'
```

```diff:redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb
*** 84,90 ****
      issue = context[:issue]
      journal = context[:journal]
  
!     if Watcher.find :first, :conditions =>["watchable_type = (?) and watchable_id = (?) and user_id = (?)", journal[:journalized_type], issue[:id].to_i, journal[:user_id].to_i] == nil
          hash_watcher = HashWithIndifferentAccess.new
          hash_watcher[:user_id]  = journal[:user_id].to_s
          watcher = Watcher.new(hash_watcher)
--- 84,90 ----
      issue = context[:issue]
      journal = context[:journal]
  
!     if Watcher.where("watchable_type = (?) and watchable_id = (?) and user_id = (?)", journal[:journalized_type], issue[:id].to_i, journal[:user_id].to_i).first == nil
          hash_watcher = HashWithIndifferentAccess.new
          hash_watcher[:user_id]  = journal[:user_id].to_s
          watcher = Watcher.new(hash_watcher)
```

# 各機能の動作

## 自動アサイン機能
使用していないので未確認。

## 自動クローズ機能
ここまでの変更で問題なし。

## 自動ウォッチャー機能
ここまでの変更で問題なし。

## 関連するチケット作成機能
エラーとなったため対応。

```text
ActiveRecord::RecordNotFound (Couldn't find all Issues with 'id': (first, {:conditions=>["id = (?)", 1034]}) (found 0 results, but was looking for 2)):
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:101:in `issue_added_relation'
  plugins/redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb:53:in `controller_issues_new_after_save'
```

```diff:redmine_issue_extensions/lib/issue_extensions_issue_hooks.rb
*** 98,104 ****
    def issue_added_relation context
      issue = context[:issue]
      params = context[:params]
!     from_issue = Issue.find :first, :conditions =>["id = (?)", params[:relation_issue_id].to_i]
      unless from_issue == nil
        relation = IssueRelation.new
        relation.relation_type = IssueRelation::TYPE_RELATES
--- 98,104 ----
    def issue_added_relation context
      issue = context[:issue]
      params = context[:params]
!     from_issue = Issue.find_by :id => params[:relation_issue_id].to_i
      unless from_issue == nil
        relation = IssueRelation.new
        relation.relation_type = IssueRelation::TYPE_RELATES
```

## チケット検索機能
この機能は使ったことがない。有効なのか？
> - チケットに表示されている「題名」の隣のテキストボックスに検索したいチケットの題名を記入し、「適用」をクリックします。
> - 検索結果が、「ヒットしたチケット」中に表示されるので、関連付けしたいチケットの関連付けアイコンをクリックすると関連付けされます。 
>  - 関連付け以外(重複やブロック、先行・後続)は表示されているチケット番号を参考に行ってください。

## ショートカット機能
ここまでの変更で問題なし。

## 新規複製機能
```text
Processing by IssuesController#new as HTML
  Parameters: {"duplicate_issue"=>"t", "project_id"=>"5", "copy_from"=>"1034"}
  Current user: foo (id=3)
  Rendered common/error.html.erb within layouts/base (0.1ms)
  Rendered plugins/redmine_code_review/app/views/code_review/_html_header.html.erb (0.1ms)
  Rendered plugins/redmine_wiki_extensions/app/views/wiki_extensions/_html_header.html.erb (0.0ms)
  Rendered plugins/sidebar_hide/app/views/sidebar/_hideButton_partial.html.erb (0.3ms)
  Rendered plugins/redmine_code_review/app/views/code_review/_body_bottom.html.erb (0.0ms)
  Rendered plugins/redmine_wiki_extensions/app/views/wiki_extensions/_body_bottom.html.erb (0.0ms)
Completed 403 Forbidden in 136ms (Views: 13.1ms | ActiveRecord: 62.6ms)
```

ロールと権限の設定で「チケットのコピー」を行う権限がなかったため。
権限を追加したことろ問題なし。