Catalyst の Helper Script の作成方法について説明していきます。
HelperScriptというと広義において、テストサーバーやテスト実行のスクリプトも含みますが、
今回は、コンポーネントやその他のファイルを生成するヘルパースクリプトを扱います。
このようなスクリプトをgeneratorと呼ぶことにします。
Catalystでは script/myapp_create.pl がそれにあたります。
Catalystでは、このgeneratorを使用してコンポーネントを生成していきます。
生成すると、ファイルの中身は、すでにデフォルトの内容が記述されています。
毎回同じような処理を書く場合、その処理内容をデフォルトとして、コンポーネントを生成するヘルパーを作っておくといいでしょう。
今回は、このヘルパースクリプトの作り方を簡単に解説していきます。
あなたが、MyAppというアプリケーションのスケルトンをCatalystで作成した場合、
script/myapp_create.pl というファイルが生成されます。
このスクリプトを使用して、ファイルを生成していきます。
script/myapp_create.pl controller Hoge
上記のように、第一引数にコンポーネントタイプを指定します。
コンポーネントタイプは model, view, controller の中から選択します。
第二引数にコンポーネント名を指定します。
デフォルトのHelperによる、コンポーネントの生成が実行されます。
上記の例では、MyApp::C::Hoge というパッケージのファイル、及び
そのパッケージのためのテストスクリプトが自動生成されます。
script/myapp_create.pl view MyTT TT
この例では、コンポーネントタイプにview、コンポーネント名がMyTTですので
MyApp::V::MyTT が生成されます。
先ほどの例と違うのは第三引数があることです。
第三引数はヘルパー名となります。
この場合、Catalyst::Helper::View::TT を使用して、
MyApp::V::MyTT を生成する、という命令になります。
script/myapp_create.pl model MyCDBI CDBI dbi:mysql:database user pass
コンポーネントタイプがmodelで、コンポーネント名がMyCDBIですので
MyApp::M::MyCDBIが生成されます。
ヘルパー名がCDBIですので、Catalyst::Helper::Model::CDBI を使用して、ファイルを生成することになります。
ヘルパー名より後ろの値は引数としてヘルパーに渡されます。
今回は、コンポーネント生成用のHelperScriptを作成してみます。
コンポーネントと無関係のファイル生成用のHelperもありますが、
今回は言及しません。Catalyst::Helper::Prototypeなどがそれにあたりますので、
興味のある人は処理内容を見てみるといいでしょう。
名前の付け方は上の説明で理解できたと思います。
Catalyst::Helper::コンポーネントタイプ::ヘルパー名という形になります。
今回は controller を 生成するスクリプトを書きます。
ヘルパー名はMyScaffoldとしますので、名前は
Catalyst::Helper::Controller::MyScaffoldとなります。
コンポーネントを生成するタイプのhelperを作成する場合、
まずはじめに、mk_compclass というメソッドを定義しなければいけません。
package Catalyst::Helper::Controller::MyScaffold; use strict; use warnings; sub mk_compclass { my ( $self, $helper, @args ) = @_; my $filepath = $helper->{file}; $helper->render_file('hoge', $filepath); } 1; __DATA__ __hoge__ package [% class %]; use strict use base 'Catalyst::Base'; sub list : Local { ... } sub add : Local { ... } 1;
mk_compclassに引数として渡されます。
このヘルパーオブジェクトを使用してファイルを生成していくことになります。
使用するのは主に、render_file メソッドです。
$helper->render_file(テンプレート, ファイルパス);
render_fileは、Template-Toolkitを使用して、指定されたパスに
ファイルを書き出すメソッドです。
テンプレートはヘルパークラス内に書きます。
まず、テンプレートに名前を付けます。
今回は hoge という名前にします。
ヘルパークラスの最後、__DATA__ 以降に、 __hoge__という行を足し
それ以降にTemplate-Toolkit用のテンプレートを書いていきます。
package Catalyst::Helper::Controller::MyScaffold; use strict; use warnings; sub mk_compclass { ... } 1; __DATA__ __hoge__ #ここからテンプレート package [% class %]; use strict use base 'Catalyst::Base'; # Template-Toolkit 用のテンプレートを書く 1;
この場合、$helper->render_file('hoge', $filepath) とすると、
__hoge__からファイル末端までを、一つのテンプレートとして処理し、
生成されたデータを、指定された $filepath に出力します。
テンプレートには $helper オブジェクト自身のハッシュが渡されますので
$helperオブジェクトが持つデータをテンプレートから呼び出せます。
$helperは次のような値を持っています。
$helper->{app}
アプリケーション名です。例題の場合は、MyAppが入ります。
$helper->{base}
アプリケーションのルートディレクトリのパスが入ります。
$helper->{class}
生成するコンポーネントのクラス名が入ります。
script/myapp_create.pl controller Hoge
として実行された場合には、
MyApp::C::Hoge という文字列が入ります。
$helper->{file}
生成するコンポーネントのファイルパスが入ります。
上記例題では、このファイルパスをそのままrender_fileに渡しています。
これらの値を
[% class %] [% file %] [% app %]
というような形で
テンプレートから呼び出すことが出来ます。
これら以外の値をテンプレート内で使用したい場合、
sub mk_compclass { my ($self, $helper, @args) = @_; $helper->{foobar} = "foobar"; $helper->render_file('hoge', $helper->{file}); }
のように、$helperのハッシュに値を追加してからrender_fileを呼びます。
テンプレート内からは [% foobar %] で値を呼び出せるようになります。
Webアプリケーションを作成するとき、データベースの各テーブルに関するCRUDな処理を毎回書いていると思います。
例えば、User テーブルがあった場合に、
userのリストを表示する機能、userを追加する機能、編集する機能、削除する機能などです。
こういった処理は、特に管理システムのようなアプリケーションで頻出します。
別のテーブル、例えばCompanyテーブルがあった場合にも、
そのテーブルに関するCRUDな処理を書く場合、
Userテーブルと共通箇所の多い処理を書くことがほとんどです。
このように毎回書かなければいけない処理を、デフォルトとして既にファイルに記述された状態で
スクリプトを生成するHelperをScaffoldといって、Ruby On Rails や、 Catalyst の Plugin で実装されています。
これらの Scaffold はコントローラーのスクリプトだけでなく、
それから必要とされる、テンプレートファイルまでも生成します。
Scaffold とは 「足場」という意味です。
例えば、毎回次のような処理を書いているとします。
package MyApp::C::User; use strict; use warnings; use base qw/Catalyst::Base/; sub list : Local { my ( $self, $c ) = @_; my @records = MyApp::M::User->retrieve_all; $c->stash->{records} = \@records; $c->stash->{template} = "User/list.tt"; } sub add : Local { my ( $self, $c ) = @_; $c->stash->{template} = "User/add.tt"; } sub do_add : Local { my ( $self, $c ) = @_; $c->form( # validation ); my $result = $c->form; if ( $result->has_invalid or $result->has_missing ) { $c->stash->{result} = $result; $c->detach('add'); } MyApp::M::User->create_from_form($result); $c->stash->{template} = "User/do_add.tt"; } sub edit : Local { my ( $self, $c, $id ) = @_; my $record = MyApp::M::User->retrieve($id); $c->stash->{record} = $record; $c->stash->{template} = "User/edit.tt"; } sub do_edit : Local { my ( $self, $c, $id ) = @_; $c->form( # validation ); my $result = $c->form; if ( $result->has_invalid or $result->has_missing ) { $c->stash->{result} = $result; $c->detach('edit'); } MyApp::M::User->retrieve($id)->update_from_form($resutl); $c->stash->{template} = "User/do_edit.tt"; } sub delete : Local { my ( $self, $c, $id ) = @_; my $record = MyApp::M::User->retrieve($id); $record->delete; $c->stash->{template} = "User/delete.tt"; } 1;
これはUserテーブルを扱う場合の処理ですが、
Companyテーブルを扱う場合も、一部を変更するだけで、ほとんどの箇所を使いまわせます。
毎回書くのは面倒なので、これを簡単に生成するヘルパーを作ります
package Catalyst::Helper::Controller::MyScaffold; use strict; use warnings; sub mk_compclass { my ($self, $helper, $table) = @_; my $filepath = $helper->{file}; $helper->{table} = $table; $helper->render_file('compclass', $filepath); } 1; __DATA__ __compclass__ package [% class %]; use strict; use base 'Catalyst::Base'; sub list : Local { my ( $self, $c ) = @_; my @records = [% app %]::M::[% table %]->retrieve_all; $c->stash->{records} = \@records; $c->stash->{template} = "[% table %]/list.tt"; } sub add : Local { my ( $self, $c ) = @_; $c->stash->{template} = "[% table %]/add.tt"; } sub do_add : Local { my ( $self, $c ) = @_; $c->form( # validation ); my $result = $c->form; if ( $result->has_invalid or $result->has_missing ) { $c->stash->{result} = $result; $c->detach('add'); } [% app %]::M::[% table %]->create_from_form($result); $c->stash->{template} = "[% table %]/do_add.tt"; } sub edit : Local { my ( $self, $c, $id ) = @_; my $record = [% app %]::M::[% table %]->retrieve($id); $c->stash->{record} = $record; $c->stash->{template} = "[% table %]/edit.tt"; } sub do_edit : Local { my ( $self, $c, $id ) = @_; $c->form( # validation ); my $result = $c->form; if ( $result->has_invalid or $result->has_missing ) { $c->stash->{result} = $result; $c->detach('edit'); } [% app %]::M::[% table %]->retrieve($id)->update_from_form($resutl); $c->stash->{template} = "[% table %]/do_edit.tt"; } sub delete : Local { my ( $self, $c, $id ) = @_; my $record = [% app %]::M::[% table %]->retrieve($id); $record->delete; $c->stash->{template} = "[% table %]/delete.tt"; } 1;
このヘルパーを使用して、CRUD用のControllerを生成することによって、
毎回書いている処理を書く手間がはぶけます。
次のように使います。
script/create.pl controller User MyScaffold User
コンポーネント以外のファイルを生成することも可能です。
list.ttも一緒に生成してみます。
package Catalyst::Helper::Controller::MyScaffold; use strict; use warnings; use File::Spec; sub mk_compclass { my ($self, $helper, $table) = @_; my $filepath = $helper->{file}; $helper->{table} = $table; $helper->render_file('compclass', $filepath); my $base = $helper->{base}; my $dir = File::Spec->catdir($base, 'root', $table); $helper->mk_dir($dir); $helper->render_file('list', File::Spec->catfile($dir, 'list.tt')); } 1; __DATA__ __compclass__ package [% class %]; use strict; use base 'Catalyst::Base'; sub list : Local { my ( $self, $c ) = @_; my @records = [% app %]::M::[% table %]->retrieve_all; $c->stash->{template} = \@records; $c->stash->{template} = "[% table %]/list.tt"; } # 以下略 __list__ [% TAGS star -%] <html> <head><title>[* app *]</title></head> <body> <h1>[* table *]</h1> <table> [% FOREACH record IN records %] <tr> </tr> [% END %] </table> </body> </html>
上の例では、 __compclass__というテンプレートの指定の後に、さらに
__list__ で区切ってテンプレートを書いています。このように、一つのヘルパー内に、
複数のテンプレートを記述することが可能です。
ただし、コンポーネント以外のファイルを出力する場合は、
出力前に $helper->mk_dir($dir) でディレクトリを作成する必要があります。
また、今回は Template-Toolkit用のテンプレートファイル を Template-Toolkitで出力することになりますので、タグのエスケープが必要です。
[% TAGS star %] で タグを [* *] に変換することによって対処しています。
このように、コンポーネントだけで無く、使用するテンプレートまで作ってしまう事が出来ます。
是非、自分好みのScaffoldを用意してみて下さい。
Catalyst::Helperには、今回言及しなかった機能がまだいくつかあります。
興味を持たれたらCatalyst::Helperや、既存のHelperスクリプトを調べてみるといいでしょう。
Catalyst::Helper::Controller::Scaffoldや、Catalyst::View::TTSiteが参考になると思います。