4 use Class::MakeMethods::Template::Hash (
6 'array_of_objects -class Turnkey::Edge' => [ qw( edges ) ],
7 'array_of_objects -class Turnkey::CompoundEdge' => [ qw( compoundedges ) ],
8 'array_of_objects -class Turnkey::HyperEdge' => [ qw( hyperedges ) ],
9 'hash' => [ qw( many via has) ],
10 scalar => [ qw( base name order primary_key primary_key_accessor table) ],
14 package Turnkey::Edge;
17 use Class::MakeMethods::Template::Hash (
19 scalar => [ qw( type ) ],
20 array => [ qw( traversals ) ],
22 'thisfield' => {class => 'SQL::Translator::Schema::Field'},
23 'thatfield' => {class => 'SQL::Translator::Schema::Field'},
24 'thisnode' => {class => 'Turnkey::Node'},
25 'thatnode' => {class => 'Turnkey::Node'},
32 return Turnkey::Edge->new( thisfield => $self->thatfield,
33 thatfield => $self->thisfield,
34 thisnode => $self->thatnode,
35 thatnode => $self->thisnode,
36 type => $self->type eq 'import' ? 'export' : 'import'
41 package Turnkey::HyperEdge;
44 use base qw(Turnkey::Edge);
45 use Class::MakeMethods::Template::Hash (
46 'array_of_objects -class SQL::Translator::Schema::Field' => [ qw( thisviafield thatviafield thisfield thatfield) ],
47 'array_of_objects -class Turnkey::Node' => [ qw( thisnode thatnode ) ],
48 object => [ 'vianode' => {class => 'Turnkey::Node'} ],
52 package Turnkey::CompoundEdge;
55 use base qw(Turnkey::Edge);
56 use Class::MakeMethods::Template::Hash (
59 'via' => {class => 'Turnkey::Node'},
61 'array_of_objects -class Turnkey::Edge' => [ qw( edges ) ],
65 package SQL::Translator::Producer::Turnkey;
68 use vars qw[ $VERSION $DEBUG ];
69 $VERSION = sprintf "%d.%02d", q$Revision: 1.6 $ =~ /(\d+)\.(\d+)/;
70 $DEBUG = 1 unless defined $DEBUG;
72 use SQL::Translator::Schema::Constants;
73 use SQL::Translator::Utils qw(header_comment);
77 my %CDBI_auto_pkgs = (
83 # -------------------------------------------------------------------
87 my $no_comments = $t->no_comments;
88 my $schema = $t->schema;
89 my $args = $t->producer_args;
91 my $parser_type = (split /::/, $t->parser_type)[-1];
93 local $DEBUG = $t->debug;
96 format_fk => $t->format_fk_name,
97 template => $args->{'template'} || '',
98 baseclass => $args->{'main_pkg_name'} || $t->format_package_name('DBI'),
99 db_user => $args->{'db_user'} || '',
100 db_pass => $args->{'db_pass'} || '',
101 parser => $t->parser_type,
102 producer => __PACKAGE__,
103 dsn => $args->{'dsn'} || sprintf( 'dbi:%s:_', $CDBI_auto_pkgs{ $parser_type }
104 ? $CDBI_auto_pkgs{ $parser_type }
111 # build package objects
115 foreach my $table ($schema->get_tables){
117 die __PACKAGE__." table ".$table->name." doesn't have a primary key!" unless $table->primary_key;
118 die __PACKAGE__." table ".$table->name." can't have a composite primary key!" if ($table->primary_key->fields)[1];
120 my $node = Turnkey::Node->new();
121 $nodes{ $table->name } = $node;
123 $node->order( ++$order );
124 $node->name( $t->format_package_name($table->name) );
125 $node->base( $meta{'baseclass'} );
126 $node->table( $table );
127 $node->primary_key( ($table->primary_key->fields)[0] );
128 # Primary key may have a differenct accessor method name
129 $node->primary_key_accessor(
130 defined($t->format_pk_name)
131 ? $t->format_pk_name->( $node->name, $node->primary_key )
136 foreach my $node (values %nodes){
137 foreach my $field ($node->table->get_fields){
138 next unless $field->is_foreign_key;
140 my $that = $nodes{ $field->foreign_key_reference->reference_table };
141 #this means we have an incomplete schema
144 my $edge = Turnkey::Edge->new(
149 thatfield => ($field->foreign_key_reference->reference_fields)[0]
153 $node->has($that->name, $node->has($that->name)+1);
154 $that->many($node->name, $that->many($node->name)+1);
157 $node->push_edges( $edge );
158 $that->push_edges( $edge->flip );
163 # type MM relationships
165 foreach my $lnode (sort values %nodes){
166 next if $lnode->table->is_data;
167 foreach my $inode1 (sort values %nodes){
168 next if $inode1 eq $lnode;
170 my @inode1_imports = grep { $_->type eq 'import' and $_->thatnode eq $inode1 } $lnode->edges;
171 next unless @inode1_imports;
173 foreach my $inode2 (sort values %nodes){
174 my %i = map {$_->thatnode->name => 1} grep { $_->type eq 'import'} $lnode->edges;
175 if(scalar(keys %i) == 1) {
177 last if $inode1 eq $inode2;
180 next if $inode2 eq $lnode;
181 my @inode2_imports = grep { $_->type eq 'import' and $_->thatnode eq $inode2 } $lnode->edges;
182 next unless @inode2_imports;
184 my $cedge = Turnkey::CompoundEdge->new();
187 $cedge->push_edges( map {$_->flip} grep {$_->type eq 'import' and ($_->thatnode eq $inode1 or $_->thatnode eq $inode2)} $lnode->edges);
189 if(scalar(@inode1_imports) == 1 and scalar(@inode2_imports) == 1){
190 $cedge->type('one2one');
192 $inode1->via($inode2->name,$inode1->via($inode2->name)+1);
193 $inode2->via($inode1->name,$inode2->via($inode1->name)+1);
195 elsif(scalar(@inode1_imports) > 1 and scalar(@inode2_imports) == 1){
196 $cedge->type('many2one');
198 $inode1->via($inode2->name,$inode1->via($inode2->name)+1);
199 $inode2->via($inode1->name,$inode2->via($inode1->name)+1);
201 elsif(scalar(@inode1_imports) == 1 and scalar(@inode2_imports) > 1){
204 elsif(scalar(@inode1_imports) > 1 and scalar(@inode2_imports) > 1){
205 $cedge->type('many2many');
207 $inode1->via($inode2->name,$inode1->via($inode2->name)+1);
208 $inode2->via($inode1->name,$inode2->via($inode1->name)+1);
211 $inode1->push_compoundedges($cedge);
212 $inode2->push_compoundedges($cedge) unless $inode1 eq $inode2;
221 foreach my $node_from (values %nodes){
222 next unless $node_from->table->is_data;
223 foreach my $cedge ( $node_from->compoundedges ){
224 my $hyperedge = Turnkey::HyperEdge->new;
228 foreach my $edge ($cedge->edges){
229 if($edge->thisnode->name eq $node_from->name){
230 $hyperedge->vianode($edge->thatnode);
232 if($edge->thatnode->name ne $cedge->via->name){
233 $node_to ||= $nodes{ $edge->thatnode->table->name };
236 $hyperedge->push_thisnode($edge->thisnode);
237 $hyperedge->push_thisfield($edge->thisfield);
238 $hyperedge->push_thisviafield($edge->thatfield);
242 if($edge->thisnode->name ne $cedge->via->name){
243 $node_to ||= $nodes{ $edge->thisnode->table->name };
246 $hyperedge->push_thatnode($edge->thisnode);
247 $hyperedge->push_thatfield($edge->thisfield);
248 $hyperedge->push_thatviafield($edge->thatfield);
252 if($hyperedge->count_thisnode == 1 and $hyperedge->count_thatnode == 1){ $hyperedge->type('one2one') }
253 elsif($hyperedge->count_thisnode > 1 and $hyperedge->count_thatnode == 1){ $hyperedge->type('many2one') }
254 elsif($hyperedge->count_thisnode == 1 and $hyperedge->count_thatnode > 1){ $hyperedge->type('one2many') }
255 elsif($hyperedge->count_thisnode > 1 and $hyperedge->count_thatnode > 1){ $hyperedge->type('many2many') }
257 #warn $node_from->name ."\t". $node_to->name ."\t". $hyperedge->type ."\t". $hyperedge->vianode->name;
259 $node_from->push_hyperedges($hyperedge);
263 $meta{"nodes"} = \%nodes;
264 return(translateForm($t, \%meta));
267 ###########################################
268 # Here documents for the tt2 templates #
269 ###########################################
271 my $turnkey_dbi_tt2 = <<EOF;
272 [% MACRO printPackage(node) BLOCK %]
273 # --------------------------------------------
275 package [% node.name %];
276 use base '[% node.base %]';
277 use Class::DBI::Pager;
279 [% node.name %]->set_up_table('[% node.table.name %]');
280 [% printPKAccessors(node.primary_key, node.table.name) %]
281 [% printHasA(node.edges, node) %]
282 [% printHasMany(node.edges, node) %]
283 [% printHasCompound(node.compoundedges, node.hyperedges, node.name) %]
286 [% MACRO printPKAccessors(array, name) BLOCK %]
288 # Primary key accessors
290 [% FOREACH item = array %]
291 sub id { shift->[% item %] }
292 sub [% name %] { shift->[% item %] }
296 [% MACRO printHasA(edges, name) BLOCK %]
300 [% FOREACH edge = edges %]
301 [%- IF edge.type == 'import' -%]
302 [% node.name %]->has_a([% edge.thisfield.name %] => '[% edge.thatnode.name %]');
303 [%- IF node.has(edge.thatnode.name) < 2 %]
304 sub [% edge.thatnode.table.name %] { return shift->[% edge.thisfield.name %] }
306 sub [% format_fk(edge.thisnode.table.name,edge.thisfield.name) %] { return shift->[% edge.thisfield.name %] }
312 [% MACRO printHasMany(edges, node) BLOCK %]
316 [% FOREACH edge = edges %]
317 [%- IF edge.type == 'export' -%]
318 [% node.name %]->has_many([% edge.thatnode.table.name %]_[% edge.thatfield.name %], '[% edge.thatnode.name %]' => '[% edge.thatfield.name %]');
319 [%- IF node.via(edge.thatnode.name) >= 1 %]
320 sub [% edge.thatnode.table.name %]_[% format_fk(edge.thatnode.table.name,edge.thatfield.name) %]s { return shift->[% edge.thatnode.table.name %]_[% edge.thatfield.name %] }
321 [%- ELSIF edge.thatnode.table.is_data %]
322 sub [% edge.thatnode.table.name %]s { return shift->[% edge.thatnode.table.name %]_[% edge.thatfield.name %] }
328 [% MACRO printHasCompound(cedges,hedges,name) BLOCK %]
332 [% FOREACH cedge = cedges %]
333 [% FOREACH edge = cedge.edges %]
334 [%- NEXT IF edge.thisnode.name != name -%]
335 sub [% cedge.via.table.name %]_[% format_fk(edge.thatnode.table.name,edge.thatfield.name) %]s { return shift->[% cedge.via.table.name %]_[% edge.thatfield.name %] }
338 [% FOREACH h = hedges %]
339 [%- NEXT IF h.thisnode.name != name -%]
340 [%- IF h.type == 'one2one' %]
341 1sub [% h.thatnode.table.name %]s { my \$self = shift; return map \$_->[% h.thatviafield.name %], \$self->[% h.vianode.table.name %]_[% h.thisviafield.name %] }
342 [%- ELSIF h.type == 'one2many' %]
344 [%- ELSIF h.type == 'many2one' %]
345 3sub [% h.thatnode.table.name %]s { my \$self = shift; return map \$_->[% h.thatviafield.name %], \$self->[% h.vianode.table.name %]_[% h.thisviafield.name %] }
346 [%- ELSIF h.type == 'many2many' %]
352 [% MACRO printList(array) BLOCK %][% FOREACH item = array %][% item %] [% END %][% END %]
353 package [% baseclass %];
355 # Created by SQL::Translator::Producer::Turnkey
356 # Template used: classdbi
359 use base qw(Class::DBI::Pg);
361 Durian::Model::DBI->set_db('Main', '[% db_str %]', '[% db_user %]', '[% db_pass %]');
363 [% FOREACH node = nodes %]
364 [% printPackage(node.value) %]
368 my $turnkey_atom_tt2 = <<'EOF';
369 [% ###### DOCUMENT START ###### %]
371 [% FOREACH node = linkable %]
373 ##############################################
375 package Durian::Atom::[% node.key FILTER ucfirst %];
377 [% pname = node.key FILTER ucfirst%]
378 [% pkey = "Durian::Model::${pname}" %]
380 use base qw(Durian::Atom);
389 my $dbobject = shift;
390 # Assumption here that if it's not rendering on it's own dbobject
391 # then it's a list. This will be updated when AtomLists are implemented -boconnor
392 if(ref($dbobject) eq 'Durian::Model::[% node.key FILTER ucfirst %]') {
393 return(_render_record($dbobject));
395 else { return(_render_list($dbobject)); }
399 my $dbobject = shift;
403 [% FOREACH field = nodes.$pkey.columns_essential %]
404 $field_hash->{[% field %]} = $dbobject->[% field %]();
406 $row->{data} = $field_hash;
407 $row->{id} = $dbobject->id();
413 my $dbobject = shift;
415 my @objects = $dbobject->[% node.key %]s;
416 foreach my $object (@objects)
420 [% FOREACH field = nodes.$pkey.columns_essential %]
421 $field_hash->{[% field %]} = $object->[% field %]();
423 $row->{data} = $field_hash;
424 $row->{id} = $object->id();
439 my $turnkey_xml_tt2 = <<EOF;
440 <?xml version="1.0" encoding="UTF-8"?>
441 <!DOCTYPE Durian SYSTEM "Durian.dtd">
444 <!-- The basic layout is fixed -->
445 <container bgcolor="#FFFFFF" cellpadding="0" cellspacing="0" height="90%" orientation="vertical" type="root" width="100%" xlink:label="RootContainer">
446 <container cellpadding="3" cellspacing="0" orientation="horizontal" type="container" height="100%" width="100%" xlink:label="MiddleContainer">
447 <container align="center" cellpadding="2" cellspacing="0" class="leftbar" orientation="vertical" type="minor" width="0%" xlink:label="MidLeftContainer"/>
448 <container cellpadding="0" cellspacing="0" orientation="vertical" width="100%" type="major" xlink:label="MainContainer"/>
452 <!-- Atom Classes -->
453 [% FOREACH node = linkable %]
454 <atom class="Durian::Atom::[% node.key FILTER ucfirst %]" name="[% node.key FILTER ucfirst %]" xlink:label="[% node.key FILTER ucfirst %]Atom"/>
457 <!-- Atom Bindings -->
459 [% FOREACH focus_atom = linkable %]
460 [% FOREACH link_atom = focus_atom.value %]
461 <atomatombinding xlink:from="#[% focus_atom.key FILTER ucfirst %]Atom" xlink:to="#[% link_atom.key FILTER ucfirst %]Atom" xlink:label="[% focus_atom.key FILTER ucfirst %]Atom2[% link_atom.key FILTER ucfirst %]Atom"/>
466 <atomcontainerbindings>
467 [% FOREACH focus_atom = linkable %]
468 <atomcontainerbindingslayout xlink:label="Durian::Model::[% focus_atom.key FILTER ucfirst %]">
469 [% FOREACH link_atom = focus_atom.value %]
470 <atomcontainerbinding xlink:from="#MidLeftContainer" xlink:label="MidLeftContainer2[% link_atom.key FILTER ucfirst %]Atom" xlink:to="#[% link_atom.key FILTER ucfirst %]Atom"/>
472 <atomcontainerbinding xlink:from="#MainContainer" xlink:label="MainContainer2[% focus_atom.key FILTER ucfirst %]Atom" xlink:to="#[% focus_atom.key FILTER ucfirst %]Atom"/>
473 </atomcontainerbindingslayout>
475 </atomcontainerbindings>
478 <uribinding uri="/" class="Durian::Util::Frontpage"/>
482 [% FOREACH focus_atom = linkable %]
483 <classbinding class="Durian::Model::[% focus_atom.key FILTER ucfirst %]" plugin="#[% focus_atom.key FILTER ucfirst %]Atom" rank="0"/>
491 my $turnkey_template_tt2 = <<'EOF';
493 [% MACRO renderpanel(panel,dbobject) BLOCK %]
494 <!-- begin panel: [% panel.label %] -->
495 <table border="0" width="[% panel.width %]" height="[% panel.height %]" bgcolor="[% panel.bgcolor %]" valign="top" cellpadding="[% panel.cellpadding %]" cellspacing="[% panel.cellspacing %]" align="[% panel.align %]" valign="[% panel.valign %]">
497 [% FOREACH p = panel.containers %]
498 [% IF p.can_render(panel) %]
499 <td valign="top" class="[% p.class %]" align="[% panel.align %]" height="[% p.height || 1 %]" width="[% p.width %]">
500 [% IF p.type == 'Container' %]
501 [% renderpanel(p,dbobject) %]
503 <table cellpadding="0" cellspacing="0" align="left" height="100%" width="100%">
505 <tr bgcolor="#4444FF" height="1">
506 <td><font color="#FFFFFF">[% p.name %][% IF panel.type == 'major' %]: [% dbobject.name %][% END %]</font></td>
507 <td align="right" width="0"><!--<nobr><img src="/images/v.gif"/><img src="/images/^.gif"/>[% IF p.delible == 'yes' %]<img src="/images/x.gif"/>[% END %]</nobr>--></td>
510 <tr><td colspan="2" bgcolor="#FFFFFF">
511 <!-- begin atom: [% p.label %] -->
512 <table cellpadding="0" cellspacing="0" align="left" height="100%" width="100%"><!-- [% ref(atom) %] [% ref(dbobject) %] -->
513 [% renderatom(p,dbobject) %] <!-- used to be renderplugin(p,panel) -->
518 [% IF panel.orientation == 'vertical' %]
525 <!-- end panel: [% panel.label %] -->
527 [% MACRO renderatom(atom, dbobject) SWITCH atom.name %]
528 [- FOREACH node = linkable -]
529 [% CASE '[- node.key FILTER ucfirst -]' %]
530 [% render[- node.key FILTER ucfirst -]Atom(atom.render(dbobject)) %]
533 [% renderlist(atom.render(dbobject)) %]
535 [- FOREACH node = linkable -]
536 [% MACRO render[- node.key FILTER ucfirst -]Atom(lstArr) BLOCK %]
537 [% FOREACH record = lstArr %]
538 [% fields = record.data %]
539 [- pname = node.key FILTER ucfirst -]
540 [- pkey = "Durian::Model::${pname}" -]
541 [- FOREACH field = nodes.$pkey.columns_essential -]
542 <tr><td><b>[- field -]</b></td><td>[% fields.[- field -] %]</td></tr>
545 <tr><td><a href="?id=[% id %];class=Durian::Model::[- node.key FILTER ucfirst -]">Link</a></td><td></td></tr>
549 [% MACRO renderlist(lstArr) BLOCK %]
550 [% FOREACH item = lstArr %]
561 # my $output = shift;
562 my $args = $t->producer_args;
563 my $tt2 = $meta->{'template'};
566 if ($tt2 eq 'atom') { $tt2Ref = \$turnkey_atom_tt2; }
567 elsif ($tt2 eq 'classdbi') { $tt2Ref = \$turnkey_dbi_tt2; }
568 elsif ($tt2 eq 'xml') { $tt2Ref = \$turnkey_xml_tt2; }
569 elsif ($tt2 eq 'template') { $tt2Ref = \$turnkey_template_tt2; }
570 else { die __PACKAGE__." didn't recognize your template option: $tt2" }
573 EVAL_PERL => 1, # evaluate Perl code blocks
576 # create Template object
577 my $template = Template->new($config);
580 # specify input filename, or file handle, text reference, etc.
581 # process input template, substituting variables
582 $template->process($tt2Ref, $meta, \$result) || die $template->error();
588 # -------------------------------------------------------------------
594 SQL::Translator::Producer::Turnkey - create Turnkey classes from schema
598 Creates output for use with the Turnkey project.
602 L<http://turnkey.sourceforge.net>.
606 Allen Day E<lt>allenday@ucla.eduE<gt>
607 Ying Zhang E<lt>zyolive@yahoo.comE<gt>,
608 Brian O'Connor E<lt>brian.oconnor@excite.comE<gt>.