[ Redmine index ]
Redmineのプラグインを作成したいと試行錯誤を重ねている途中です。RubyやRuby on Railsには馴染めそうにないなぁと感じるC++/Javaプログラマーが一歩一歩進めています。
さまざまな業務において、一意な識別を目的としたコード(符号)を採番するプラグインです。符号化体系は、「固定文字列+連番」とします。
符号例) XYZ98-00101
固定文字列:XYZ98-
連番:00101
固定文字列は、1文字以上255文字以下の任意の文字列
連番は、所定の桁数の正の整数でゼロサプレスなし(つまりゼロ埋めあり)
メニューはプロジェクトメニューに配置し、アクションは次とします。
採番系列(固定文字列)データの構成は
採番項目データの構成は、
とします。
プラグイン名は、"Redmine Numbering"とします。
今回のプラグイン開発は次の環境で作業しています。
※1 Windowsマシンしかない場合でも、VMware Player/VirtualBox/Hyper-V等の仮想化ソフト上でLinuxを動かすのがよいでしょう。
※2 CentOS 6の標準vimはruby対応が抜かれてビルドされているので(vim --versionで確認可)、Emacsに変更しました。
最初にRedmineを開発作業用にセットアップします。
まず、RedmineをRubyForgeからダウンロードしてきます。
ホームディレクトリ下に適当な作業ディレクトリを作って展開します。
~$ wget http://rubyforge.org/frs/download.php/76867/redmine-2.3.0.tar.gz ~$ mkdir redmine ~$ cd redmine redmine$ tar xzf ~/redmine-2.3.0.tar.gz redmine$ cd redmine-2.3.0 redmine-2.3.0$
次にデータベース設定をします。プラグイン開発ではシステムに影響を与えたくないので、データベースはSQLiteを使います。また、SQLiteはデータを作業場所の単一ファイルに置くので、バックアップやテストデータの切り替え等が容易にできます。プラグイン開発には重宝します。
redmine-2.3.0$ emacs config/database.yml
database.ymlには以下の内容を記述します。
development: adapter: sqlite3 database: db/redmine.db timeout: 5000
Redmineの動作に必要なrubyのgemモジュールをこの作業用Redmine固有にインストールします。
redmine-2.3.0$ bundle install --path vendor/bundler \ --without rmagick :
もしbundleコマンドが実行できない場合、bundler gemパッケージをインストールします。bundler gemはシステムにインストールするので管理者権限で実行します。
# gem install bundler
libxml2、libxsltがないとgemのnokogiriのインストールでエラーとなりました。libxml2-devel、libxslt-develパッケージをインストールします。
# yum install libxml2-devel libxslt-devel
gemのインストールが完了したら、セッション生成に使用する秘密鍵、データベーススキーマ、初期データをデータベースにロードします。このとき、RAILS_ENVは指定せず、デフォルトのdevelopmentモードで実行しています。
redmine-2.3.0$ bundle exec rake generate_secret_token redmine-2.3.0$ bundle exec rake db:migrate : redmine-2.3.0$ bundle exec rake redmine:load_default_data Select language: ar, az, bg, bs, ca, cs, da, de, el, en, en-GB, es, et, eu, fa, fi, fr, gl, he, hr, hu, id, it, ja, ko, lt, lv, mk, mn, nl, no, pl, pt, pt-BR, ro, ru, sk, sl, sq, sr, sr-YU, sv, th, tr, uk, vi, zh, zh-TW [en] ja ==================================== Default configuration data loaded. redmine-2.3.0$
Redmineサーバーを起動します。サーバーはWEBrickを使います。
redmine-2.3.0$ bundle exec rails server => Booting WEBrick => Rails 3.2.13 application starting in development on http://0.0.0.0:3000 => Call with -d to detach => Ctrl-C to shutdown server [2013-03-20 15:19:14] INFO WEBrick 1.3.1 [2013-03-20 15:19:14] INFO ruby 1.9.3 (2013-02-22) [x86_64-linux] [2013-03-20 15:19:14] INFO WEBrick::HTTPServer#start: pid=2942 port=3000 :
サーバーはポート番号3000で待ち受けるので、Webブラウザでポート3000を指定してアクセスします。Redmine画面が出ればOKです。
エラーになる場合、WEBrickのコンソール出力を詳しく調べます。
iptables(ファイアウォール)は開発用マシンではoffにしておいたほうが無難です。
これからいろいろ試行錯誤を繰り返していくことになるので、バージョン管理ツールを使って作業を記録しておくのが吉です。作業ディレクトリのトップに管理用ディレクトリ(=リポジトリ)を作るだけで済むgitを使うとローカルディレクトリの管理がとっても楽です。mercurialも同様でしょう。subversionは、作業ディレクトリとは別な場所にリポジトリを作るので面倒です。
redmine-2.3.0$ git init . Initialized empty Git repository in /home/torutk/redmine-2.3.0/.git/ redmine-2.3.0$
もし、この作業をしているユーザーアカウントで初めてgitを使う場合、ユーザー名、メールアドレスを登録します。
redmine-2.3.0$ git config --global user.name "Toru Takahashi" redmine-2.3.0$ git config --global user.email torutk@example.com
git commit ほかでメッセージを編集するエディタをvi以外にする場合、設定をします。コンソール限定であれば次の設定をします。この設定は、gitコマンドを実行するターミナル上でemacsを起動します。X Window上のGUIアプリケーションでemacsを開きたい場合は別な設定をする必要があります。
redmine-2.3.0$ git config --global core.editor emacs
ユーザー名、メールアドレスが登録済みかどうか調べる場合、
redmine-2.3.0$ git config --global --list user.name=Toru Takahashi user.email=torutk@example.com color.ui=auto alias.co=checkout :(略)
redmine-2.3.0には、Mercurial用の無視ファイル設定 .hgignoreが含まれています。このディレクトリで上述のgit initを実行すると、.hgignoreの設定を流用した.gitignoreファイルが生成されました(初めて知った動き)。
必要があれば、このファイルに無視したいファイルを追記します。例えば、エディタがファイル保存時に同じディレクトリにバックアップファイルを残す場合、バックアップファイルのパターンを追記します(*~など)。
今回は、vendor/bundler にインストールしたgem群を対象外とするため、以下を追記します。
/vendor/bundler
最初の状態をgitリポジトリに登録しておきます。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
redmine初期設定完了時点を初回登録 データベース設定を行い、WEBrickサーバーを起動しRedmineが利用可能に なった時点で登録する。
コミット理由(メッセージ)は、1行目に要約、2行目は空白、3行目以降に詳細を記述するのがよい書き方です。各種ツールでgitのログが読みやすいように、ASCII文字で72文字以内で改行を入れます。
emacsを終了すると、gitの登録が実行されます。
まずプラグインとして最低限の部分を作成します。
Redmineのプラグイン用コード・設定のひな形(スケルトン)を生成するコマンドが用意されているので、これを使ってプラグインを生成し、Redmineの[管理]>[プラグイン]に情報が表示される最低限の記述をします。
redmine-2.3.0$ ruby script/rails generate redmine_plugin redmine_numbering create plugins/redmine_numbering/app create plugins/redmine_numbering/app/controllers create plugins/redmine_numbering/app/helpers create plugins/redmine_numbering/app/models create plugins/redmine_numbering/app/views create plugins/redmine_numbering/db/migrate create plugins/redmine_numbering/lib/tasks create plugins/redmine_numbering/assets/images create plugins/redmine_numbering/assets/javascripts create plugins/redmine_numbering/assets/stylesheets create plugins/redmine_numbering/config/locales create plugins/redmine_numbering/test create plugins/redmine_numbering/test/fixtures create plugins/redmine_numbering/test/unit create plugins/redmine_numbering/test/functional create plugins/redmine_numbering/test/integration create plugins/redmine_numbering/README.rdoc create plugins/redmine_numbering/init.rb create plugins/redmine_numbering/config/routes.rb create plugins/redmine_numbering/config/locales/en.yml create plugins/redmine_numbering/test/test_helper.rb redmine-2.3.0$
プラグイン名は、スネーク形式(小文字、単語の間をアンダースコアで結ぶ)で指定します。redmine_ほげほげと命名するのが慣習となっているようです。
スケルトンが生成されたら、カレントディレクトリを、いま生成したプラグインディレクトリに移動しておきます。
redmine-2.3.0$ cd plugins/redmine_numbering redmine_numbering$
生成されたinit.rbにプラグイン情報を記載します。
redmine_numbering$ emacs init.rb
スケルトンで生成した情報を書き換えます。
1 Redmine::Plugin.register :redmine_numbering do 2 name 'Redmine Numbering plugin' 3 author 'Toru Takahashi' 4 description 'This is a plugin for Redmine to numbering' 5 version '0.0.1' 6 url 'https://github.com/torutk/redmine_numbering' 7 author_url 'http://02.246.ne.jp/~torutk/' 8 end
これらは、Redmineの[管理] > [プラグイン]で表示される情報となります。
WEBrickを再起動し、WebブラウザからRedmineにアクセスし、adminでログイン後、[管理] > [プラグイン]を選択します。次の表示が出れば作成したプラグインが認識されています。
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
プラグインredmine_numberingをスケルトン生成し情報記述 スケルトンを生成した後、init.rbにプラグイン情報を記述し、Redmineに 認識されるところまで作成した。
プラグイン作成(その1)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-01 redmine-2.3.0$
プラグインをメニューに追加します。今回のプラグインは、プロジェクト単位で機能させるので、プロジェクトメニューに追加します。
メニューからはコントローラのアクションを呼ぶので、プラグインのコントローラを作成する必要があります。
コントローラとアクションをURLにマッピングするルート設定を行う必要もあります。
このプラグインでは、コントローラをデータ種類毎(採番系列データ、採番項目データ)に作ることとします。そして、メニューから呼ぶのは最初に表示する採番系列データ一覧を扱う採番系列コントローラとします。
まず、採番系列データ(numbering_prefix)用のコントローラを作成します。なお、採番系列データはここでは作成せず、「プラグイン(その4)」で作成します。
redmine-2.3.0$ ruby script/rails generate redmine_plugin_controller \ redmine_numbering numbering_prefixes index new edit create plugins/redmine_numbering/app/controllers/numbering_prefixes_controller.rb create plugins/redmine_numbering/app/helpers/numbering_prefixes_helper.rb create plugins/redmine_numbering/test/functional/numbering_prefixes_controller_test.rb create plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb create plugins/redmine_numbering/app/views/numbering_prefixes/new.html.erb create plugins/redmine_numbering/app/views/numbering_prefixes/edit.html.erb redmine-2.3.0$
コントローラ名は、ガイドにある慣習に従ってデータの複数形とします。このコントローラは、採番系列データ(numbering_prefix)を扱うので、numbering_prefixesとしています。Rubyコード上ではコントローラのクラス名は先頭を大文字とするキャメルケースNumberingPrefixesとして表現されます。
ファイル名ではスネークケースで、クラス名ではキャメルケースでと命名規約が混在している点は、Ruby on Railsの厄介な点ですね。
アクションは、仕様に記載のとおり、一覧/新規/編集を指定します。
プロジェクトメニューにプラグインを追加するためには、プロジェクトモジュールの定義とメニュー定義をinit.rbに記述します。
また、コントローラに関するルート設定をconfig/routes.rbに記述します。Redmine 2.x(Rails 3.x)では、ルート設定がないとURLとコントローラ/アクションのマッピングが自動では行われないようです。
1 Redmine::Plugin.register :redmine_numbering do 2 name 'Redmine Numbering plugin' 3 author 'Toru Takahashi' 4 description 'This is a plugin for Redmine to numbering' 5 version '0.0.1' 6 url 'https://github.com/torutk/redmine_numbering' 7 author_url 'http://02.246.ne.jp/~torutk/' 8 9 project_module :numbering do 10 permission :view_numbering_prefix, :numbering_prefixes => [:index] 11 permission :manage_numbering_prefix, 12 :numbering_prefixes => [:new, :edit, :create, :update, :destroy], 13 :require => :member 14 end 15 16 menu :project_menu, :numbering, 17 {:controller => 'numbering_prefixes', :action => 'index'}, 18 :param => :project_id 19 20 end
9-14行目がプロジェクトモジュールの定義です。
プロジェクトモジュール名は:numberingとします(9行目)。
このプロジェクトモジュール名は次のメニュー定義で使用します。プロジェクトモジュールの場合、合わせて権限の定義を記述します。ここでは:view_numbering_prefixと:manager_numbering_prefixの2つの権限を定義しています(10-13行目)。
2つ権限とは、データの変更が生じないindexアクションを実行できる「閲覧」と、データの変更が生じるnew/edit/create/update/destroy アクションを実行できる「管理」とになります。
閲覧権限は10行目に定義し、permissionで権限名には:view_numbering_prefixを定義、コントローラに:numbering_prefixesを指定、アクションは:indexを1つ指定しています。
管理権限は11-13行目に定義し、permissionで権限名に:manage_numbering_prefixを定義、コントローラには:numbering_prefixesを指定、アクションは:new,
:edit, :create, :update, :destroyを指定、権限の実行にはプロジェクトのメンバーであることが必要という指定をしています。
16-18行目がメニューの定義です。
メニューの種類として:project_menuを指定、モジュールはプロジェクトモジュール:numberingを指定、メニュー操作時のURLに、コントローラとアクションの組として:numbering_prefixesとindexを指定、URLにパラメータとして:project_idを付けるよう指定しています。
実験メモ
17行目の記述を、project_numbering_prefixes_pathに変更してみましたが、
undefined method `project_numbering_prefixes_path'
となってしまいました。
1 Rails.application.routes.draw do 2 resources :projects do 3 resources :numbering_prefixes 4 end 5 end
ルート設定は1行目のRails.application.routes.drawのブロックに記述します。
プロジェクト配下のルート(URLで/projects/<プロジェクト識別子>/<コントローラ名>/...となるもの)は、2行目の:projectsリソースのブロックに定義を記述します。
3行目でコントローラ:numbering_prefixesに関するリソースを設定します。RESTfulなアクションがデフォルトで定義されます。
この記述で定義されたルート設定を確認します。
redmine-2.3.0$ rake routes |grep prefix project_numbering_prefixes GET /projects/:project_id/numbering_prefixes(.:format) numbering_prefixes#index POST /projects/:project_id/numbering_prefixes(.:format) numbering_prefixes#create new_project_numbering_prefix GET /projects/:project_id/numbering_prefixes/new(.:format) numbering_prefixes#new edit_project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id/edit(.:format) numbering_prefixes#edit project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#show PUT /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#update DELETE /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#destroy redmine-2.3.0$
ルート名、HTTPメソッド、URL、コントローラ#アクション のリストが表示されます。
WEBrickを再起動し、動作確認をします。
adminユーザーで動作確認をすると、権限設定によらず使用可能となってしまいますので、動作確認では権限の異なるユーザーをいくつか作成しておきます。少なくても次の2つは必要かと思います。
Redmineでは、後から追加したプラグインに権限(permission)制御がある場合、デフォルトではどのロールにも権限が割り当てられていません。[管理]>[ロールと権限]>[権限レポート]をクリックし、採番プラグインの権限設定を表示すると、デフォルトでは次のようになっています。
次のように設定します。今回は、開発者も採番系列を新規に追加/既存の変更ができるように権限を付与しています。
Redmineでは、後から追加したプラグイン(プロジェクトモジュール)はプロジェクトで使用可能にはなっていません。そこで、プロジェクトメニューの[設定]>[モジュール]でプラグインを有効にします。
まずは非メンバーでログインし、プロジェクトを開きます。プロジェクトメニューは次のようになっており、Numbering Prefixプラグインは表示されていません。
次に、メンバーでログインし直し、プロジェクトを開きます。プロジェクトメニューに"Numbering”が追加されています。
"Numbering"をクリックすると、次の画面に飛びます。
このとき、ブラウザのアドレスバーには次のようなURLが表示されています。
http://<サーバー名>:3000/projects/<プロジェクト識別子>/numbering_prefixes
init.rbにてメニューをクリックしたときは、numbering_prefixesコントローラのindexアクションを設定しています。routes.rbの記述で定義されるルート設定(rake routesで確認可)で次の部分と一致しています。
project_numbering_prefixes GET /projects/:project_id/numbering_prefixes(.:format) numbering_prefixes#index
ブラウザ画面の表示は、numbering_prefixesコントローラのスケルトン生成で作られたindexアクション時のビュー(index.html.erb)が出すものです。
1 <h2>NumberingPrefixesController#index</h2>
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
プロジェクトメニューに表示する設定まで作成 コントローラをスケルトン生成し、プロジェクトモジュールとして権限設定、 プロジェクトメニューへの追加設定をした。
プラグイン作成(その2)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-02 redmine-2.3.0$
メニューの表示、権限レポートの表示を国際化(日本語対応)します。
表示文字列はロケール毎に外部ファイルとして定義します。config/localesディレクトリの下に言語コード(2文字)に拡張子.ymlを付けたファイルを用意すれば、その言語コードで実行されるときに読み込まれ表示に使用されます。日本語の場合、ja.ymlというファイル名を用意します。
このファイルは、最初に言語コード+":"を、2行目以降はシンボル名: メッセージ の書式で定義します。インデントは大事です(TAB使用禁止)。
ja: label_numbering: 採番 label_numbering_prefixes_new: 新規系列
プロジェクトメニューの表示は、規約でlabel_<メニュー名>というシンボル名の定義が使われます。今回メニュー名はinit.rb(16行目)で"numbering"としているので、label_numberingになります。
label_numbering: 採番
権限レポートでのモジュール名の表示は、規約でproject_module_<モジュール名>というシンボル名の定義が使われます。今回モジュール名はinit.rb(9行目)で"numbering"としているので、project_module_numberingになります。
project_module_numbering: 採番プラグイン
権限レポートでの権限名の表示は、規約でpermission_<権限項目名>というシンボル名の定義が使われます。今回権限名はinit.rb(10・11行目)で"view_numbering_prefix"および"manage_numbering_prefix"としているので、permission_view_numbering_prefixおよびpermission_manage_numbering_prefixになります。
permission_view_numbering_prefix: 採番系列の閲覧 permission_manage_numbering_prefix: 採番系列の管理
ja: label_numbering: 採番 project_module_numbering: 採番プラグイン permission_view_numbering_prefix: 採番系列の閲覧 permission_manage_numbering_prefix: 採番系列の管理
ja.yml編集後、サーバーを再起動し、[管理]>[ロールと権限]>[権限レポート]をクリックし、採番プラグインの権限設定を表示すると、次のように日本語化されています。
プロジェクトメニューは次のように日本語化されています。
一応、en.ymlを作成しておきます。同じシンボル名について英語メッセージを定義します。
en: label_numbering: Numbering project_module_numbering: Numbering plugin permission_view_numbering_prefix: View numbering prefix permission_manage_numbering_prefix: Manage numbering prefix
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
国際化対応(日本語化ファイル、英語化ファイル)
プラグイン作成(その3)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-03 redmine-2.3.0$
採番系列(固定文字列)データのモデルを作成し、一覧/新規/編集/削除のアクションに対応するビューおよびコントローラの処理を実装します。
次の表は、採番系列(固定文字列)データの要素と属性名・型を対応づけたものです。なお、プロジェクトIDはプロジェクトで作成(管理)している採番系列だけを表示するために仕様検討時から追加しました。
要素 | 名前 | 型 | 備考 |
---|---|---|---|
プロジェクトID | project_id | 整数(integer) | |
固定文字列 | fixed | 文字列(String) | |
現在採番した連番の最大数 | assigned | 整数(integer) | |
題名 | subject | 文字列(String) |
プロジェクトIDは、「Railsを知らない人のためのプラグイン開発ガイド」にある通りの名前と型を使っています。固定文字列は、最大255文字もあれば十分なのでStringにしました。採番の最大数は整数で、題名も最大255文字のStringとしました。
上述のデータ構成に従って採番系列モデルのスケルトンを作成します。
redmine-2.3.0$ ruby script/rails generate redmine_plugin_model redmine_numbering \ numbering_prefix project_id:integer fixed:string assigned:integer subject:string create plugins/redmine_numbering/app/models/numbering_prefix.rb create plugins/redmine_numbering/test/unit/numbering_prefix_test.rb create plugins/redmine_numbering/db/migrate/001_create_numbering_prefixes.rb redmine-2.3.0$
データベーススキーマの生成(マイグレーション)
redmine-2.3.0$ bundle exec rake redmine:plugins:migrate Migrating redmine_numbering (Redmine Numbering plugin)... == CreateNumberingPrefixes: migrating ======================================== -- create_table(:numbering_prefixes) -> 0.1099s == CreateNumberingPrefixes: migrated (0.1100s) =============================== redmine-2.3.0$
プロジェクトメニューから最初に呼ばれるindexアクションを実装します。スケルトン生成されたNumberingPrefixesコントローラのindexメソッドおよびNumberingPrefixesビューのindex.html.erbを実際の機能に置き換えます。
文献[1]に倣ってほぼそのまま実装します(コントローラ名、モデル名が違う程度です)。
1 class NumberingPrefixesController < ApplicationController 2 unloadable 3 menu_item :numbering 4 before_filter :find_project, :authorize 5 6 def index 7 @numbering_prefixes = NumberingPrefix.find( 8 :all, :conditions => ["project_id = #{@project.id}"]) 9 end 10 11 def new 12 end 13 14 def edit 15 end 16 17 private 18 19 def find_project 20 @project = Project.find(params[:project_id]) 21 rescue ActiveRecord::RecordNotFound 22 render_404 23 end 24 25 end
indexアクション(一覧表示)時に表示するHTMLのテンプレートです。文献[1]に倣って記述します。
1 <div class="contextual"> 2 <%= link_to_if_authorized(l(:label_numbering_prefixes_new), 3 {:action => 'new', :project_id => @project}, 4 :class => 'icon icon-add') %> 5 </div> 6 7 <h2><%=h l(:label_numbering) %></h2> 8 9 <% if @numbering_prefixes.blank? %> 10 <p class="nodata"><%= l(:label_no_data) %></p> 11 <% else %> 12 <table class="list"> 13 <thead> 14 <tr> 15 <th>#</th> 16 <th><%=h l(:field_fixed) %></th> 17 <th><%=h l(:field_assigned) %></th> 18 <th><%=h l(:field_subject) %></th> 19 </tr> 20 </thead> 21 <tbody> 22 <% @numbering_prefixes.each do |prefix| %> 23 <tr class="<%= cycle('odd', 'even') %>"> 24 <td><%= prefix.id %></td> 25 <td><%= prefix.fixed %></td> 26 <td><%= prefix.assigned %></td> 27 <td><%= prefix.subject %></td> 28 </tr> 29 <% end %> 30 </tbody> 31 </table> 32 <% end %>
label_numbering_prefixes_new: 新規採番系列
初期状態は、1件も採番系列データがないので、一覧表示は空っぽです。
新規作成機能を実装すればデータを作れるのですが、それではすべての機能が正しく動くまで個々の機能を確認できないので、あまりうれしくありません。かといって、Redmineの(Railsの?)テスト機能を使ってテストを書くのは現時点では敷居が高すぎます。
ということで、直接データベースにデータを作ってしまえ、と思います。
最初はSQLite3のプロンプトで直接テーブル"number_prefixes"にデータをINSERTすればよさそうだ、と思ったのですが、project_idを調べる必要があったり、INSERT文書くのは結構手間だったりと、あまりうまくはなさそうです。
そこで、railsのコンソール機能でデータを作成することにチャレンジします。
redmine-2.3.0$ ruby script/rails console Loading development environment (Rails 3.2.13) irb(main):001:0>
と、まずはターミナルのコマンドプロンプトからrailsのコンソールを起動します。
irb(main):001:0> Project.find(:all) Project Load (0.4ms) SELECT "projects".* FROM "projects" => [#<Project id: 1, name: "1号計画", description: "最初の計画。\r\n", homepage: "", is_public: true, parent_id: nil, created_on: "2013-04-21 23:08:26", updated_on: "2013-04-21 23:08:26", identifier: "hotel", status: 1, lft: 1, rgt: 2, inherit_members: false>] irb(main):002:0>
irb(main):002:0> prefix = NumberingPrefix.new() => #<NumberingPrefix id: nil, project_id: nil, fixed: nil, assigned: nil, subject: nil> irb(main):003:0> prefix.project_id = 1 => 1 irb(main):004:0> prefix.fixed = 'FIXED-1' => "FIXED-1" irb(main):005:0> prefix.assigned = 0 => 0 irb(main):006:0> prefix.subject = 'TEST SERIES 1' => "TEST SERIES 1" irb(main):007:0> prefix.save() (0.1ms) begin transaction SQL (0.9ms) INSERT INTO "numbering_prefixes" ("assigned", "fixed", "project_id", "subject") VALUES (?, ?, ?, ?) [["assigned", 0], ["fixed", "FIXED-1"], ["project_id", 1], ["subject", "TEST SERIES 1"]] (94.9ms) commit transaction => true irb(main):008:0>
いまRailsのコンソールで作成しデータベースに格納したデータが表示されています。
ラベルがメッセージ定義にないので、config/locales/ja.ymlに追記します。
field_fixed: 固定文字列 field_assigned: 採番済み番号
WEBrickサーバーを再起動します。
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番系列データのモデル生成と一覧のアクション実装 - 採番系列データのモデルをスケルトン生成。 - indexアクションの実装(他のアクション呼び出しは除く)
プラグイン作成(その4)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-04 redmine-2.3.0$
採番系列の新規作成を実装します。
新規作成から呼ばれるnewアクションを実装します。スケルトン生成されたNumberingPrefixesコントローラのnewメソッドおよびNumberingPrefixesビューのnew.html.erbを実際の機能に置き換えます。
def new @numbering_prefix=NumberingPrefix.new() end
新たにインスタンスを生成します。@numbering_prefixに格納しておき、ビュー(new.html.erb)で使用します。
1 <%= labelled_form_for :numbering_prefix, @numbering_prefix, 2 :url => project_numbering_prefixes_path(@project) do |f| %> 3 <%= render :partial => 'numbering_prefixes/form', :locals => {:form => f} %> 4 <%= f.submit l(:button_create) %> 5 <% end %>
labelled_form_forで、HTMLの入力フォームを生成します。フォームの内容は、第1引数で指定したキーでparamsに格納されます(params[:numbering_prefix]に格納)。フォームの各入力フィールドは、第2引数で指定したインスタンスのフィールド値が入ります。new()で生成したインスタンスは各フィールドがnilなので空欄となります。
第3引数に:urlをキーにしたハッシュでサブミット時のURLを指定します。フォームはPOSTとして扱われます。ルート設定においてルート名 project_numbering_prefixes は、numbering_prefixesのcreateアクションを呼ぶようになっています。ルート設定から該当部分を抜粋します。
project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#show PUT /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#update
3行目のrenderで、別なビューファイル(app/views/numbering_prefixes/_form.html.erb)を呼び出します。このように複数のビューで共通となるビュー処理は括り出して一元化します。
4行目はサブミットボタンを生成します。ボタンにはRedmineで定義済みのメッセージ:button_createを指定しています。
1 <%= error_messages_for 'numbering_prefix' %> 2 <div class="box"> 3 <p><%= form.text_field :fixed, :size => 100, :required => true %></p> 4 <p><%= form.text_field :subject, :size => 100, :required => false %></p> 5 </div>
フォームの入力フィールドを1つ1つ定義します。
3-4行目は、テキストフィールド入力欄を設け、フィールドの識別子、入力桁数、必須か否かを指定しています。フィールド欄のラベル(名前)を省略しているので、field_fixedおよびfield_subjectをシンボルとしてメッセージを取り出して使用します。
必須指定すると、入力欄に必須を示す記号(*)が付きます。ただしチェックは自動では行われないので、別途モデルクラスにチェックコードを追加します。
1 class NumberingPrefix < ActiveRecord::Base 2 unloadable 3 4 validates_presence_of :fixed 5 validates_length_of :fixed, :maximum => 255 6 validates_length_of :subject, :maximum => 255 7 end
4行目は、必須入力としたfixedをチェックするためのコードです。
5-6行目は、フィールドの文字列が255文字以下となることをチェックするコードです。
ビューのフォームで入力した値は、このモデルに定義したvalidatesでチェックされ、違反があればエラーとして表示されます。
見てくれは今一です。できればテキスト入力フィールドの表示位置は揃ってほしいのですが、今後の課題にしておき次に進みます。
[作成]ボタンを押すと、createアクションが実行されます。createメソッドはスケルトン生成されていないので、メソッド定義から追加します。
def create @numbering_prefix = NumberingPrefix.new(params[:numbering_prefix]) @numbering_prefix.project_id = @project.id @numbering_prefix.assigned = 0 if @numbering_prefix.save flash[:notice] = l(:notice_successful_create) redirect_to project_numbering_prefixes_path(@project) end end
フォームで入力された値は、params[:numbering_prefix]で取得できるので、これをnewの引数としてインスタンスを生成します。
プロジェクトIDはフォームでは入力しないデータなので、ここで指定しています。
インスタンスのsaveメソッドでデータベースに格納されます。
flashで作成に成功したメッセージを表示しています。
redirect_toで処理後のアクションを指定しています。project_numbering_prefixesはGETの場合一覧表示(indexアクション)となります。ルート設定から抜粋したものを次に示します。各アクションではprojectが必須なのでパラメータに渡しています。
project_numbering_prefixes GET /projects/:project_id/numbering_prefixes(.:format) numbering_prefixes#index
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番系列の新規作成を実装 -newアクション、createアクションの実装 -newのビューを実装 -モデルのバリデーション定義
プラグイン作成(その5)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-05 redmine-2.3.0$
一覧表示(indexアクション)のビューに編集および削除のリンクを追加し、編集(editアクション)および削除(destroyアクション)を実装します。
<tbody> <% @numbering_prefixes.each do |prefix| %> <tr class="<%= cycle('odd', 'even') %>"> <td><%= prefix.id %></td> <td><%= prefix.fixed %></td> <td><%= prefix.assigned %></td> <td><%= prefix.subject %></td> + <td><%= link_to l(:button_edit), + edit_project_numbering_prefix_path(@project, prefix), + :class => 'icon icon-edit' %></td> </tr> <% end %> </tbody>
行頭+で記載した3行が編集リンクの追加です。link_toメソッドで、1つ目の引数に編集メッセージ、2つ目の引数にeditアクションへのリンクのrouteパスを指定、editアクションのURLはパラメータとしてプロジェクトとデータIDを取るのでパスに2つパラメータを渡しています。3つ目の引数に、編集アイコンを表示する設定を指定しています。
ルート設定からルート名edit_project_numbering_prefixを抜粋したものを次に示します。
edit_project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id/edit(.:format) numbering_prefixes#edit
なお、edit_project_numbering_prefix_pathにパラメータを1つしか渡さないとエラーとなってしまいました。URL中に2つパラメータ(:project_idと:id)があるからでしょうか。このあたりは設定ルールがよくわからない部分です。
link_to のURL指定と生成されるURLの関係を調べてみました。プロジェクト識別子をmyprojとして、prefixのIDが2のリンクで確認しました。
HTTP 404エラー ActionController::RoutingError (No route matches {:action=>"edit", :controller=>"numbering_prefixes", :project_id=>#<NumberingPrefix id: 1, (以下略)
HTTP 404エラー ActionController::RoutingError (No route matches {:action=>"edit", :controller=>"numbering_prefixes", :project_id=>1}):
ActionController::RoutingError (No route matches {:action=>"edit", :controller=>"numbering_prefixes", :project_id=>#<Project id: 1, (以下略)
ルート設定のURLに必須パラメータが2つ(:project_idと:id)あります。ルート設定のURLに登場する順でルートパスの引数に指定するか、「キー => 値」で指定することができます。また、同じプロジェクト内でのリンクは:project_idの指定を省略することもできるようです。:project_id省略時は、:idを「キー => 値」で指定します。
<tbody> <% @numbering_prefixes.each do |prefix| %> <tr class="<%= cycle('odd', 'even') %>"> <td><%= prefix.id %></td> <td><%= prefix.fixed %></td> <td><%= prefix.assigned %></td> <td><%= prefix.subject %></td> <td><%= link_to l(:button_edit), edit_project_numbering_prefix_path(@project, prefix), :class => 'icon icon-edit' %></td> + <td><%=delete_link project_numbering_prefix_path(@project, prefix) %></td> </tr> <% end %> </tbody>
行頭+で記載した行が削除リンクの追加です。delete_linkはURLを引数にとり、deleteアクションへのリンクのrouteパスを指定しています。delete_linkメソッドはHTTP DELETEメソッドを使用するのでmethodの指定は不要です。また、アイコンとメッセージを自動で付けてくれます。
ルート設定でDELETEに対応するのがルート名project_numbering_prefixです。HTTPメソッドがGETかPUTかDELETEかで対応付くコントローラのアクションが異なります。ルート設定からルート名project_numbering_prefixを抜粋したものを次に示します。
project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#show PUT /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#update DELETE /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#destroy
編集のリンクと同様link_toで削除を実現することもできます。
<%= link_to l(:label_delete), {:action => 'destroy', :project_id => @project, :id => prefix.id}, :confirm => l(:text_are_you_sure), :method => :delete, :class => 'icon icon-del' %>
ラベルの文字列、削除のアイコン、HTTPのメソッド種別(DELETE)、削除操作時の確認ダイアログの表示などを逐一指定する必要があります。
この例では、リンク先URLをルート名に基づくパスではなく、コントローラ(上述例では省略)、アクション、オプションをハッシュで指定する方法で書いています。ルート名で書くことは可能かと思います(単に未確認)。
見栄えはともかく、編集と削除のリンクが表示されました。
editビューの中で採番系列データにアクセスするので、editメソッド呼び出し前に選択された採番系列データをインスタンス変数@numbering_prefixに保持します。
editビューのフォームをサブミットするとupdateアクションが呼ばれるので、updateメソッドを定義し実装します。
削除のアクション(destory)が呼ばれた時のdestoryメソッドを定義し実装します。
4 menu_item :numbering 5 before_filter :find_project, :authorize + 6 before_filter :find_numbering_prefix, :except => [:index, :new, :create] +38 def find_numbering_prefix +39 @numbering_prefix = NumberingPrefix.find_by_id(params[:id]) +40 render_404 unless @numbering_prefix +41 end 42 43 end
38-41行目:find_numbering_prefixメソッドを追加し、HTTPパラメータでidが渡されるので、idから採番系列データのインスタンスを取得し、インスタンス変数@numbering_prefixに格納します。
6行目:アクションメソッド実施前にこのメソッドが実行されるようbefore_filter定義を追加します。なお、idを扱わないアクション(一覧、新規、作成)は除外するようexceptで指定します。
+26 def update +27 @numbering_prefix.attributes = params[:numbering_prefix] +28 if @numbering_prefix.save +29 flash[:notice] = l(:notice_successful_update) +30 redirect_to project_numbering_prefixes_path(@project) +31 end +32 end
26-27行目:updateメソッドを定義し、フォームで入力したフィールドをHTTPパラメータから取得します。
28行目:採番系列データをデータベースに保存します。
30行目:保存に成功したら、一覧表示アクションを呼びます。route設定でコントローラのindexアクションに対応付いているのは以下のルート名です。そこで、redirect_to
でこのルート名に基づくパスを指定します。
project_numbering_prefixes GET /projects/:project_id/numbering_prefixes(.:format) numbering_prefixes#index
+38 def destroy +39 @numbering_prefix.destroy +40 redirect_to project_numbering_prefixes_path(@project) +41 end
38行目:destroyメソッドを定義
39行目:採番系列データを削除
40行目:一覧表示アクションを呼ぶ
1 <h2><%=h l(:label_numbering) %>#<%= @numbering_prefix.id %></h2> 2 3 <%= labelled_form_for :numbering_prefix, @numbering_prefix, 4 {:url => project_numbering_prefix_path(@project), 5 :html => {:multipart => true, :id => 'numbering_prefix_form'}} do |p| %> 6 <%= render :partial => 'numbering_prefixes/form', :locals => {:form => p} %> 7 <%= p.submit l(:button_edit) %> 8 <% end %>
4行目:フォームのサブミット時はコントローラのupdateが呼ばれるよう、ルート名project_numbering_prefixのパスを指定しています。サブミットはHTTPのPUTメソッドとなります。
以下にroutes設定からルート名がproject_numbering_prefixのURLと対応するコントローラ#アクションを抜粋します。
project_numbering_prefix GET /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#show PUT /projects/:project_id/numbering_prefixes/:id(.:format) numbering_prefixes#update
PUTのときは、updateアクションが対応する設定なので、editビューのサブミットによりコントローラのupdateメソッドが呼ばれることになります。
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番系列の編集・削除を実装 - 編集・削除のリンクを一覧表示に追加 - 編集の実装 - 削除の実装
プラグイン作成(その6)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-06 redmine-2.3.0$
ここからいよいよ採番項目データ側の実装に入ります。
採番項目(NumberingItem)のコントローラをスケルトン生成し、採番系列一覧表示から採番項目一覧表示へのリンクを用意するところまでを(その7)で実装します。
採番項目データ(numbering_item)用のコントローラのスケルトンを生成します。
redmine-2.3.0$ ruby script/rails generate redmine_plugin_controller redmine_numbering \ numbering_items index new show edit create plugins/redmine_numbering/app/controllers/numbering_items_controller.rb create plugins/redmine_numbering/app/helpers/numbering_items_helper.rb create plugins/redmine_numbering/test/functional/numbering_items_controller_test.rb create plugins/redmine_numbering/app/views/numbering_items/index.html.erb create plugins/redmine_numbering/app/views/numbering_items/new.html.erb create plugins/redmine_numbering/app/views/numbering_items/show.html.erb create plugins/redmine_numbering/app/views/numbering_items/edit.html.erb redmine-2.3.0$
コントローラを作成するだけではアクセスできません。そこで、ルート設定に採番項目コントローラに対応するルールを追加します。
1 Rails.application.routes.draw do 2 resources :projects do 3 resources :numbering_prefixes + 4 resources :numbering_items 5 end 6 end
4行目に追加しました。
numbering_itemsに関するルート設定を確認します。
redmine-2.3.0 rake routes|grep numbering_item project_numbering_items GET /projects/:project_id/numbering_items(.:format) numbering_items#index POST /projects/:project_id/numbering_items(.:format) numbering_items#create new_project_numbering_item GET /projects/:project_id/numbering_items/new(.:format) numbering_items#new edit_project_numbering_item GET /projects/:project_id/numbering_items/:id/edit(.:format) numbering_items#edit project_numbering_item GET /projects/:project_id/numbering_items/:id(.:format) numbering_items#show PUT /projects/:project_id/numbering_items/:id(.:format) numbering_items#update DELETE /projects/:project_id/numbering_items/:id(.:format) numbering_items#destroy redmine-2.3.0$
採番項目データの一覧表示(index)は、採番項目データのすべてを表示するのではなく、指定された採番系列に属するものだけを表示します。
そのためには、採番項目のindexアクションでは何らかの方法で採番系列を受け取る必要があります。
今回は、URLのリクエストパラメータに指定することとします。
<% @numbering_prefixes.each do |prefix| %> <tr class="<%= cycle('odd', 'even') %>"> <td><%= prefix.id %></td> + <td><%= link_to prefix.fixed, + project_numbering_items_path(:numbering_prefix_id => prefix) \%></td> <td><%= prefix.assigned %></td>
link_toのURL指定でproject_numbering_items_pathを指定し、そのオプションにルート設定では指定しないキーと値の組を指定すると、URLのリクエストパラメータ(URL末尾の?name=valueと示されるもの)としてリンクを生成します。ルート設定ではパラメータとして:project_idが含まれますが、省略すると現在のプロジェクトが設定されるので、ここではリクエストパラメータだけ指定しています。
次に生成されたリンクのURLを示します。
http://localhost:3000/projects/myproj/numbering_items?numbering_prefix_id=2
indexアクションが呼ばれた際の実装を追加します。
1 class NumberingItemsController < ApplicationController 2 unloadable 3 before_filter :find_project 4 before_filter :find_numbering_prefix 5 6 def index 7 end 8 9 def new 10 end 11 12 def show 13 end 14 15 def edit 16 end 17 18 private 19 20 def find_project 21 @project = Project.find(params[:project_id]) 22 end 23 24 def find_numbering_prefix 25 @numbering_prefix = NumberingPrefix.find(params[:numbering_prefix_id]) 26 end 27 28 end
採番項目の一覧表示では、検索の条件で採番系列のIDを必要とします。採番系列のIDはHTTPのリクエストパラメータで渡されてくるので、24-26行目で採番系列IDのインスタンスを取得します。4行目でbefore_filterで各アクションメソッドが実行されるまえに呼ばれるようにします。
プロジェクトIDについても同様に20-22行目でインスタンスを取得します。
ここでは実験コードとして、採番項目コントローラのindexメソッドでセットしたインスタンス変数@projectと@numbering_prefixを表示する実装を記述します。実際の処理は、モデルを生成してからとなります。
1 <h2>NumberingItemsController#index</h2> 2 <p><%= "project_id = #{@project.id}" %></p> 3 <p><%= "numbering_prefix_id = #{@numbering_prefix.id}" %></p>
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番項目コントローラの生成 動作確認用のindexアクションとビューの実装
プラグイン作成(その7)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-07 redmine-2.3.0$
採番項目データを作成し、採番項目ビューのindex(一覧表示)を実装します。
次の表は、採番項目データの要素と属性名・型を対応づけたものです。採番は採番系列単位で連番になります。そして採番された各番号ごとに説明を持ちます。
次の表は、採番した番号とその内容を保持するテーブルです。
要素 | 名前 | 型 | 備考 |
---|---|---|---|
固定文字列ID | numbering_prefix_id | 整数(integer) | |
連番 | number | 整数(integer) | |
題名 | subject | 文字列(string) | |
詳細 | description | 文字列(text) |
redmine-2.3.0$ ruby script/rails generate redmine_plugin_model redmine_numbering \ numbering_item numbering_prefix_id:integer number:integer subject:string \ description:text create plugins/redmine_numbering/app/models/numbering_item.rb create plugins/redmine_numbering/test/unit/numbering_item_test.rb create plugins/redmine_numbering/db/migrate/002_create_numbering_items.rb redmine-2.3.0$
プラグインのデータベース・マイグレーション実行
redmine-2.3.0$ bundle exec rake redmine:plugins:migrate Migrating redmine_numbering (Redmine Numbering plugin)... == CreateNumberingItems: migrating =========================================== -- create_table(:numbering_items) -> 0.0343s == CreateNumberingItems: migrated (0.0344s) ================================== redmine-2.3.0$
採番項目コントローラのindexアクションを実装します。
1 class NumberingItemsController < ApplicationController 2 unloadable 3 before_filter :find_project + 4 before_filter :find_numbering_prefix, :authorize 5 6 def index + 7 @numbering_items = NumberingItem.find( + 8 :all, :conditions => ["numbering_prefix_id = #{@numbering_prefix.id}"]) 9 end :(中略) 22 def find_project 23 @project = Project.find(params[:project_id]) +24 rescue ActiveRecord::RecordNotFound +25 render_404 26 end 27 28 def find_numbering_prefix 29 @numbering_prefix = NumberingPrefix.find(params[:numbering_prefix_id]) +30 rescue ActiveRecord::RecordNotFound +31 render_404 32 end
行頭の+の箇所を追記しました。4行目はプロジェクトに権限を持つユーザーのみ利用可能とする制限を付けました。(採番系列コントローラと同様)
7,8行目は、一覧表示のときに渡された採番系列IDに対応する採番項目を検索しています。
24,25および30,31はエラー処理を追加しました(採番系列コントローラと同様)。
採番項目ビューのindexを実装します。
1 <div class="contextual"> 2 <%= link_to_if_authorized(l(:label_numbering_items_new), 3 {:action => 'new', :project_id => @project, 4 :numbering_prefix_id => @numbering_prefix}, 5 :class => 'icon icon-add') %> 6 </div> 7 8 <h2><%=h l(:label_numbering) %></h2> 9 10 <% if @numbering_items.blank? %> 11 <p class="nodata"><%= l(:label_no_data) %></p> 12 <% else %> 13 <table class="list"> 14 <thead> 15 <tr> 16 <th>#</th> 17 <th><%=h l(:field_subject) %></th> 18 </tr> 19 </thead> 20 <tbody> 21 <% @numbering_items.each do |item| %> 22 <tr class="<%= cycle('odd', 'even') %>"> 23 <td><%= item.id %></td> 24 <td><%= link_to(item.subject, 25 project_numbering_item_path(:id => item, 26 :numbering_prefix_id => @numbering_prefix)) %></td> 27 </tr> 28 <% end %> 29 </tbody> 30 </table> 31 <% end %>
1-6行目は、権限があるユーザーの場合、採番項目の新規作成リンクを表示するものです。
全体は、採番系列ビューの一覧表示とほぼ類似しています。
ここで動作確認すると、採番項目の新規作成リンクが表示されません。権限の設定でまだ採番項目について定義していないからです。
9 project_module :numbering do 10 permission :view_numbering_prefix, :numbering_prefixes => [:index] 11 permission :manage_numbering_prefix, 12 :numbering_prefixes => [:new, :edit, :create, :update, :destroy], 13 :require => :member +14 permission :view_numbering_item, :numbering_items => [:index, :show] +15 permission :manage_numbering_item, +16 :numbering_items => [:new, :edit, :create, :update, :destroy, :preview], +17 :require => :member 18 end
行頭の+箇所が追加分です。コントローラに管理権限を定義しています。この定義をしておかないと、コントローラ(とその先のビュー)で権限を必要とする記述をしても権限なしと扱われます。
1 ja: 2 label_numbering: 採番 3 project_module_numbering: 採番プラグイン 4 permission_view_numbering_prefix: 採番系列の閲覧 5 permission_manage_numbering_prefix: 採番系列の管理 + 6 permission_view_numbering_item: 採番項目の閲覧 + 7 permission_manage_numbering_item: 採番項目の管理 8 label_numbering_prefixes_new: 新規採番系列 + 9 label_numbering_items_new: 新規採番 10 11 field_fixed: 固定文字列 12 field_assigned: 採番済み番号 +13 field_subject: 題名
6,7行目は、権限レポート([管理]>[ロールと権限]>[権限レポート])で表示される権限名のメッセージです。
9行目は、indexビューで右上に表示する新規作成のリンクのラベルメッセージです。
サーバーを再起動し、権限レポートを開き、採番プラグインを表示した画面を次に示します。
デフォルトではすべて権限なしなので、必要な権限を追加します。
[保存]ボタンを押すのをわすれないように。つい忘れて「あれ、チェック付けたのに表示されない」といらぬデバッグに入り込むと無駄な時間を費やしてしまいます。
採番系列の時と同様に、テスト用データをデータベースに入れます。
redmine-2.3.0$ ruby script/rails console Loading development environment (Rails 3.2.13) irb(main):001:0> NumberingPrefix.find(:all) NumberingPrefix Load (0.3ms) SELECT "numbering_prefixes".* FROM "numbering_prefixes" => [#<NumberingPrefix id: 1, project_id: 1, fixed: "DOG-ONE-", assigned: 0, subject: "SERIES ONE">] irb(main):002:0> item = NumberingItem.new() => #<NumberingItem id: nil, numbering_prefix_id: nil, number: nil, subject: nil, description: nil> irb(main):003:0> item.numbering_prefix_id = 1 => 0 irb(main):004:0> item.number = 1 => 1 irb(main):005:0> item.subject = 'Item number 1' => "Item number 1" irb(main):006:0> item.description = 'This is a first assigned number item' => "This is a first assigned number item" irb(main):007:0> item.save() (0.2ms) begin transaction SQL (10.9ms) INSERT INTO "numbering_items" ("description", "number", "numbering_prefix_id", "subject") VALUES (?, ?, ?, ?) [["description", "This is a first assigned number item"], ["number", 1], ["numbering_prefix_id", 0], ["subject", "Item number 1"]] (115.6ms) commit transaction => true irb(main):008:0>
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番項目データ作成と一覧表示の実装
プラグイン作成(その8)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-08 redmine-2.3.0$
採番項目ビュー/コントローラの残りのアクションを実装します。
新規作成は、newアクション→newビューでフォーム入力とサブミット→createアクション の流れで行われます。実装方法は、採番系列コントローラ(numbering_prefixes_controller.rb)とほぼ同様です。
12 def new 13 logger.debug("[DEBUG]NumberingItemsController#new") 14 @numbering_item = NumberingItem.new() 15 end 16 17 def create 18 logger.debug("[DEBUG]NumberingItemsController#create") 19 numbering_item = NumberingItem.new(params[:numbering_item]) 20 numbering_item.numbering_prefix_id = @numbering_prefix.id 21 @numbering_prefix.assigned = @numbering_prefix.assigned + 1 22 numbering_item.number = @numbering_prefix.assigned 23 24 numbering_item.save() and @numbering_prefix.save() 25 flash[:notice] = l(:notice_successful_create) 26 redirect_to project_numbering_item_path(:id => numbering_item, :numbering_prefix_id = @numbering_prefix) 27 rescue ActiveRecord::StaleObjectError 28 flash.now[:error] = l(:notce_locking_conflict) 29 end
13,18行目はログ出力文なので記述不要です。
newアクションでは空のインスタンスを作ってnewのビューに渡します(14行目)。
createアクションでは、フォームに入力された内容をもとにインスタンスを生成(19行目)、フォームで入力しない属性に値を補って(20-22行目)からデータを保存し、詳細表示アクションを呼びます(26行目)。
1 <%= labelled_form_for :numbering_item, @numbering_item, 2 :url => project_numbering_items_path( 3 :numbering_prefix_id => @numbering_prefix.id) do |f| %> 4 <h2><%= "固定文字列: #{@numbering_prefix.fixed}" %></h2> 5 <%= render :partial => 'numbering_items/form', :locals => {:form => f} %> 6 <%= f.submit l(:button_create) %> 7 <% end %>
1 <%= error_messages_for 'numbering_item' %> 2 <div class="box"> 3 <p><%= form.text_field :subject, :size => 100, :required => true %></p> 4 <p><%= form.text_field :description, :size => 100, :required => false %></p> 5 </div>
詳細表示用に、リンクURL中の:idからnumbering_itemインスタンスを取得するprivateメソッドfind_numbering_itemを定義し、:idをURL中に含むアクションメソッド(edit, show, update, destroy)を指定したbefore_filterを記述します。
before_filter :find_numbering_item, :except => [:index, :new, :create, :preview] : def find_numbering_item @numbering_item = NumberingItem.find(params[:id]) rescue ActiveRecord::RecordNotFound render_404 end
1 <div class="contextual"> 2 <%= link_to_if_authorized(l(:button_edit), 3 {:action => 'edit', :project_id => @project, :id => @numbering_item.id, 4 :numbering_prefix_id => @numbering_prefix.id}, 5 :class => 'icon icon-edit') %> 6 <%= link_to_if_authorized(l(:button_delete), 7 {:action => 'destroy', :project_id => @project, :id => @numbering_item.id, 8 :numbering_prefix_id => @numbering_prefix.id}, 9 :comfirm => l(:text_are_you_sure), :method => :delete, 10 :class => 'icon icon-del') %> 11 </div> 12 13 <h2><%=h l(:label_numbering_item) %>#<%= @numbering_item.id %></h2> 14 15 <div class="issue"> 16 17 <div> 18 <h3><%= @numbering_prefix.fixed %> 19 <%= sprintf('%05d', @numbering_item.number) %></h3> 20 </div> 21 22 <div class="subject"> 23 <h3><%= @numbering_item.subject %></h3> 24 </div> 25 26 <% unless @numbering_item.description.blank? %> 27 <p><strong><%=l(:field_description) %></strong></p> 28 <div class="wiki"> 29 <%= textilizable @numbering_item.description %> 30 </div> 31 <% end %> 32 33 </div>
1-11行目は右上に編集および削除リンクを表示します。削除時は削除後に一覧表示画面に遷移させたいので、一覧表示画面に必要なリクエストパラメータnumbering_prefix_idを付加しています。編集時は編集画面の表示に採番系列の固定文字列を使うかもしれないので、同じくリクエストパラメータnumbering_prefix_idを付加しています。
18-19行目は、採番系列の固定文字列と採番項目の一連番号(5桁でゼロサプレスなし)を結合した「採番」を表示します。
def destroy @numbering_item.destroy redirect_to project_numbering_items_path(:numbering_prefix_id => @numbering_prefix) end
インスタンスを削除後、一覧表示に移ります。
4 before_filter :find_numbering_prefix, :authorize, :except => :update (中略) 40 def update 41 logger.debug("[DEBUG]NumberingItemsController#update") 42 @numbering_item.attributes = params[:numbering_item] 43 if @numbering_item.save 44 flash[:notice] = l(:notice_successful_update) 45 redirect_to project_numbering_item_path(@project, @numbering_item.id, 46 :numbering_prefix_id => @numbering_item.numbering_prefix_id) 47 end 48 rescue ActiveRecord::StaleObjectError 49 flash.now[:error] = l(:notice_locking_conflict) 50 end
updateアクションは、editビューのフォームのサブミットから呼ばれますが、フォームのサブミット時にHTTPリクエストパラメータとして numbering_prefix_idを指定する方法が分からなかったので、before_filterでfind_numbering_prefixの適用除外にupdateメソッドを指定し(4行目)、@numbering_itemの属性からnumbering_prefix_idを取得しています。
1 <h2><%=h l(:label_numbering_item) %>#<%= @numbering_item.id %></h2> 2 3 <%= labelled_form_for :numbering_item, @numbering_item, 4 :url => project_numbering_item_path(@project), 5 :html => {:multipart => true, :id => 'numbering_item_form'} do |f| %> 6 <%= render :partial => 'numbering_items/form', :locals => {:form => f} %> 7 <%= f.submit l(:button_edit) %> 8 <% end %>
ざっと一通り操作してみたところ、おおむね機能はしています。表示(見栄え)、操作性にはところどころ難点があるもののまずは区切りとしてよさそうです。
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番項目の残りのアクションを実装
プラグイン作成(その9)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-09 redmine-2.3.0$
見栄えの改修に取り組んでみます。
とりあえず気になる箇所は、見出し行の欠落箇所と、数値が左寄せになっている点です。
diff --git a/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb b/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb index 296b4c3..b6ef329 100644 --- a/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb +++ b/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb @@ -16,6 +16,7 @@ <th><%=h l(:field_fixed) %></th> <th><%=h l(:field_assigned) %></th> <th><%=h l(:field_subject) %></th> + <th colspan="2"><%=h l(:field_manage) %></th> </tr> </thead> <tbody> diff --git a/plugins/redmine_numbering/config/locales/en.yml b/plugins/redmine_numbering/config/locales/en.yml index f1310c8..73c90be 100644 --- a/plugins/redmine_numbering/config/locales/en.yml +++ b/plugins/redmine_numbering/config/locales/en.yml @@ -11,4 +11,5 @@ en: field_fixed: Fixed text field_assigned: Latest number assigned field_subject: Subject + field_manage: Management diff --git a/plugins/redmine_numbering/config/locales/ja.yml b/plugins/redmine_numbering/config/locales/ja.yml index be5943c..696bb40 100644 --- a/plugins/redmine_numbering/config/locales/ja.yml +++ b/plugins/redmine_numbering/config/locales/ja.yml @@ -11,4 +11,5 @@ ja: field_fixed: 固定文字列 field_assigned: 採番済み番号 field_subject: 題名 + field_manage: 管理
最初右寄せにしたのですが、隣の題名とくっつきすぎて今一なので中央寄せにしてみました。これなら左寄せのままでもいいかもしれません。
diff --git a/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb b/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb index b6ef329..f6ec2e2 100644 --- a/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb +++ b/plugins/redmine_numbering/app/views/numbering_prefixes/index.html.erb @@ -25,7 +25,7 @@ <td><%= prefix.id %></td> <td><%= link_to prefix.fixed, project_numbering_items_path(:numbering_prefix_id => prefix) %></td> - <td><%= prefix.assigned %></td> + <td align="center"><%= prefix.assigned %></td> <td><%= prefix.subject %></td> <td><%= link_to l(:button_edit), edit_project_numbering_prefix_path(@project, prefix),
diff --git a/plugins/redmine_numbering/app/views/numbering_prefixes/_form.html.erb b/plugins/redmine_numbering/app/views/numbering_prefixes/_form.html.erb index a90bd11..fd8e6cd 100644 --- a/plugins/redmine_numbering/app/views/numbering_prefixes/_form.html.erb +++ b/plugins/redmine_numbering/app/views/numbering_prefixes/_form.html.erb @@ -1,5 +1,5 @@ <%= error_messages_for 'numbering_prefix' %> -<div class="box"> +<div class="box tabular"> <p><%= form.text_field :fixed, :size => 100, :required => true %></p> <p><%= form.text_field :subject, :size => 100, :required => false %></p> </div>
現在は、詳細表示で初めて「固定文字列-連番」を表示しているので、これを一覧表示ビューにも表示します。
diff --git a/plugins/redmine_numbering/app/views/numbering_items/index.html.erb b/plugins/redmine_numbering/app/views/numbering_items/index.html.erb index 581af75..e2098e2 100644 --- a/plugins/redmine_numbering/app/views/numbering_items/index.html.erb +++ b/plugins/redmine_numbering/app/views/numbering_items/index.html.erb @@ -14,6 +14,7 @@ <thead> <tr> <th>#</th> + <th><%=h l(:label_numbering) %></th> <th><%=h l(:field_subject) %></th> </tr> </thead> @@ -21,6 +22,7 @@ <% @numbering_items.each do |item| %> <tr class="<%= cycle('odd', 'even') %>"> <td><%= item.id %></td> + <td><%= @numbering_prefix.fixed %><%= sprintf('%05d', item.number) %></td> <td><%= link_to(item.subject, project_numbering_item_path(:id => item, :numbering_prefix_id => @numbering_prefix)) %></td> </tr>
列を1つ増やし、そこに採番の固定文字列・連番(5桁ゼロサプレスなし)を表示しただけのものです。
これはモデルに新たな属性の追加を伴う(=データベース・マイグレーションが必要)ため、今後の対応とします。
「項目名 項目」のように表形式で表示します。基本<table>タグで表示しますが、そのままでは見栄えが今一です。<Redmineインストールディレクトリ>/public/application.css をさっと眺めて使えそうな定義を調べます。
<div class="box"> <table class="attributes"> <tbody> <tr> <th><%=h l(:label_numbering) %></th> <td><strong><%= @numbering_prefix.fixed %><%= sprintf('%05d', @numbering_item.number) %></strong></td> </tr> <tr> <th><%=h l(:field_subject) %></th><td><%= @numbering_item.subject %></td> </tr> <% unless @numbering_item.description.blank? %> <tr> <th><%=h l(:field_description) %></th><td><%= textilizable @numbering_item.description %></td> </tr> <% end %> </tbody> </table> </div>
div.boxを使うと、背景がグレーで囲われるので何となくこれを使いました。
table.attributesは、列と列とが適度に間隔をあけて表示されたので、これを使いました。
見出しの「採番項目#22」の採番項目の文字列部分をindexへのリンクとします。
<h2><%=link_to_if_authorized(l(:label_numbering_item), {:action => 'index', :project_id => @project, :numbering_prefix_id => @numbering_prefix.id}) %>#<%= @numbering_item.id %></h2>
1.および2.の対応結果の画面を次に示します。
ここまでの成果をgitリポジトリに登録します。
redmine-2.3.0$ git add . redmine-2.3.0$ git commit
emacsエディタが立ち上がるので、コミット理由を記述します。
採番項目の残りのアクションを実装
プラグイン作成(その10)はここで完了なので、タグを打っておきます。
redmine-2.3.0$ git tag milestone-10 redmine-2.3.0$
開発の途上では、モデルの属性(データベースのカラム)を変更することが時折発生します。その際は、いったん次のコマンドでプラグインが使用するデータベースのテーブルを破棄します。
redmine-2.3.0$ bundle exec rake redmine:plugins:migrate NAME=redmine_numbering \ VERSION=0 (in /home/torutk/develop/redmine/redmine-2.3.0) Migrating redmine_numbering (Redmine Numbering plugin)... == CreateNumberingItems: reverting =========================================== -- drop_table("numbering_items") -> 0.0014s == CreateNumberingItems: reverted (0.0015s) ================================== == CreateNumberingPrefixes: reverting ======================================== -- drop_table("numbering_prefixes") -> 0.0011s == CreateNumberingPrefixes: reverted (0.0012s) ===============================
データベース上から2つのテーブル(numbering_itemsとnumbering_prefixes)が削除されました。
次に、変更するモデルのスケルトンを生成し直します(または既存のモデルの実装を変更します)。
redmine-2.3.0$ ruby script/rails generate redmine_plugin_model redmine_numbering \ numbering_prefix project_id:integer fixed:string assigned:integer subject:string conflict plugins/redmine_numbering/app/models/numbering_prefix.rb Overwrite /home/toru/redmine/1.redmine-2.3.0/plugins/redmine_numbering/app/models/ numbering_prefix.rb? (enter "h" for help) [Ynaqdh] Y force plugins/redmine_numbering/app/models/numbering_prefix.rb identical plugins/redmine_numbering/test/unit/numbering_prefix_test.rb create plugins/redmine_numbering/db/migrate/007_create_numbering_prefixes.rb redmine-2.3.0$
ここでは、既存の(変更前の)モデルクラスを上書きするため[Y]を入力しています。migrate用のデータベース作成スクリプトは、既存のスクリプトの番号より1つ大きい番号(上述の例では007)で生成されます。古いスクリプトは削除します。
データベース上に新しいテーブルを作成するため、migrateを実行します。
redmine-2.3.0$ bundle exec rake redmine:plugins:migrate Migrating redmine_numbering (Redmine Numbering plugin)... == CreateNumberingItems: migrating =========================================== -- create_table(:numbering_items) -> 0.0889s == CreateNumberingItems: migrated (0.0891s) ================================== == CreateNumberingPrefixes: migrating ======================================== -- create_table(:numbering_prefixes) -> 0.0024s == CreateNumberingPrefixes: migrated (0.0025s) ===============================
データベースの破棄をrakeコマンドで実行します。
redmine-2.3.0$ bundle exec rake db:drop redmine-2.3.0$ bundle exec rake db:migrate : redmine-2.3.0$ bundle exec rake redmine:plugins:migrate :
redmine-2.3.0$ sqlite3 db/redmine.db sqlite>
sqlite> .table attachments news auth_sources numbering_items : slite>
sqlite> .q redmine-2.3.0$
sqlite> .h .backup ?DB? FILE Backup DB (default "main") to FILE .bail ON|OFF Stop after hitting an error. Default OFF : sqlite>
sqlite> .schema numbering_items CREATE TABLE "numbering_items" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "project_id" integer, "number" integer, "subject" varchar(255), "description" text); sqlite>
sqlite> .show echo: off explain: off headers: off mode: list nullvalue: "" output: stdout separator: "|" width: sqlite>
sqlite> sqlite>
権限がないユーザー(あるいは非ログイン)では表示されないリンクですが、権限があるユーザーにもかかわらず表示されないことがありました。このメソッドは、プロジェクトの権限を見ているため
ことで表示されず、四苦八苦しました。
developmentモード・WEBRick限定なデバッグ(稚拙なデバッグ技術)としてputs(p)メソッドを使用した方法があります。
例
puts "[DEBUG]@prefix=#{@prefix.attributes}"
rubyでは、文字列中に変数の評価を展開するための#{変数名}という記法があります。
WEBRickのコンソール出力
[DEBUG]@prefix={"id"=>4, "project_id"=>1, "fixed"=>"ALFA-試験-", "assigned"=>nil, "subject"=>"試験関係での採番"}
Redmine(Rails)にはロギングが定義されているので、putsよりはログを使う方がよいです。
logger.debug("[DEBUG]NumberingPrefixesController#find_numbering_prefix")
Webアプリケーションは、とにかくページ遷移が面倒です。本ページのプラグインづくりをしていて一番はまったことは、ページリンクでエラーになることです。呼ばれるはずのアクションメソッドが呼ばれない、リンクの指定(主にビュー側の記述)がエラーとなる、パラメータが渡ってこないのでnilでエラーになる、など様々です。原因がなかなか判明しなくて、コントローラの全メソッドにロギングを埋め込んでやっとわかったということもありました。
ということで、リンクをしっかり押さえるとRails力が上がると感じました。