Customization of DBIC::Schema::Loader2. Agenda DBIC::Schema::Loader のおさらい make_schema_at() 生成されたファイル自体に拡張 inc パスの追加による拡張 really_erase_files の値 Schema クラスも拡張対象にする DBIC::Schema::Loader を改造 DBIC::Schema::Loader の概要 名前に制約をつけてリレーション設定 まとめ 3. make_schema_at() DBIC::Schema::Loader のメソッド 引数 1. schema クラス名 2. 生成オプション (HAShref) 3. connect_info (ARRAYREF) 基本系 #!/usr/bin/perl use FindBin; use File::Spec; use DBIx::Class::Schema::Loader qw(make_schema_at) ; make_schema_at( 'MyApp::DBIC::Schema' , { dump_directory => File::Spec->catfile( $FindBin::Bin , '..' , 'lib' ), relly_erase_files => 1 , }, [ 'dbi:mysql:database=dbictest' , 'root' ], ); 4. 拡張 (1) ファイル直書き方式 - 1 生成されたファイルに拡張 生成された Schema, Table クラスそれぞれの下の方に “ DO NOT MODIFY THIS ” と書かれたコメントがある そこから “ You can replace this text ” の領域は拡張領域で自動生成の対象ではない 生成されたファイルに直接書いて良い 追加で load_components() したりとか あるいは他のプラグインのメソッド叩いたり テーブル定義をゴニョゴニョしたりとか 5. 拡張 (1) ファイル直書き方式 - 2 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:WFbbTfTFDFr/kewSj3QwAw package MyApp::DBIC::Schema; __PACKAGE__->load_components( qw/+MyApp::DBIC::MyComp/ ); __PACKAGE__->init_mycomp; # You can replace this text with custom content, and it will be preserved on regeneration 1 ; 6. 拡張 (2) incにあるテンプレ読み込み方式 - 1 incパスに通したテンプレからinclude use libで適当なディレクトリにパスを通すと、そこにあるファイルを対応するモジュールの拡張領域に差し込んでくれる機能 ここで注意しないとダメなのはSchemaクラスは対象外 Tableクラスのみ拡張可能 ちなみに拡張領域の箇所はファイル直書き方式と同じ 7. 拡張 (2) incにあるテンプレ読み込み方式 - 2 ディレクトリ構成 lib MyApp DBIC Schema User.pm schema MyApp DBIC Schema User.pm 自動生成される領域 Include ( ファイルの下部にくっつく ) 8. make_schema_at() [2] really_erase_files ( 生成オプション ) true の時 毎回ファイルを消して生成する Schema, Table クラス両方消しちゃうので、 ファイル直書き方式は使えない >< 必然的に include になるが、 Schema に対しては適用出来ない false の時 毎回ファイルを消さない 直書き方式が使える 但し消さないと include で挿入されたブロックが次回の直書きと見なされ、再び include されるので重複コードになるwww 9. ここまでのまとめ 現在の問題点 Schema, Table 共に後付的に拡張したい Schema も対象にするなら really_erase_files を false にする そうすると直書き領域に make_schema_at する度に重複するコードが出来てしまう どうすれば良いか really_erase_files => 0 でも自分で Table クラスを消せば同じ事になる 拡張は Schema は直書き、 Table は include でやる。 10. 改良版Schema生成 改良版 #!/usr/bin/perl use strict ; use warnings ; use FindBin; use File::Spec; use lib ( File::Spec->catfile( $FindBin::Bin , qw/.. lib/ ), File::Spec->catfile( $FindBin::Bin , qw/.. schema/ ) ); use DBIx::Class::Schema::Loader qw(make_schema_at) ; die unless @ARGV ; my $schema_class = 'MyClass::DBIC::Schema' ; # こんな感じで自分で消す unlink ( glob ( File::Spec->catdir( $FindBin::Bin , '..' , 'lib' , split ( / :: / , $schema_class ) ) . '/*.pm' ) ); make_schema_at( $schema_class , { components => [ qw/ResultSetManager UTF8Columns InflateColumn::DateTime TimeStamp/ ], dump_directory => File::Spec->catfile( $FindBin::Bin , qw/.. lib/ ), debug => 0 , really_erase_my_files => 0 , }, ARGV ); 12. Class::Diagram of DBIC::Schema::Loader Class Diagram DBIC::Schema::Loader DBIC::Schema ::Loader::Base DBIC::Schema ::Loader::Base::DBI::mysql DBIC::Schema ::Loader::RelBuilder 各ドライバごとにクラスがある リレーションの構築 Dump 時のオプションの詳細 13. RelationShip [1] belongs_to とか has_many とか belongs_to(“user_id”, ...) $rs-> user_id -> user_id ダサすぎ 残念ながら現在はこうなる belongs_to(“user”, ...) $rs-> user -> user_id $rs-> user_id も OK 自然になる こうしたい 14. RelationShip [2] 問題の箇所 DBIC::Schema::Loader::Base の _load_relationship() foreach my $src_class ( sort keys %$rel_stmts ) { my $src_stmts = $rel_stmts ->{ $src_class }; foreach my $stmt ( @$src_stmts ) { ### ここら辺を書き換えちゃえば良い $self ->_dbic_stmt( $src_class , $stmt ->{method}, ## belongs_to とか @{ $stmt ->{args}} ## belongs_to の引数リスト ); } } 15. RelationShip [4] 改良版 foreach my $src_class ( sort keys %$rel_stmts ) { my $src_stmts = $rel_stmts ->{ $src_class }; foreach my $stmt ( @$src_stmts ) { ### belongs_to の時だけ無茶する if ( $stmt ->{method} eq 'belongs_to' ) { my $table_class_suffix = [ split / :: / => $stmt ->{args}->[ 1 ] ]->[ -1 ]; $stmt ->{args}->[ 0 ] = String::CamelCase::decamelize( $table_class_suffix ); } $self ->_dbic_stmt( $src_class , $stmt ->{method}, @{ $stmt ->{args} } ); } } 16. まとめ Schema::Loader との付き合いかた really_erase_files を false でも自分で Table 消す Schema は直書き、 Table は include で辻褄合わせる belongs_to は問題箇所を redifine する 名前でコード生成時の制約をつけるのは良さそう。 on_created, on_updated(DATETIME) で NOW() 相当とか 時間の都合上割愛したけど、それ以上は自分で DBIC 流儀のプラグイン書く ( 割と便利 ) そこまでするなら自分で Loader 書くべき?(ぇ あるいは DBIC を使わない