プロジェクト

全般

プロフィール

Redmineプラグイン開発

プラグイン開発の流れ

まず、自由にいじれる独自のRedmine実行環境を用意します。
Linuxマシンの自分のユーザーのHOMEディレクトリ下にRedmineを展開し、Railsに付属するWEBRickサーバー(Redmine 4(Rails 5)からはPumaサーバー)で実行し、DBにはSQLiteを使うのが開発用にはお手軽です。

Redmine開発環境の用意

Linuxコマンドライン環境(Windows 10で利用可能なWindows Subsystem for Linuxを含む)で簡単な開発環境を用意します。

プラグインの雛形生成

プラグイン名を指定して、以下のコマンドを実行します。

plugins$ bundle exec rails generate redmine_plugin redmine_hello
                                                   ^^^^^^^^^^^^^ 生成するプラグイン名

プラグインの雛形で生成されるディレクトリ構造

redmine_hello/
├─ app
│   ├─ controllers
│   ├─ helpers
│   ├─ models
│   └─ views
├─ assets
│   ├─ images
│   ├─ javascripts
│   └─ stylesheets
├─ config
│   └─ locales
├─ db
│   └─ migrate
├─ lib
│   └─ tasks
└─ test
     ├─ fixtures
     ├─ functional
     ├─ integration
     └─ unit

プラグインの雛形で生成されるファイル

redmine_hello/
├─ README.rdoc
├─ config
│   ├─ locales
│   │   └─ en.yml
│   └─ routes.rb
├─ init.rb
└─ test
     └─ test_helper.rb

helloプラグイン最低限の実装

init.rbの編集

プラグイン情報を記載します。記載内容が、Redmineの[管理] > [プラグイン]で確認できればOKです。

雛形で生成された内容 雛形を修正した内容(例)
Redmine::Plugin.register :redmine_hello do
  name 'Redmine Hello 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
Redmine::Plugin.register :redmine_hello do
  name 'Redmine Hello plugin'
  author 'TAKAHASHI,Toru'
  description 'This is a tiny sample plugin for Redmine'
  version '0.0.1'
  url 'http://www.torutk.com/projects/swe/wiki/Redmineプラグイン開発/'
  author_url 'http://www.torutk.com'
end

プラグイン作成のいろいろな方法

Redmineのプラグインには、いくつかの方法があります。現時点で認識しているプラグイン作成方法でも次のものがあります。

  1. MVCの各部品を作成するMVCプラグイン
  2. 既存の表示に追加するView hooksプラグイン
  3. 既存の表示(ビューテンプレート)を差し替えるプラグイン
  4. 既存の振る舞い(メソッド)を差し替えるパッチプラグイン
  5. マクロを提供するプラグイン

使い分けに関しては、データをデータベースに保存する場合はモデルの定義が必要なのでMVCプラグイン、既存の表示に付けたしをする程度であれば、適するフック箇所があればView hooksプラグイン、なければビューテンプレートを差し替えるプラグインかパッチプラグイン、Wikiの記載でちょっとした処理をするならマクロといったところでしょうか。

フック

Redmineには、プラグインで処理を差し込めるようにあらかじめフックが設けられています。
フックには、View、Controller、Model、HelperとそれぞれRailsの構成要素の種類に応じて用意されています。

フック一覧を調べる

Redmine本家サイトに掲載されています。
http://www.redmine.org/projects/redmine/wiki/Hooks_List

次の方法でも調べることができます(上述ページに記載の方法)。

redmine$ grep -r call_hook *

Viewフックのお試し

チケット一覧のViewに用意されている:view_issues_index_bottomにフックを差し込むサンプルを記述します。
フックの定義は上述の方法で調べました。

redmine$ grep -r call_hook *
  : 
app/views/issues/index.html.erb:<%= call_hook(:view_issues_index_bottom, { :issues => @issues, :project => @project, :query => @query }) %>

フックを実装するRubyのクラスを定義します。

  • redmine/plugins/redmine_mein/lib/redmine_mein/hooks.rb
    module RedmineMein
      class Hooks < Redmine::Hook::ViewListener
        # 実装
      end
    end
    

名前の衝突を避けるため、クラス名にプラグイン名を付けて長くなるのは避けたいので、moduleを使って名前空間を導入します。
ただし、moduleを使うとソースファイルを置く場所がモジュール名に対応するディレクトリの下にする必要があるようです。

モジュール名、クラス名は、Pascalケース(先頭が大文字で、続く単語の先頭が大文字のキャメルケース)で記述しますが、ディレクトリ名、ファイル名はスネークケースになるようです。

次に、このフックをプラグインで有効にするため、init.rbに依存を定義します。

require_dependency 'redmine_mein/hooks'

モジュールを導入しているのでモジュール名に対応するディレクトリとクラス名に対応するファイル名(拡張子.rbは不要)を記述します。

コントローラーとビュー

RailsのMVC構造におけるVCの部分を作成します。モデルは既存のものを利用します。
プラグインとしては、既にあるデータを活用して、新たなビューを作りたいときに該当します。

コントローラーの雛形作成

書式: ruby bin/rails generate redmine_plugin_controller <プラグイン名> <コントローラー名> <アクション>...
redmine$ ruby bin/rails generate redmine_plugin_controller redmine_mein mein index
create  plugins/redmine_mein/app/controllers/mein_controller.rb
      create  plugins/redmine_mein/app/helpers/mein_helper.rb
      create  plugins/redmine_mein/test/functional/mein_controller_test.rb
      create  plugins/redmine_mein/app/views/mein/index.html.erb

コントローラーには雛形生成時に指定したindexアクションに対応するindexメソッドが定義されます。
ビューには雛形生成時に指定したindexアクションに対応するindex.html.erbファイルが生成されます。

コントローラー名の指定について

コントローラー名は、モデルに1:1に対応するコントローラーの場合はモデル名の複数形にControllerを付加した命名とします。
モデルと対応しないコントローラー名は役割に応じた命名とします(複数形でなくてよい)。

  • 例1)モデルUserに対応するコントローラー名は、UsersControllerとする。
    • railsのgenerateコマンドではキャメルケースで指定した場合(この例ではUsers)、スネークケースのファイル名に変換されるはず(users_controller.rb)ですが、redmine_plugin_controllerを指定した場合、モデル名をキャメルケースで指定すると(Users)そのままファイル名に使用されてしまいます(Users_controller.rb)。小文字スネークケースで指定してください。
      https://www.redmine.org/issues/28668
    • redmine_plugin_modelを指定してモデルを作成する場合は大文字キャメルケースで指定したらファイル名は小文字スネークケースになります。

ルート設定

プラグインディレクトリ下のconfig/routes.rb にパスを定義します。

Rails.application.routes.draw do
  resources :mein
end

resources で指定したコントローラーは、URLのパスにコントローラー名でアクセスが可能となります。
この例では、http://<サーバー名>/mein でindexメソッドに処理が渡されます。

resources :mein の指定で、次のHTTPリクエストに対するコントローラーのメソッド呼び出しの対応が定義されます。

リクエストメソッド リクエストURI コントローラー メソッド
GET /mein mein index
POST /mein create
GET /mein/new new
GET /mein/:id/edit edit
GET /mein/:id show
PATCH, PUT /mein/:id update
DELETE /mein/:id destroy

ルート設定の確認は、次のコマンドで可能です。

redmine$ bundle exec rails routes
indexアクションだけをルート定義する
resources :mein, only: :index
プロジェクトの下でコントローラーにアクセスする

T.B.D.

モデル

モデルクラスは、データベースのテーブル構造に対応します。モデルクラスのオブジェクト1つは、テーブルのレコードのデータを保持します。そして、モデルクラスのオブジェクトが持つデータをテーブルに反映(新規のレコードとなるならインサート、既存のレコードの変更であればアップデート)します。また、逆にレコードのデータを持つモデルクラスのオブジェクトを取得します。

モデルクラスの生成

rails generateコマンドで、redmine_plugin_model を指定し、生成対象プラグインとモデルクラス名を指定します。
今回は属性(データベースのテーブルの列)を指定せず空のモデルを生成します(後から追加は可能です)。

redmine$ bundle exec rails generate redmine_plugin_model redmine_hello Hello
      create  plugins/redmine_hello/app/models/hello.rb
      create  plugins/redmine_hello/test/unit/hello_test.rb
      create  plugins/redmine_hello/db/migrate/001_create_hellos.rb

生成されたファイルの内容は次の通りです(Redmine 4.0/Rails 5.1で生成)。

  • hello.rb
    class Hello < ActiveRecord::Base
    end
    
  • 001_create_hellos.rb
    class CreateHellos < ActiveRecord::Migration[5.1]
      def change
        create_table :hellos do |t|
        end
      end
    end
    
    • マイグレーションのクラス CreateHellos は、ActiveRecord::Migration[5.1]を継承するとちょっと奇妙なコードになっています。Rails 5からは、MigrationはRailsのバージョンによって異なるクラスを継承できるように、Migrationクラスの[]メソッドを呼び引数のバージョンによって対応するクラスを返すようになっています。
      従来のように、ActiveRecord::Migration と記述したら次のエラーとなってしまいます。
      StandardError: Directly inheriting from ActiveRecord::Migration is not supported.
      Please specify the Rails release the migration was written for:
      
    • このマイグレーションを実行すると、SQLite3を使用した場合次のテーブルが作成されます。
      CREATE TABLE IF NOT EXISTS "hellos" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL);
      

      モデルクラスで指定しなくても、主キーで自動インクリメントされるidカラムが用意されます。
属性の指定

モデルを生成するrails generateコマンドで、オプションに属性名と型を指定します。

$ bundle exec rails generate redmine_plugin_model redmine_hello Hello message:string
  :

属性は、マイグレーション用のコードに記載され、モデルクラスには記述されません。

  • hello.rb
    class Hello < ActiveRecord::Base
    end
    
  • db/migrate/002_create_hellos.rb
    class CreateHellos < ActiveRecord::Migration[5.1]
      def change
        create_table :hellos do |t|
          t.string :message
        end
      end
    end
    
    • マイグレーションを実行すると、SQLite3を使用した場合次のスキーマが生成されます。
      CREATE TABLE IF NOT EXISTS "hellos" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "message" varchar);
      

属性の型とデータベース上の型

モデルの生成時に指定する属性の型と、データベース(MySQL)上のカラムの型の対応は次の通りです。

No モデルの属性型 MySQLデータベースの型 内容
1 String varchar(255) 255文字以下の文字列
2 Text text 無制限長の文字列
3 Integer int(11) 数値
4 Float float
5 Decimal decimal
6 Boolean tinyint(1)
7 Date date
8 Time time
9 DateTime datetime
10 Timestamp datetime

タイムスタンプに関する属性

モデルオブジェクト(データベースのテーブルのレコード)の生成日時および更新日時は、マイグレーション用のコードにtimestampsの指定をすると生成されます。
  • db/migrate/001_create_hellos.rb
    class CreateHellos < ActiveRecord::Migration[5.1]
      def change
        create_table :hellos do |t|
          t.string :message
    
          t.timestamps
        end
      end
    end
    
  • SQLite3の場合次のスキーマが生成されます。
    CREATE TABLE IF NOT EXISTS "hellos" (
        "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
         "message" varchar,
         "created_at" datetime NOT NULL,
         "updated_at" datetime NOT NULL);
    

マイグレーションスクリプトあれこれ

  • changeメソッドで記述するのは、add_column等それだけでマイグレーションの実行とそのロールバックが可能な場合
  • upメソッドとdownメソッドの双方を記述するのは、changeメソッドだけではロールバックができない場合

モデルオブジェクトのメソッド

  • インスタンスを生成 new
  • インスタンスの属性を更新 attributes
    引数に属性名をキー、値をバリューとするハッシュを渡して複数属性をまとめて設定
  • インスタンスの属性をデータベースに保存 save
  • インスタンスの生成とデータベース保存 create
    引数は属性名をキー、値をバリューとするハッシュを渡し、インスタンスの生成とデータベース保存を一気に実行
  • インスタンスの属性を更新しデータベース保存 update
  • インスタンスの属性を1つ更新しデータベース保存 update_attribute
  • すべてのインスタンスを取得 all
  • 主キーで検索 find
    引数には検索するキーを複数指定可
  • 条件を指定し1件を検索 find_by
    LIMITで1個に絞っている模様
  • 条件を指定しN件を検索 where
  • 件数を調べる count
  • 並び順を指定 order
whereの指定方法
  • 文字列指定 モデル.where("category_id = '3'")
  • ハッシュ指定 モデル.where(category_id: 3)

モデル間の関係

モデル同士の関連の種類(抜粋)

  • belongs_to
  • has_one
  • has_many
belongs_to
class Alfa < ActiveRecord::Base
end

class Bravo < ActiveRecord::Base
  belongs_to :alfa
  • bravo.alfa と属性としてアクセス可能
  • bravosテーブルが alfa_idを外部キーとしてalfasテーブルを参照
class Charlie < ActiveRecord::Base
  belongs_to :author, class_name: 'User'

関連するクラス名(テーブル名)とは別の名前で関連付けする場合、class_nameで実のクラス名を指定します。
外部キーは、この場合、author_id の列名で生成されます。

has_many

belongs_to で関連付けられた側に指定します。belongs_toと対で使用しないと意味がなさそうです。一方、belongs_toを単独して使用することは意味があります。

class Alfa < ActiveRecord::Base
  has_many :bravos
end

class Bravo < ActiveRecord::Base
  belongs_to :alfa
  • alfa.bravos と属性としてアクセス可能(配列として取得)
    alfa.bravos.each do |b|

マクロ

マクロは、Wikiに埋め込むと実行時に展開されるブロックです。

便利集

Railsコンソール

Rails環境をコンソールで扱います。

redmine$ bundle exec rails console
irb(main):002:0> "man".pluralize
=> "men" 
irb(main):003:0>

参考資料

RAILS GUIDES日本語訳

クリップボードから画像を追加 (サイズの上限: 1 GB)