Redmine pluginの開発について当初自分が疑問に思ってたことに回答


##はじめに
Railsアプリは作ったことがあるけど、Redmine plugin開発に初めて取り組む際、
いろいろな疑問がわきました。
実際に開発していく中で疑問が解消されていったのでまとめておきます。

##前提
- 諸事情により今回はWindows版のRedmineを使用することを想定します。
- 必要な環境をまとめてインストールしてくれるBitNami Redmine Stackを使います。
- バージョンは少し古い2.3.2-1です。
- Cドライブ直下にインストールすることを想定します。

##疑問1 WindowsでどうやってRedmineを動かすの？
RubyからWebサーバーから必要なものを全部インストールしてくれるBitNami Redmine Stackを使います。

https://bitnami.com/stack/redmine

最新版は3.0.0ですが、2.3.2-1でインストールされたものは次の通りです。（気になったパッケージのみ）

- Ruby 1.9.3
- Rails 3.2.13
- Thin 1.3.1
- git 1.8.3
- Apache 2.4.6
- MySQL 5.5
- mingw32

mingw上で動くわけですね！

Apache - Thin - Redmine という構成です。

インストールすると自動的にサービスが起動しています。（productionモードで動作しています）
専用アプリで関連サービスの起動、停止、再起動を管理できます。

`C:¥BitNami¥redmine-2.3.2-1¥manager-windows.exe`

![2.png](https://qiita-image-store.s3.amazonaws.com/0/38173/f22a272d-d676-d302-ad96-944eb52d54a9.png "2.png")

![5.png](https://qiita-image-store.s3.amazonaws.com/0/38173/13679a5e-a953-cfd3-7785-d71d7a123c4c.png "5.png")

##疑問2 どうやってdevelopmentモードやtestモードで動かすの？

###1.コンソールを起動

まず、いろいろなPATHを通した専用コンソールを起動します。

`C:¥BitNami¥redmine-2.3.2-1¥use_redmine.bat`

![3.png](https://qiita-image-store.s3.amazonaws.com/0/38173/c530cff9-91da-0a1d-8c46-74dbf01d9ea9.png "3.png")


このコンソールを使えばLinuxコマンド(mingw32）やrailsコマンド等が使えます。

![4.png](https://qiita-image-store.s3.amazonaws.com/0/38173/300480a1-8685-47e1-c21d-1658b781c4dc.png "4.png")

###2.Redmineをdevelopmentモードで起動する

####2.1.起動中のサービスを停止
productionモードで動作中のサービスを停止します。

![1.png](https://qiita-image-store.s3.amazonaws.com/0/38173/2d8d3bc1-b1ac-2e6e-3bd4-cb3900134b4a.png "1.png")


####2.2.相対パスの無効化
まず、アプリの相対パスを無効にします。

`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥config¥additional_environment.rb`の中をコメントアウトします。（productionで動かす時は戻す必要があります）

```rb:additional_environment.rb
#config.action_controller.relative_url_root='/redmine'
```

####2.3.development用、test用bundle設定有効化
development用、test用bundleが無効になっているので有効化します。

`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥.bundle¥config`を変更します。

```:config(変更前)
---
BUNDLE_BIN:bin
BUNDLE_WITHOUT: development:test:posgresql:sqlite3
```

```:config(変更後)
---
BUNDLE_BIN:bin
BUNDLE_WITHOUT: posgresql:sqlite3
```

####2.4.bundle実行

`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs`に移動します。
（ここがRedmineのRails.rootになります）

`bundle`を実行します。

```
>bundle
```

####2.5.DB設定を確認

`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥config¥database.yml`を確認します。

BitNamiのバージョンにもよりますが、productionとdevelopmentとtestが同じDBを指しているので、必要に応じて変更します。

また、DBのrootのパスワードはBitNamiインストール時のRedmine管理者アカウントのパスワードと同じものになっています。

####2.6.db:migrate実行

DBをproductionと分ける場合はDBを初期化します。

```
>bundle exec rake db:drop db:create db:migrate redmine:plugins:migrate redmine:load_default_data RAILS_ENV=development
```

####2.7.Redmineを起動

railsを起動します。

```
>bundle exec rails s
```

![8.png](https://qiita-image-store.s3.amazonaws.com/0/38173/fc08fd67-4a86-8451-df5e-1b01673023e8.png "8.png")

####2.8.Redmineを終了

`Ctrl + C`で終了します。

![9.png](https://qiita-image-store.s3.amazonaws.com/0/38173/908dfe19-59d6-8402-b2cb-a6af50097785.png "9.png")

##疑問3 pluginはどんな構成でどう配置するの？
[Redmineのサイトにチュートリアル](http://www.redmine.org/projects/redmine/wiki/Plugin_Tutorial)を見るのが一番わかりやすです。

一言で言うと、Redmineアプリの中に複数のRailsアプリ(plugin)が共存するイメージです。

- `ruby script/rails generate redmine_plugin <plugin_name>`コマンドで作成する
- `C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥plugins`以下に`<plugin_name>`（RedmineのリソースにアクセスできるRailsアプリ）が配置される
- plugin独自の設定（Redmine内の権限やメニュー設定など）はpluginの各アプリ直下の`init.rb`に記述する

##疑問4 pluginのControllerやModelの作成方法は？

plugin用のgenerateコマンドを使います。

Model:

`ruby script/rails generate redmine_plugin_model <plugin_name> <model_name> [field[:type][:index] field[:type][:index] ...]`

Controller:

`ruby script/rails generate redmine_plugin_controller <plugin_name> <controller_name> [<actions>]`

##疑問5 pluginのdb:migrateはどこで実行するの？

Redmineルート(C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs)で実行します。

plugin用のmigrateは`bundle exec rake redmine:plugins:migrate`です。

##疑問6 pluginのgemを追加する場合どこに書くの？

`plugins/<plugin_name>/Gemfile`に書きます。

bundleコマンドはRedmineルート(C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs)で実行します。
（全pluginのGemfileがマージされるようになっているので、pluginの方で実行しても意図したgemがロードされません）

##疑問7 pluginのroutesを追加する場合どこに書くの？

`plugins/<plugin_name>/config/routes.rb`に書きます。

Redmineルート`/`からのパスで記述します。（普通のRailsアプリと同じ）

##疑問8 pluginのjavascriptとcssを追加する場合どこに書くの？

[MVC of Redmine Plugin in Bootstrap](http://qiita.com/ogomr/items/fe139ceb93ad13794323)が参考になりました！

Viewに次の感じで組み込めます。
（`<plugin_name>`部は自分のpluginで読み替えてください）

CSS:

`<%= stylesheet_link_tag 'sample.css', :plugin => '<plugin_name>', :media => 'all' %>`

JavaScript:

`<%= javascript_include_tag 'sample.js', :plugin => '<plugin_name>' %>`

JavaScriptで`$('#xx').on("click", ...)`のようなDOM情報を使うもの含まれる場合は、コンテンツより後に記述しておく必要があります。

コンテンツより先に書いてしまうと、対象となるタグが存在しないのでイベントが追加されずはまってしまいました。
（普段のRailsアプリでは直接javascript_include_tagを書くことがないので気づくのに時間がかかりました）

置き場所は次の通りです。

CSS:

`plugins/<plugin_name>/assets/stylesheets/sample.css`

JavaScript:

`plugins/<plugin_name>/assets/javascripts/sample.js`


##疑問9 Bootstrapは使えるの？
疑問8のようにstylesheet_link_tagを使えば可能です。
[MVC of Redmine Plugin in Bootstrap](http://qiita.com/ogomr/items/fe139ceb93ad13794323)がとてもわかりやすいです。

ただ、bootstrap-sassのようにprecompileが必要なものはうまく動かせていません。
（asstes:precompileを実行してもcssが生成されない感じ）
このあたりのやり方を知っている人がいたらぜひ教えてほしいです！

##疑問10 CoffeeScriptは使えるの？
わかっていません。
いろいろ試してみたのですが、precompileが必要なものがうまく動かせていません。
こちらもやり方を知っている人がいたらぜひ教えてほしいです！

追記：
CoffeeScriptやSassなど、asset pipeline系は使えなさそうです。
public以下にはjavascriptsやstylesheetsが存在していますが、app以下にはassetsが存在しません。

config¥application.rbには
`config.assets.enabled = false
`が設定されており、そもそもasset piplineを使わない想定になっています。

どうしても使いたい場合は、手動コンパイルして、生成されたjsやcssをpluginのassets¥javascriptsやassets¥stylesheetsに置く必要がありそうです。
（起動時にpublic¥plugin_assets¥にコピーしてくれるので置いてjsやcssは普通に使えます）

##疑問11 pluginのlocaleはどこに書くの？

`plugins/<plugin_name>/config/locales/`内のymlファイルに書きます。

##疑問12 カスタムフィールドにはどうアクセスするの？

[Redmine.jp](http://redmine.jp/tech_note/plugins/developer/quickref/get-custom-value/)では次のような方法で取得すると紹介されています。

```rb
issue = Issue.last
cf_name = "カスタムフィールド名"
cv = issue.custom_field_values.detect {|c| c.custom_field.name == cf_name}
cv.value if cv
```

確かにアクセスできることはできるのですが、カスタムフィールドの数が増えてくると遅いです。

- `Issue - CustomValue - IssueCustomField`の関係となっている
- IssueCustomFieldはCustomFieldの単一テーブル継承（Single Table Inheritance）となっている

ということがわかったので、次のようにすれば快適に取得できます。

```rb:フィールド名から一度に値を取得
issue = Issue.last
cf_name = "カスタムフィールド名"
cv = CustomValue.where(customized_type: 'Issue').where(customized_id: issue.id).joins(:custom_field).where(custom_fields: {name: cf_name}).first
cv.try(:value)
```

動作が遅く感じる場合はこちらの方が早いかもしれません。

```rb:フィールド名からフィールドidを取得し、値を取得
issue = Issue.last
cf_name = "カスタムフィールド名"
field_id = IssueCustomField.find_by_name(cf_name).try(:id)
cv = CustomValue.where(customized_type: 'Issue').where(customized_id: self.id).where(custom_field_id: field_id).first
cv.try(:value)
```
※該当するフィールドがない場合はfield_idがnilになるので注意。

Projectなど他のカスタムフィールドも同じ感じで取得できます。

組み込みたいクラスにメソッド化しておくと良いかと思います。

```rb:Issueクラスに組み込む場合
def cf_value_by_name(cf_name)
  cv = CustomValue.where(customized_type: 'Issue').where(customized_id: self.id).joins(:custom_field).where(custom_fields: {name: cf_name}).first
  cv.try(:value)
end
```

where句で取る方法がわかれば、検索にも使えて便利です。

##疑問13 Project等のRedmineのクラスにメソッドを追加するには？

オープンクラスを使って追加します。

例として`plugins/<plugin_name>/lib/customized_project.rb`を作成します。

```rb:customized_project.rb
module CustomizedProject
  attr_accessor :xxx, :yyy, :zzz

  def self.included base
    base.extend ClassMethods
  end

  # クラスメソッド
  module ClassMethods
    def xxxxx
    end
  end

  # インスタンスメソッド
  def yyyyy
  end
end

class Project
  include CustomizedProject
end
```

##疑問14 pluginのTest（RSpec使用）の実行方法は？

[RedmineのプラグインをRSpecでテストする](http://blog.mkt-sys.jp/2013/09/redminerspec.html)が参考になりました！

###準備

`plugins/<plugin_name>/Gemfile`にgemを追加

```:Gemfile例
:
group :test do
  gem 'rspec-rails', '~> 3.1.0'
  gem 'factory_girl_rails', '~> 4.4.1'
  gem 'timecop', '~> 0.7.1'
  gem 'capybara', '~> 2.3.0'
  gem 'poltergeist', '~> 1.5.0'
end
:
```

`plugins/<plugin_name>/spec/spec_helper.rb`を追加

```rb:spec_helper.rb例
require File.expand_path(File.dirname(__FILE__) + '/../../../test/test_helper')
require File.expand_path("../../../../config/environment", __FILE__)
require 'rspec/rails'
require 'capybara'
require 'capybara/rspec'
require 'capybara/poltergeist'

require File.expand_path(File.dirname(__FILE__) + '/support/shared_connection')
require File.expand_path(File.dirname(__FILE__) + '/support/wait_for_ajax')
Capybara.javascript_driver = :poltergeist
Capybara.current_driver = Capybara.javascript_driver

RSpec.configure do |config|
  config.use_transactional_fixtures = true
  config.infer_spec_type_from_file_location!
  config.include FactoryGirl::Syntax::Methods
  FactoryGirl.definition_file_paths = [File.expand_path("../factories", __FILE__)]
  FactoryGirl.find_definitions
  config.before(:all) do
    FactoryGirl.reload
  end
end
```

`plugins/<plugin_name>/support/shared_connection.rb`を追加

参考:[Three tips to improve the performance of your test suite](http://blog.plataformatec.com.br/2011/12/three-tips-to-improve-the-performance-of-your-test-suite/)

```rb:shared_connection.rb例
class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || retrieve_connection
  end
end

# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection
```

`plugins/<plugin_name>/support/wait_for_ajax.rb`を追加

参考:[Automatically wait for AJAX with Capybara](https://robots.thoughtbot.com/automatically-wait-for-ajax-with-capybara)

```rb:wait_for_ajax.rb例
module WaitForAjax
  def wait_for_ajax(timeout = Capybara.default_wait_time)
    Timeout.timeout(timeout) do
      loop until finished_all_ajax_requests?
    end
  end

  def finished_all_ajax_requests?
    page.evaluate_script('jQuery.active').zero?
  end
end

RSpec.configure do |config|
  config.include WaitForAjax, type: :feature
end
```

あとはfactoriesやmodels等を追加してspecを書きます。

###実行

Redmineルート(C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs)にて、

`bundle exec rpec plugins¥<plugin_name>¥spec`

を実行します。

##疑問15 Windows用のGitツールは何を使う？

いくつか試しましたが、[GitHub Windows](https://windows.github.com/)の`Git Shell`が一番しっくりきました。
defaultではPowerShellで動きます。

![6.png](https://qiita-image-store.s3.amazonaws.com/0/38173/44209725-3dae-2df6-b9f6-2ae5c63a0619.png "6.png")

##疑問16 エディタはどうしよう？
MacではVimを使っているのでWindows用Vimを試してみましたが、
`use_redmine.bat`との連携がなかなか思ったようにいきませんでした。

なのでWindowsでは[Atom](https://atom.io/)を使うことにしました。

![7.png](https://qiita-image-store.s3.amazonaws.com/0/38173/ee022a84-4aab-3725-3284-481176d5c0e8.png "7.png")


##Tips1 コンソールに出力されるログが文字化け

`use_redmine.bat`がUTF-8でないので日本語が文字化けします。
`log/development.log`の方はUTF-8なので問題ありません。

##Tips2 コンソールが突然落ちる

これは原因がわかるまで悩みました。
（落ちるときと落ちない時があったので）

原因：

　SQL文のlogで日本語が文字化けすることで、文字によってはコンソールが落ちてしまっていました。（チームメンバーが原因を特定してくれました）

対応：
　logのlevelを変更して日本語logが出力しないようにしました。

　`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥config¥environments¥development.rb`に1行追加します。

```rb:development.rb
RedmineApp::Application.configure do
:
  config.log_level = :info
end
```

##Tips3 developmentモードとtestモードがproductionモードに比べ劇的に重いんですけど、、

developmentで動かすと正直使えないくらい動作が重くなりました。
（100件もないtestが10分以上かかるなど）

原因:

　SQLのlogが多過ぎることで重くなっていました。
　カスタムフィールドが多いと、アクセス方法によっては大量のSQLクエリーが発行されます。

対応:

　こちらもlogのlevelを変更することで速度が劇的に改善されました。

　`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥config¥environments¥development.rb`に1行追加します。

```rb:development.rb
RedmineApp::Application.configure do
:
  config.log_level = :info
end
```

　`C:¥BitNami¥redmine-2.3.2-1¥apps¥redmine¥htdocs¥config¥environments¥test.rb`に1行追加します。

```rb:test.rb
RedmineApp::Application.configure do
:
  config.log_level = :info
end
```

##Tips4 Test時にPhantomJSが認識されない

poltergeistを使いたかったのでPhantomJSが必要です。
[PhantomJSのサイト](http://phantomjs.org/download.html)からWindows用をダウンロードして使いますが、いくつかはまった点があるので紹介しておきます。

- 最新の2.0.0ではpoltergeistとの連携がうまくいかなかった
- [1.8.2](https://code.google.com/p/phantomjs/downloads/detail?name=phantomjs-1.8.2-windows.zip&can=4&q=)だとうまく連携できた
- BitNamiをCドライブ直下にインストールしないとRailsからphantomjsが認識できなかった（原因は不明）

##Tips5 Test時にCustomValueが空っぽになる

例えばProjectCustomFieldをcreateして、Projectをcreateすると、対応するCustomValueが自動的にcreateされます。
CustomValueを自前でcreateしたい場合は、Projectのcreateより先にcreateしておく必要があります。

詳しくは[Redmine plugin開発で、テストコード作成時にCustomValueにテストデータを設定する時の注意点](http://qiita.com/t_oginogin/items/1b176f5f70c49053d623)をご覧ください。

##Tips6 Projectメニューのメニュー名とコントローラー名が異なると、メニュータブをクリックしても選択状態にならない

`init.rb`に記述するメニュー名とコントローラー名は同じ名前にしておきましょう。

```rb:init.rb（メニュークリックで選択状態にならない場合）
:
menu :project_menu, :polls_dummy, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
:
```

```rb:init.rb（メニュークリックで選択状態になる場合）
:
menu :project_menu, :polls, { :controller => 'polls', :action => 'index' }, :caption => 'Polls', :after => :activity, :param => :project_id
:
```