change test env vars to match DBIx::Class
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Generic.pm
CommitLineData
18fca96a 1package DBIx::Class::Schema::Loader::Generic;
a78e3fed 2
3use strict;
a4a19f3c 4use warnings;
5
6use base qw/DBIx::Class::Schema/;
7
a78e3fed 8use Carp;
9use Lingua::EN::Inflect;
a4a19f3c 10
a78e3fed 11require DBIx::Class::Core;
a4a19f3c 12
e26a4023 13__PACKAGE__->mk_classaccessor('_loader_inflect');
14__PACKAGE__->mk_classaccessor('_loader_db_schema');
15__PACKAGE__->mk_classaccessor('_loader_drop_db_schema');
16__PACKAGE__->mk_classaccessor('_loader_classes' => {} );
17__PACKAGE__->mk_classaccessor('_loader_monikers' => {} );
66742793 18__PACKAGE__->mk_classaccessor('_loader_debug' => 0);
a4a19f3c 19
a78e3fed 20=head1 NAME
21
18fca96a 22DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
a78e3fed 23
24=head1 SYNOPSIS
25
18fca96a 26See L<DBIx::Class::Schema::Loader>
a78e3fed 27
28=head1 DESCRIPTION
29
30=head2 OPTIONS
31
32Available constructor options are:
33
34=head3 additional_base_classes
35
36List of additional base classes your table classes will use.
37
38=head3 left_base_classes
39
40List of additional base classes, that need to be leftmost.
41
42=head3 additional_classes
43
44List of additional classes which your table classes will use.
45
46=head3 constraint
47
48Only load tables matching regex.
49
50=head3 exclude
51
52Exclude tables matching regex.
53
54=head3 debug
55
56Enable debug messages.
57
58=head3 dsn
59
60DBI Data Source Name.
61
a78e3fed 62=head3 password
63
64Password.
65
66=head3 relationships
67
68Try to automatically detect/setup has_a and has_many relationships.
69
70=head3 inflect
71
72An hashref, which contains exceptions to Lingua::EN::Inflect::PL().
73Useful for foreign language column names.
74
75=head3 user
76
77Username.
78
79=head2 METHODS
80
81=cut
82
83=head3 new
84
85Not intended to be called directly. This is used internally by the
18fca96a 86C<new()> method in L<DBIx::Class::Schema::Loader>.
a78e3fed 87
88=cut
89
a4a19f3c 90sub _load_from_connection {
a78e3fed 91 my ( $class, %args ) = @_;
3385ac62 92
e26a4023 93 $class->_loader_debug(1) if $args{debug};
94 $class->_loader_inflect($args{inflect});
95 $class->_loader_db_schema($args{db_schema} || '');
96 $class->_loader_drop_db_schema($args{drop_db_schema});
3385ac62 97
a78e3fed 98 my $additional = $args{additional_classes} || [];
99 $additional = [$additional] unless ref $additional eq 'ARRAY';
3385ac62 100
a78e3fed 101 my $additional_base = $args{additional_base_classes} || [];
102 $additional_base = [$additional_base]
103 unless ref $additional_base eq 'ARRAY';
3385ac62 104
a78e3fed 105 my $left_base = $args{left_base_classes} || [];
106 $left_base = [$left_base] unless ref $left_base eq 'ARRAY';
3385ac62 107
e26a4023 108 my %load_classes_args = (
3385ac62 109 additional => $additional,
110 additional_base => $additional_base,
111 left_base => $left_base,
112 constraint => $args{constraint} || '.*',
113 exclude => $args{exclude},
e26a4023 114 );
115
116 $class->connection($args{dsn}, $args{user},
117 $args{password}, $args{options});
118
3385ac62 119 warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/
120 if $class->_loader_debug;
e26a4023 121
122 $class->_loader_load_classes(%load_classes_args);
66742793 123 $class->_loader_relationships if $args{relationships};
e26a4023 124
3385ac62 125 warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/
126 if $class->_loader_debug;
a4a19f3c 127 $class->storage->dbh->disconnect; # XXX this should be ->storage->disconnect later?
128
129 1;
a78e3fed 130}
131
af6c2665 132# The original table class name during Loader,
3385ac62 133sub _loader_find_table_class {
a4a19f3c 134 my ( $class, $table ) = @_;
e26a4023 135 return $class->_loader_classes->{$table};
a78e3fed 136}
137
af6c2665 138# Returns the moniker for a given table name,
139# for use in $conn->resultset($moniker)
fbd83464 140
141=head3 moniker
142
143Returns the moniker for a given literal table name. Used
144as $schema->resultset($moniker), etc.
145
146=cut
af6c2665 147sub moniker {
a4a19f3c 148 my ( $class, $table ) = @_;
e26a4023 149 return $class->_loader_monikers->{$table};
a78e3fed 150}
151
a78e3fed 152=head3 tables
153
154Returns a sorted list of tables.
155
156 my @tables = $loader->tables;
157
158=cut
159
160sub tables {
a4a19f3c 161 my $class = shift;
e26a4023 162 return sort keys %{ $class->_loader_monikers };
a78e3fed 163}
164
165# Overload in your driver class
3385ac62 166sub _loader_db_classes { croak "ABSTRACT METHOD" }
a78e3fed 167
66742793 168# not a class method.
169sub _loader_stringify_hash {
170 my $href = shift;
171
172 return '{ ' .
173 join(q{, }, map("$_ => $href->{$_}", keys %$href))
174 . ' }';
175}
176
16f6b6ac 177# Inflect a relationship name
178# XXX (should pluralize, but currently also tends to de-pluralize plurals)
179sub _loader_inflect_relname {
180 my ($class, $relname) = @_;
a78e3fed 181
e26a4023 182 if(my $inflections = $class->_loader_inflect) {
16f6b6ac 183 $relname = $inflections->{$relname}
184 if exists $inflections->{$relname};
a78e3fed 185 }
186 else {
16f6b6ac 187 $relname = Lingua::EN::Inflect::PL($relname);
708c0939 188 }
189
16f6b6ac 190 return $relname;
191}
a78e3fed 192
16f6b6ac 193# Set up a simple relation with just a local col and foreign table
194sub _loader_make_simple_rel {
195 my ($class, $table, $other, $col) = @_;
708c0939 196
16f6b6ac 197 my $table_class = $class->_loader_find_table_class($table);
198 my $other_class = $class->_loader_find_table_class($other);
199 my $table_relname = $class->_loader_inflect_relname(lc $table);
66742793 200
16f6b6ac 201 warn qq/\# Belongs_to relationship\n/ if $class->_loader_debug;
202 warn qq/$table_class->belongs_to( '$col' => '$other_class' );\n\n/
203 if $class->_loader_debug;
204 $table_class->belongs_to( $col => $other_class );
708c0939 205
16f6b6ac 206 warn qq/\# Has_many relationship\n/ if $class->_loader_debug;
207 warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
208 . qq/$col);\n\n/
209 if $class->_loader_debug;
708c0939 210
16f6b6ac 211 $other_class->has_many( $table_relname => $table_class, $col);
212}
a78e3fed 213
16f6b6ac 214# Set up a complex relation based on a hashref condition
215sub _loader_make_cond_rel {
216 my ( $class, $table, $other, $cond ) = @_;
a78e3fed 217
16f6b6ac 218 my $table_class = $class->_loader_find_table_class($table);
219 my $other_class = $class->_loader_find_table_class($other);
220 my $table_relname = $class->_loader_inflect_relname(lc $table);
221 my $other_relname = lc $other;
708c0939 222
16f6b6ac 223 # for single-column case, set the relname to the column name,
224 # to make filter accessors work
225 if(scalar keys %$cond == 1) {
226 my ($col) = keys %$cond;
227 $other_relname = $cond->{$col};
4ce22656 228 }
16f6b6ac 229
230 my $rev_cond = { reverse %$cond };
231
232 my $cond_printable = _loader_stringify_hash($cond)
233 if $class->_loader_debug;
234 my $rev_cond_printable = _loader_stringify_hash($rev_cond)
235 if $class->_loader_debug;
236
237 warn qq/\# Belongs_to relationship\n/ if $class->_loader_debug;
238
239 warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/
240 . qq/$cond_printable);\n\n/
241 if $class->_loader_debug;
242
243 $table_class->belongs_to( $other_relname => $other_class, $cond);
244
245 warn qq/\# Has_many relationship\n/ if $class->_loader_debug;
246
247 warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
248 . qq/$rev_cond_printable);\n\n/
249 . qq/);\n\n/
250 if $class->_loader_debug;
251
252 $other_class->has_many( $table_relname => $table_class, $rev_cond);
a78e3fed 253}
254
255# Load and setup classes
3385ac62 256sub _loader_load_classes {
e26a4023 257 my ($class, %args) = @_;
258
259 my $additional = join '',
260 map "use $_;\n", @{$args{additional}};
af6c2665 261
3385ac62 262 my @tables = $class->_loader_tables();
263 my @db_classes = $class->_loader_db_classes();
a78e3fed 264
a78e3fed 265 foreach my $table (@tables) {
e26a4023 266 next unless $table =~ /$args{constraint}/;
267 next if defined $args{exclude} && $table =~ /$args{exclude}/;
af6c2665 268
af6c2665 269 my ($db_schema, $tbl) = split /\./, $table;
af96f52e 270 my $tablename = lc $table;
a78e3fed 271 if($tbl) {
e26a4023 272 $tablename = $class->_loader_drop_db_schema ? $tbl : lc $table;
af6c2665 273 }
e26a4023 274 my $lc_tblname = lc $tablename;
af6c2665 275
3385ac62 276 my $table_moniker = $class->_loader_table2moniker($db_schema, $tbl);
65644119 277 my $table_class = "$class\::$table_moniker";
af6c2665 278
e26a4023 279 # XXX all of this needs require/eval error checking
a4a19f3c 280 $class->inject_base( $table_class, 'DBIx::Class::Core' );
a78e3fed 281 $_->require for @db_classes;
a4a19f3c 282 $class->inject_base( $table_class, $_ ) for @db_classes;
e26a4023 283 $class->inject_base( $table_class, $_ ) for @{$args{additional_base}};
284 eval "package $table_class;$_;" for @{$args{additional}};
285 $class->inject_base( $table_class, $_ ) for @{$args{left_base}};
286
3385ac62 287 warn qq/\# Initializing table "$tablename" as "$table_class"\n/ if $class->_loader_debug;
e26a4023 288 $table_class->table($lc_tblname);
af6c2665 289
3385ac62 290 my ( $cols, $pks ) = $class->_loader_table_info($table);
a78e3fed 291 carp("$table has no primary key") unless @$pks;
a4a19f3c 292 $table_class->add_columns(@$cols);
293 $table_class->set_primary_key(@$pks) if @$pks;
af6c2665 294
3385ac62 295 warn qq/$table_class->table('$tablename');\n/ if $class->_loader_debug;
a78e3fed 296 my $columns = join "', '", @$cols;
3385ac62 297 warn qq/$table_class->add_columns('$columns')\n/ if $class->_loader_debug;
a78e3fed 298 my $primaries = join "', '", @$pks;
3385ac62 299 warn qq/$table_class->set_primary_key('$primaries')\n/ if $class->_loader_debug && @$pks;
af6c2665 300
65644119 301 $class->register_class($table_moniker, $table_class);
e26a4023 302 $class->_loader_classes->{$lc_tblname} = $table_class;
303 $class->_loader_monikers->{$lc_tblname} = $table_moniker;
a78e3fed 304 }
305}
306
307# Find and setup relationships
3385ac62 308sub _loader_relationships {
a4a19f3c 309 my $class = shift;
310 my $dbh = $class->storage->dbh;
708c0939 311 my $quoter = $dbh->get_info(29) || q{"};
a4a19f3c 312 foreach my $table ( $class->tables ) {
708c0939 313 my $rels = {};
314 my $sth = $dbh->foreign_key_info( '',
e26a4023 315 $class->_loader_db_schema, '', '', '', $table );
708c0939 316 next if !$sth;
317 while(my $raw_rel = $sth->fetchrow_hashref) {
318 my $uk_tbl = lc $raw_rel->{UK_TABLE_NAME};
319 my $uk_col = lc $raw_rel->{UK_COLUMN_NAME};
320 my $fk_col = lc $raw_rel->{FK_COLUMN_NAME};
4ce22656 321 my $relid = lc $raw_rel->{UK_NAME};
708c0939 322 $uk_tbl =~ s/$quoter//g;
323 $uk_col =~ s/$quoter//g;
324 $fk_col =~ s/$quoter//g;
4ce22656 325 $relid =~ s/$quoter//g;
326 $rels->{$relid}->{tbl} = $uk_tbl;
327 $rels->{$relid}->{cols}->{$uk_col} = $fk_col;
708c0939 328 }
329
4ce22656 330 foreach my $relid (keys %$rels) {
331 my $reltbl = $rels->{$relid}->{tbl};
332 my $cond = $rels->{$relid}->{cols};
16f6b6ac 333 eval { $class->_loader_make_cond_rel( $table, $reltbl, $cond ) };
708c0939 334 warn qq/\# belongs_to_many failed "$@"\n\n/
3385ac62 335 if $@ && $class->_loader_debug;
a78e3fed 336 }
337 }
338}
339
65644119 340# Make a moniker from a table
3385ac62 341sub _loader_table2moniker {
a4a19f3c 342 my ( $class, $db_schema, $table ) = @_;
af6c2665 343
af96f52e 344 my $db_schema_ns;
af6c2665 345
af96f52e 346 if($table) {
347 $db_schema = ucfirst lc $db_schema;
e26a4023 348 $db_schema_ns = $db_schema if(!$class->_loader_drop_db_schema);
af96f52e 349 } else {
350 $table = $db_schema;
a78e3fed 351 }
af6c2665 352
65644119 353 my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table;
354 $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
af96f52e 355
65644119 356 return $moniker;
a78e3fed 357}
358
359# Overload in driver class
3385ac62 360sub _loader_tables { croak "ABSTRACT METHOD" }
a78e3fed 361
3385ac62 362sub _loader_table_info { croak "ABSTRACT METHOD" }
a78e3fed 363
364=head1 SEE ALSO
365
18fca96a 366L<DBIx::Class::Schema::Loader>
a78e3fed 367
368=cut
369
3701;