File Coverage

blib/lib/Class/DBI/ClassGenerator.pm
Criterion Covered Total %
statement 86 86 100.0
branch 12 16 75.0
condition 1 2 50.0
subroutine 13 13 100.0
pod 1 1 100.0
total 113 118 95.7


line stmt bran cond sub pod time code
1             package Class::DBI::ClassGenerator;
2              
3 2     2   203770 use strict;
  2         6  
  2         93  
4 2     2   13 use warnings;
  2         4  
  2         66  
5 2     2   26306 use DBI;
  2         91715  
  2         156  
6              
7 2     2   23 use File::Spec;
  2         4  
  2         49  
8              
9 2     2   10 use vars qw($VERSION);
  2         4  
  2         2721  
10              
11             $VERSION = '1.04';
12              
13             =head1 NAME
14              
15             Class::DBI::ClassGenerator - generate Class::DBI sub-class modules from a
16             pre-exsting database's structure.
17              
18             =head1 SUPPORT
19              
20             This module is unsupported, unloved, unmaintained, and DEPRECATED. No
21             bugs will be fixed. No patches will be accepted. No users will be helped.
22             All bug reports will be either ignored or rejected.
23              
24             I strongly recommend that you switch from using Class::DBI to using
25             L instead, and L instead of this
26             module.
27              
28             Unless, that is, someone else takes over ownership.
29              
30             =head1 SUBROUTINES
31              
32             =head2 create
33              
34             This takes the following named parameters:
35              
36             =over
37              
38             =item directory (compulsory)
39              
40             The name of the directory into which to drop the generated classes. If
41             it doesn't exist it will be created. Sub-directories will be created
42             under here as appropriate.
43              
44             directory => 'lib'
45              
46             =item connect_info (compulsory)
47              
48             An arrayref of the DSN, username and password to connect to the database.
49              
50             connect_info => ['dbi:mysql:dbname', 'username', 'password']
51              
52             =item base_class (compulsory)
53              
54             The name of the base class that all your table classes will inherit their
55             database connection from.
56              
57             base_class => 'MyApp::DB'
58              
59             =item tables (optional)
60              
61             A hashref whose keys are table names in the database and the values are
62             the classnames you desire.
63              
64             tables => {
65             artists => 'MyApp::Artist',
66             tracks => 'MyApp::Track',
67             albums => 'MyApp::Album',
68             ...
69             }
70              
71             If you leave this out, the code will assume
72             that you want classes for all tables, and that their names should be
73             generated thus:
74              
75             The first character of the tablename is converted to uppercase;
76              
77             An underscore followed by a character becomes the character, in
78             uppercase
79              
80             The base class name and :: is prepended.
81              
82             This is probably a close approximation for what you want anyway.
83              
84             =back
85              
86             It returns a list of all the files created.
87              
88             =cut
89              
90             sub create {
91 5     5 1 5576 my %params = @_;
92 5 100       32 die(__PACKAGE__."::create: no directory specified\n")
93             unless($params{directory});
94 4 100       36 die(__PACKAGE__."::create: no connect_info specified\n")
95             unless($params{connect_info});
96 3 100       18 die(__PACKAGE__."::create: no base class specified\n")
97             unless($params{base_class});
98              
99 2         7 mkdir($params{directory});
100 2 50       47 die("Couldn't create $params{directory}: $!\n")
101             unless(-d $params{directory});
102              
103 2         52 my $dbh = _get_dbh($params{connect_info});
104 2         954 my $db_driver = _get_db_driver($params{connect_info});
105              
106             # get tables from DB if necessary
107 2         8 $params{tables} = {
108             map {
109 2 100       15 $_ => _table_to_class($params{base_class}, $_)
110             } $db_driver->_get_tables($dbh)
111             } unless(ref($params{tables}));
112              
113             # get columns from DB
114 3         18 $params{tables} = {
115             map {
116 2         7 $_ => {
117             classname => $params{tables}->{$_},
118             columns => { $db_driver->_get_columns($dbh, $_) }
119             }
120 2         15 } keys %{$params{tables}}
121             };
122              
123 2         6 my @files_created = ();
124              
125 2         3 foreach my $table (keys %{$params{tables}}) {
  2         7  
126 13         33 my $pks = join(' ',
127 3         11 grep { $params{tables}->{$table}->{columns}->{$_}->{pk} }
128 3         7 keys %{$params{tables}->{$table}->{columns}}
129             );
130 13         39 my $nonpks = join(' ',
131 3         11 grep { !$params{tables}->{$table}->{columns}->{$_}->{pk} }
132 3         42 keys %{$params{tables}->{$table}->{columns}}
133             );
134 3         45 my $classfile = File::Spec->catfile(
135             $params{directory},
136             split('::', $params{tables}->{$table}->{classname}.'.pm')
137             );
138 3         46 _mkdir($params{directory}, $params{tables}->{$table}->{classname});
139 3 50       246 open(my $classfilefh, '>', $classfile) ||
140             die("Can't write $classfile: $!\n");
141 3         45 print $classfilefh "package ".$params{tables}->{$table}->{classname}.";\n";
142 3         9 print $classfilefh "use base '$params{base_class}';\n\n";
143 3         7 print $classfilefh "__PACKAGE__->table('$table');\n";
144 3         7 print $classfilefh "__PACKAGE__->columns(Primary => qw($pks));\n";
145 3         9 print $classfilefh "__PACKAGE__->columns(Others => qw($nonpks));\n";
146 3         125 close($classfilefh);
147             # system("cat $classfile");
148 3         15 push @files_created, $classfile;
149             }
150              
151 2         9 my $basefile = File::Spec->catfile(
152             _mkdir($params{directory}, $params{base_class}),
153             (split(/::/, $params{base_class}))[-1].'.pm'
154             );
155 2 50       165 open(my $basefilefh, '>', $basefile) ||
156             die("Can't write $basefile: $!\n");
157 2         12 print $basefilefh "package $params{base_class};\nuse base 'Class::DBI';\n\n";
158 2         10 print $basefilefh "$params{base_class}->connection('".
159 2         13 join("', '", @{$params{connect_info}}).
160             "');\n\n";
161 2         3 print $basefilefh "use $_;\n" foreach(
  3         15  
162             map {
163 2         6 $params{tables}->{$_}->{classname}
164             } keys %{$params{tables}}
165             );
166 2         68 close($basefilefh);
167 2         4 push @files_created, $basefile;
168             # system("cat $basefile");
169            
170              
171 2         192 return @files_created;
172             }
173              
174             # create a directory hierarchy for a class. Takes the base dir and
175             # class name. Given, eg, ('lib', 'Foo::Bar::Baz') it will create
176             # lib/Foo and lib/Foo/Bar. Returns the name of the last directory
177             # created.
178              
179             sub _mkdir {
180 6     6   4473 my($base, $class) = @_;
181 6         23 my @components = split(/::/, $class);
182 6         10 pop @components; # remove last bit - that's a filename
183 6         10 my $dir = $base;
184 6         17 while(@components) {
185 13         82 $dir = File::Spec->catdir($dir, shift(@components));
186 13   50     1602 mkdir $dir || die("Couldn't create $dir: $!\n");
187             }
188 6         41 return $dir;
189             }
190              
191             # given a DSN/username/password arrayref, get a DBH
192 3     3   10 sub _get_dbh { DBI->connect(@{$_[0]}); }
  3         23  
193              
194             # given a DSN/username/password arrayref, load and return the C::DBI::CG::DBD::blah
195             sub _get_db_driver {
196 3     3   1168155 my $dsn = shift;
197 3         19 my $db_driver = __PACKAGE__.'::DBD::'.
198             (split(':', $dsn->[0]))[1];
199 1     1   856 eval "use $db_driver";
  1     1   4  
  1     1   20  
  1         8  
  1         2  
  1         13  
  1         8  
  1         3  
  1         17  
  3         470  
200 3 50       14 die(
201             __PACKAGE__.
202             ": can't find db-specific code for ".
203             $dsn->[0].
204             "\n:$@\n"
205             ) if($@);
206 3         8 return $db_driver;
207             }
208              
209             # map a table name to a classname. Takes a base class name and a table
210             # name, returns a classname
211             sub _table_to_class {
212 2     2   22 my($base, $table) = @_;
213 2         11 $table =~ s/(^|_)(.)/uc($2)/eg;
  2         13  
214 2         11 join('::', $base, $table);
215             }
216              
217             =head1 BUGS and WARNINGS
218              
219             This should be considered to be pre-production code. It's probably chock
220             full of exciting bugs.
221              
222             =head1 DATABASES SUPPORTED
223              
224             MySQL and SQLite are supported "out-of-the-box". Adding other databases
225             is a simple matter of writing a "driver" module with two simple methods.
226             You are encouraged to upload such modules to the CPAN yourself.
227              
228             L, for how to interrogate other
229             databases.
230              
231             =head1 AUTHOR, COPYRIGHT and LICENCE
232              
233             Written by David Cantrell Edavid@cantrell.org.ukE
234              
235             Copyright 2008-2009 Outcome Technologies Ltd
236              
237             This software is free-as-in-speech software, and may be used, distributed,
238             and modified under the terms of either the GNU General Public Licence
239             version 2 or the Artistic Licence. It's up to you which one you use. The
240             full text of the licences can be found in the files GPL2.txt and
241             ARTISTIC.txt, respectively.
242              
243             =cut
244              
245             1;