File Coverage

blib/lib/Catalyst/Helper/Model/CRUD.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             package Catalyst::Helper::Model::CRUD;
2              
3 1     1   5280 use strict;
  1         4  
  1         46  
4 1     1   6 use Jcode;
  1         2  
  1         67  
5 1     1   643 use XML::Simple;
  0            
  0            
6              
7             our $VERSION = '0.21';
8              
9             =head1 NAME
10              
11             Catalyst::Helper::Model::CRUD - generate sqls, controllers and templates from DBDesigner 4 file
12              
13             =head1 SYNOPSIS
14              
15             ./myapp_create.pl model DBIC CRUD [DBDesigner 4 File] [some modules]
16              
17             =head1 DESCRIPTION
18              
19             Helper for Catalyst::Plugin::CRUD.
20              
21             This helper generates sqls, default controllers and default templates.
22              
23             =head1 METHODS
24              
25             =cut
26              
27             # relation list
28             my @relations;
29              
30             # table list
31             my @tables;
32              
33             =head2 encode($str)
34              
35             This method translates comment of DBDesigner4 to UTF-8.
36              
37             =cut
38              
39             sub encode {
40             my ( $this, $str ) = @_;
41             my @array = split( //, $str );
42             my @list;
43             for ( my $i = 0 ; $i < scalar(@array) ; $i++ ) {
44              
45             # translate "\\n" to "。"
46             if ( $array[$i] eq '\\' && $array[ $i + 1 ] eq 'n' ) {
47             push @list, 129;
48             push @list, 66;
49             $i++;
50              
51             # translate "\\\\" to "0x5C"
52             }
53             elsif ( $array[$i] eq '\\' && $array[ $i + 1 ] eq '\\' ) {
54             push @list, 92;
55             $i++;
56              
57             # "\\144" etc
58             }
59             elsif ( $array[$i] eq '\\' ) {
60             push @list, $array[ $i + 1 ] . $array[ $i + 2 ] . $array[ $i + 3 ];
61             $i += 3;
62              
63             # "[" etc
64             }
65             elsif ( 13 < ord( $array[$i] ) && ord( $array[$i] ) < 128 ) {
66             push @list, ord( $array[$i] );
67             }
68             }
69              
70             # translate Shift-JIS to UTF-8
71             my $result = pack( "C*", @list );
72             return jcode( $result, 'sjis' )->utf8;
73             }
74              
75             =head2 get_class_name($str)
76              
77             This method translates hoge_fuga_master to HogeFugaMaster.
78              
79             =cut
80              
81             sub get_class_name {
82             my ( $this, $str ) = @_;
83             my @array = split( //, $str );
84             for ( my $i = 0 ; $i < scalar(@array) ; $i++ ) {
85             if ( $i == 0 ) {
86             $array[$i] = uc $array[$i];
87             }
88             elsif ( $array[$i] eq '_' ) {
89             $array[ $i + 1 ] = uc $array[ $i + 1 ];
90             }
91             }
92             my $result = join( '', @array );
93             $result =~ s/_//g;
94             return $result;
95             }
96              
97             =head2 get_relation($relation_id)
98              
99             This method returns relation of appointed ID.
100              
101             =cut
102              
103             sub get_relation {
104             my ( $this, $relation_id ) = @_;
105             foreach my $relation (@relations) {
106             if ( $relation_id eq $relation->{'ID'} ) {
107             return $relation;
108             }
109             }
110             }
111              
112             =head2 get_table($table_id)
113              
114             This methods returns table of appointed ID.
115              
116             =cut
117              
118             sub get_table {
119             my ( $this, $table_id ) = @_;
120             foreach my $table (@tables) {
121             if ( $table_id eq $table->{'ID'} ) {
122             return $table;
123             }
124             }
125             }
126              
127             =head2 get_setting_index($array, $name)
128              
129             This method returns setting number of appointed name.
130              
131             =cut
132              
133             sub get_setting_index {
134             my ( $this, $array, $name ) = @_;
135             for ( my $i = 0 ; $i < scalar( @{$array} ) ; $i++ ) {
136             if ( $name eq $array->[$i]->{'name'} ) {
137             return $i;
138             }
139             }
140             return -1;
141             }
142              
143             =head2 get_primary(@sqls)
144              
145             This method returns primary key name.
146              
147             =cut
148              
149             sub get_primary {
150             my ( $this, @sqls ) = @_;
151             for my $sql (@sqls) {
152             if ( $sql->{type} eq 'serial' ) {
153             return $sql->{name};
154             }
155             }
156             return 'id';
157             }
158              
159             =head2 get_columns(@sqls)
160              
161             This method returns columns string.
162              
163             =cut
164              
165             sub get_columns {
166             my ( $this, @sqls ) = @_;
167             shift @sqls;
168             my @names;
169             for my $sql (@sqls) {
170             push @names, $sql->{name};
171             }
172             return join( " ", @names );
173             }
174              
175             =head2 mk_compclass($helper, $file, @limited_file)
176              
177             This method analyse DBDesigner 4 file and generate sqls, controllers and templates.
178              
179             =cut
180              
181             sub mk_compclass {
182             my ( $this, $helper, $file, @limited_file ) = @_;
183              
184             print "==========================================================\n";
185              
186             # ファイル名は必須
187             unless ($file) {
188             die "usage: ./myapp_create.pl model CRUD CRUD [DBDesigner 4 File] [some modules]\n";
189             return 1;
190             }
191              
192             # XMLファイル解析
193             my $parser = new XML::Simple();
194             my $tree = $parser->XMLin($file);
195              
196             # SQL・コントローラ・テンプレート用のディレクトリを作る
197             my $schema_dir = sprintf( "%s/sql/schema", $helper->{'base'} );
198             my $i18n_dir = sprintf( "%s/lib/%s/I18N", $helper->{'base'}, $helper->{'app'} );
199             my $controller_dir = sprintf( "%s/lib/%s/Controller", $helper->{'base'}, $helper->{'app'} );
200             my $template_dir = sprintf( "%s/root/template", $helper->{'base'} );
201             $helper->mk_dir($schema_dir);
202             $helper->mk_dir($i18n_dir);
203             $helper->mk_dir($controller_dir);
204             $helper->mk_dir($template_dir);
205              
206             # リレーションとテーブル一覧を取得する
207             if ( ref $tree->{'METADATA'}->{'RELATIONS'}->{'RELATION'} eq 'ARRAY' ) {
208             @relations = @{ $tree->{'METADATA'}->{'RELATIONS'}->{'RELATION'} };
209             }
210             else {
211             push( @relations, $tree->{'METADATA'}->{'RELATIONS'}->{'RELATION'} );
212             }
213             if ( ref $tree->{'METADATA'}->{'TABLES'}->{'TABLE'} eq 'ARRAY' ) {
214             @tables = @{ $tree->{'METADATA'}->{'TABLES'}->{'TABLE'} };
215             }
216             else {
217             push( @tables, $tree->{'METADATA'}->{'TABLES'}->{'TABLE'} );
218             }
219              
220             # 指定したモジュールのみ
221             my %limit;
222             $limit{$_} = 1 foreach (@limited_file);
223              
224             # 言語ファイル用キーワードファイル
225             my @keywords;
226              
227             foreach my $table (@tables) {
228             my $model_name = $this->get_class_name( $table->{'Tablename'} );
229             my $class_name = $model_name;
230             $class_name =~ s/Master//g;
231              
232             # 指定したモジュールのみ
233             if ( scalar @limited_file ) {
234             next unless ( $limit{$class_name} );
235             }
236              
237             # 言語ファイルに追加
238             push @keywords,
239             {
240             name => $class_name . '_class_name',
241             desc_ja => $this->encode( $table->{'Comments'} ),
242             desc_en => $class_name
243             };
244              
245             # 各テーブルの列一覧取得
246             my @columns = @{ $table->{'COLUMNS'}->{'COLUMN'} }
247             if ref $table->{'COLUMNS'}->{'COLUMN'} eq 'ARRAY';
248              
249             # 各テーブルのインデックス覧取得
250             my %indices;
251             if ( ref( $table->{'INDICES'}->{'INDEX'} ) eq 'HASH' ) {
252              
253             # 要素一個のときはハッシュになってしまうのでその対策
254             my $key = $table->{'INDICES'}->{'INDEX'}->{'INDEXCOLUMNS'}->{'INDEXCOLUMN'}->{'idColumn'};
255             my $val = $table->{'INDICES'}->{'INDEX'}->{'FKRefDef_Obj_id'};
256              
257             # 主キーは無視する
258             unless ( $val eq '-1' ) {
259             $indices{$key} = $val;
260             }
261             }
262             elsif ( ref( $table->{'INDICES'}->{'INDEX'} ) eq 'ARRAY' ) {
263             foreach my $index ( @{ $table->{'INDICES'}->{'INDEX'} } ) {
264             my $key = $index->{'INDEXCOLUMNS'}->{'INDEXCOLUMN'}->{'idColumn'};
265             my $val = $index->{'FKRefDef_Obj_id'};
266              
267             # 主キーは無視する
268             unless ( $val eq '-1' ) {
269             $indices{$key} = $val;
270             }
271             }
272             }
273              
274             my @serials; # シーケンス一覧
275             my @sqls; # SQL一覧
276             my @settings; # スキーマ一覧
277             foreach my $column (@columns) {
278             my $sql;
279             my @setting;
280              
281             # カラム名
282             push @setting, ( " " . $column->{'ColName'} );
283              
284             # 型
285             if ( $column->{'AutoInc'} eq "1" ) {
286              
287             # AutoInc="1" だったら「テーブル名_カラム名_seq」という
288             # テーブルを Postgresql が自動作成するのでその対応
289             $sql->{'type'} = "serial";
290             push @setting, "SERIAL";
291             push @serials,
292             sprintf( "GRANT ALL ON %s_%s_seq TO PUBLIC;\n", $table->{'Tablename'}, $column->{'ColName'} );
293             }
294             elsif ( $column->{'idDatatype'} eq '5' ) {
295             $sql->{'type'} = "int";
296             push @setting, "INTEGER";
297             }
298             elsif ( $column->{'idDatatype'} eq '14' ) {
299             $sql->{'type'} = "date";
300             push @setting, "DATE";
301             }
302             elsif ( $column->{'idDatatype'} eq '16' ) {
303             $sql->{'type'} = "timestamp with time zone";
304             push @setting, "TIMESTAMP with time zone";
305             }
306             elsif ( $column->{'idDatatype'} eq '20' ) {
307             $sql->{'type'} = "varchar(255)";
308             push @setting, "VARCHAR(255)";
309             }
310             elsif ( $column->{'idDatatype'} eq '22' ) {
311             $sql->{'type'} = "bool";
312             push @setting, "BOOL";
313             }
314             elsif ( $column->{'idDatatype'} eq '28' ) {
315             $sql->{'type'} = "text";
316             push @setting, "TEXT";
317             }
318             else {
319             $sql->{'type'} = "text";
320             push @setting, "TEXT";
321             }
322              
323             # 主キーかどうか
324             if ( $column->{'PrimaryKey'} eq '1' ) {
325             $sql->{'primarykey'} = 1;
326             push @setting, "PRIMARY KEY";
327             }
328             elsif ( 'id' eq lc( $column->{'ColName'} ) ) {
329              
330             # id は自動的に主キーにする
331             $sql->{'primarykey'} = 1;
332             push @setting, "PRIMARY KEY";
333             }
334              
335             # デフォルト値
336             if ( length( $column->{'DefaultValue'} ) > 0 ) {
337             $sql->{'default'} = $column->{'DefaultValue'};
338             push @setting, sprintf( "DEFAULT '%s'", $column->{'DefaultValue'} );
339             }
340             elsif ( $column->{'idDatatype'} eq '14' ) {
341              
342             # 日付は自動的に設定する
343             $sql->{'default'} = "('now'::text)::timestamp";
344             push @setting, "DEFAULT ('now'::text)::timestamp";
345             }
346             elsif ( $column->{'idDatatype'} eq '16' ) {
347              
348             # 日時は自動的に設定する
349             $sql->{'default'} = "('now'::text)::timestamp";
350             push @setting, "DEFAULT ('now'::text)::timestamp";
351             }
352             elsif ( 'disable' eq lc( $column->{'ColName'} ) ) {
353              
354             # disable は自動的に 0 にする
355             $sql->{'default'} = "0";
356             push @setting, "DEFAULT '0'";
357             }
358              
359             # NOT NULL 制約
360             if ( $column->{'NotNull'} eq '1' ) {
361             $sql->{'notnull'} = 1;
362             push @setting, "NOT NULL";
363             }
364             elsif ( 'disable' eq lc( $column->{'ColName'} ) ) {
365              
366             # disable は自動的に NOT NULL にする
367             $sql->{'notnull'} = 1;
368             push @setting, "NOT NULL";
369             }
370              
371             # 参照制約
372             if ( $indices{ $column->{'ID'} } ) {
373             my $relation = $this->get_relation( $indices{ $column->{'ID'} } );
374             my $src_table = $this->get_table( $relation->{'SrcTable'} );
375             my $class_name = sprintf( "%s::Model::ShanonDBI::%s",
376             $helper->{'app'}, $this->get_class_name( $src_table->{'Tablename'} ) );
377             $sql->{'references'} = {
378             class => $class_name,
379             name => 'id',
380             onupdate => 'cascade',
381             ondelete => 'cascade'
382             };
383             push @setting,
384             sprintf( "CONSTRAINT ref_%s REFERENCES %s (id) ON DELETE cascade ON UPDATE cascade",
385             $column->{'ColName'}, $src_table->{'Tablename'} );
386             }
387              
388             # コメント
389             if ( 'id' eq lc( $column->{'ColName'} ) ) {
390              
391             # id は自動的に ID にする
392             push @setting, '/* ID */';
393             }
394             elsif ( 'disable' eq lc( $column->{'ColName'} ) ) {
395              
396             # disable は自動的に 削除 にする
397             push @setting, '/* 削除 */';
398             }
399             elsif ( 'date_regist' eq lc( $column->{'ColName'} ) ) {
400              
401             # disable は自動的に 登録日時 にする
402             push @setting, '/* 登録日時 */';
403             }
404             elsif ( 'date_update' eq lc( $column->{'ColName'} ) ) {
405              
406             # disable は自動的に 更新日時 にする
407             push @setting, '/* 更新日時 */';
408             }
409             else {
410             push @setting, sprintf( "/* %s */", $this->encode( $column->{'Comments'} ) );
411             }
412            
413             # 言語ファイル
414             if ( 'id' eq lc( $column->{'ColName'} ) ) {
415             push @keywords,
416             {
417             name => $class_name . '_' . $column->{'ColName'},
418             desc_ja => 'ID',
419             desc_en => 'ID'
420             };
421             }
422             elsif ( 'disable' eq lc( $column->{'ColName'} ) ) {
423             push @keywords,
424             {
425             name => $class_name . '_' . $column->{'ColName'},
426             desc_ja => '削除',
427             desc_en => 'DISABLE'
428             };
429             }
430             elsif ( 'date_regist' eq lc( $column->{'ColName'} ) ) {
431             push @keywords,
432             {
433             name => $class_name . '_' . $column->{'ColName'},
434             desc_ja => '登録日時',
435             desc_en => 'DATE_REGIST'
436             };
437             }
438             elsif ( 'date_update' eq lc( $column->{'ColName'} ) ) {
439             push @keywords,
440             {
441             name => $class_name . '_' . $column->{'ColName'},
442             desc_ja => '更新日時',
443             desc_en => 'DATE_UPDATE'
444             };
445             }
446             else {
447             push @keywords,
448             {
449             name => $class_name . '_' . $column->{'ColName'},
450             desc_ja =>
451             length( $column->{'Comments'} ) == 0 ?
452             uc $column->{'ColName'} : $this->encode( $column->{'Comments'} ),
453             desc_en => uc $column->{'ColName'}
454             };
455             }
456              
457             # 列名の代入
458             $sql->{'name'} = $column->{'ColName'};
459              
460             # 列の説明の代入
461             $sql->{'desc'} = $class_name . '_' . $column->{'ColName'};
462              
463             push @sqls, $sql;
464             push @settings, join( " ", @setting );
465             }
466              
467             # SQL出力
468             my $setting_vars;
469             $setting_vars->{'table'} = $table->{'Tablename'};
470             $setting_vars->{'comment'} = $this->encode( $table->{'Comments'} );
471             $setting_vars->{'columns'} = join( ",\n", @settings );
472             $setting_vars->{'serials'} = join( "", @serials );
473             $helper->render_file( 'schema_sql', "$schema_dir/$table->{'Tablename'}.sql", $setting_vars );
474              
475             # コントローラ出力
476             my $controller_vars;
477             $controller_vars->{'app_name'} = $helper->{'app'};
478             $controller_vars->{'path_name'} = lc $class_name;
479             $controller_vars->{'base_name'} = $helper->{'name'};
480             $controller_vars->{'model_name'} = $model_name;
481             $controller_vars->{'class_name'} = $class_name;
482             $controller_vars->{'comment'} = $this->encode( $table->{'Comments'} );
483             $controller_vars->{'primary'} = $this->get_primary(@sqls);
484             $controller_vars->{'columns'} = $this->get_columns(@sqls);
485             $controller_vars->{'sqls'} = \@sqls;
486             $helper->render_file( 'controller_class', "$controller_dir/$class_name.pm", $controller_vars );
487              
488             # テンプレート出力
489             my $path_name = lc $class_name;
490             $helper->mk_dir("$template_dir/$path_name");
491              
492             # Template-Toolkit
493             $helper->render_file( 'create_tt', "$template_dir/$path_name/create.tt", $controller_vars );
494             $helper->render_file( 'read_tt', "$template_dir/$path_name/read.tt", $controller_vars );
495             $helper->render_file( 'update_tt', "$template_dir/$path_name/update.tt", $controller_vars );
496             $helper->render_file( 'list_tt', "$template_dir/$path_name/list.tt", $controller_vars );
497              
498             # ClearSilver
499             #$helper->render_file( 'create_cs', "$template_dir/$path_name/create.cs", $controller_vars );
500             #$helper->render_file( 'read_cs', "$template_dir/$path_name/read.cs", $controller_vars );
501             #$helper->render_file( 'update_cs', "$template_dir/$path_name/update.cs", $controller_vars );
502             #$helper->render_file( 'list_cs', "$template_dir/$path_name/list.cs", $controller_vars );
503             }
504              
505             # ヘッダー・フッター出力
506             unless ( scalar @limited_file ) {
507             my $header_footer_vars;
508             $header_footer_vars->{'app_name'} = $helper->{'app'};
509              
510             # Template-Toolkit
511             $helper->render_file( 'header_html', "$template_dir/header.tt", $header_footer_vars );
512             $helper->render_file( 'footer_html', "$template_dir/footer.tt", $header_footer_vars );
513              
514             # ClearSilver
515             #$helper->render_file( 'header_html', "$template_dir/header.cs", $header_footer_vars );
516             #$helper->render_file( 'footer_html', "$template_dir/footer.cs", $header_footer_vars );
517             }
518              
519             # 言語ファイル出力
520             my $i18n_vars;
521             $i18n_vars->{'keywords'} = \@keywords;
522             if ( scalar @limited_file ) {
523             $helper->render_file( 'ja_po', "$i18n_dir/ja.po", $i18n_vars );
524             $helper->render_file( 'en_po', "$i18n_dir/en.po", $i18n_vars );
525             } else {
526             $helper->render_file( 'mini_ja_po', "$i18n_dir/ja.po", $i18n_vars );
527             $helper->render_file( 'mini_en_po', "$i18n_dir/en.po", $i18n_vars );
528             }
529              
530             print "==========================================================\n";
531             }
532              
533             =head1 SEE ALSO
534              
535             DBDesigner 4 -- http://fabforce.net/dbdesigner4/index.php
536              
537             Catalyst::Helper::Model, Catalyst::Plugin::CRUD, XML::Simple
538              
539             =head1 AUTHOR
540              
541             Jun Shimizu, Ebayside@cpan.orgE
542              
543             =head1 COPYRIGHT AND LICENSE
544              
545             Copyright (C) 2006-2007 by Jun Shimizu
546              
547             This library is free software; you can redistribute it and/or modify
548             it under the same terms as Perl itself, either Perl version 5.8.2 or,
549             at your option, any later version of Perl 5 you may have available.
550              
551             =cut
552              
553             1;
554              
555             __DATA__