35
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-03-12

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

##前提

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

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

最新版は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

5.png

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

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

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

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

3.png

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

4.png

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

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

1.png

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

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

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

####2.8.Redmineを終了

Ctrl + Cで終了します。

9.png

##疑問3 pluginはどんな構成でどう配置するの?
Redmineのサイトにチュートリアルを見るのが一番わかりやすです。

一言で言うと、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が参考になりました!

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がとてもわかりやすいです。

ただ、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では次のような方法で取得すると紹介されています。

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)となっている

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

フィールド名から一度に値を取得
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)

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

フィールド名からフィールド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など他のカスタムフィールドも同じ感じで取得できます。

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

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を作成します。

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でテストするが参考になりました!

###準備

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を追加

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

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

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 WindowsGit Shellが一番しっくりきました。
defaultではPowerShellで動きます。

6.png

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

なのでWindowsではAtomを使うことにしました。

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行追加します。

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行追加します。

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行追加します。

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

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

poltergeistを使いたかったのでPhantomJSが必要です。
PhantomJSのサイトからWindows用をダウンロードして使いますが、いくつかはまった点があるので紹介しておきます。

  • 最新の2.0.0ではpoltergeistとの連携がうまくいかなかった
  • 1.8.2だとうまく連携できた
  • BitNamiをCドライブ直下にインストールしないとRailsからphantomjsが認識できなかった(原因は不明)

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

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

詳しくはRedmine plugin開発で、テストコード作成時にCustomValueにテストデータを設定する時の注意点をご覧ください。

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

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
35
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?