tests cleaned a little, added autoloading of on-disk class defs, added currently...
[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;
457eb8a6 5use base qw/Class::Accessor::Fast/;
2a4b8262 6use Class::C3;
a78e3fed 7use Carp;
8use Lingua::EN::Inflect;
a78e3fed 9require DBIx::Class::Core;
a4a19f3c 10
3980d69c 11# The first group are all arguments which are may be defaulted within,
12# The last two (classes, monikers) are generated locally:
13
14__PACKAGE__->mk_ro_accessors(qw/
15 schema
16 dsn
17 user
18 password
19 options
20 exclude
21 constraint
22 additional_classes
23 additional_base_classes
24 left_base_classes
c2849787 25 components
26 resultset_components
3980d69c 27 relationships
28 inflect
29 db_schema
30 drop_db_schema
31 debug
32
33 classes
34 monikers
35 /);
a4a19f3c 36
a78e3fed 37=head1 NAME
38
18fca96a 39DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
a78e3fed 40
41=head1 SYNOPSIS
42
18fca96a 43See L<DBIx::Class::Schema::Loader>
a78e3fed 44
45=head1 DESCRIPTION
46
457eb8a6 47This is the base class for the vendor-specific C<DBIx::Class::Schema::*>
48classes, and implements the common functionality between them.
49
50=head1 OPTIONS
a78e3fed 51
52Available constructor options are:
53
457eb8a6 54=head2 additional_base_classes
a78e3fed 55
56List of additional base classes your table classes will use.
57
457eb8a6 58=head2 left_base_classes
a78e3fed 59
60List of additional base classes, that need to be leftmost.
61
457eb8a6 62=head2 additional_classes
a78e3fed 63
64List of additional classes which your table classes will use.
65
c2849787 66=head2 components
67
68List of additional components to be loaded into your table classes.
69A good example would be C<ResultSetManager>.
70
71=head2 resultset_components
72
73List of additional resultset components to be loaded into your table
74classes. A good example would be C<AlwaysRS>. Component
75C<ResultSetManager> will be automatically added to the above
76C<components> list if this option is set.
77
457eb8a6 78=head2 constraint
a78e3fed 79
80Only load tables matching regex.
81
457eb8a6 82=head2 exclude
a78e3fed 83
84Exclude tables matching regex.
85
457eb8a6 86=head2 debug
a78e3fed 87
88Enable debug messages.
89
457eb8a6 90=head2 dsn
a78e3fed 91
92DBI Data Source Name.
93
457eb8a6 94=head2 password
a78e3fed 95
96Password.
97
457eb8a6 98=head2 relationships
a78e3fed 99
100Try to automatically detect/setup has_a and has_many relationships.
101
457eb8a6 102=head2 inflect
a78e3fed 103
104An hashref, which contains exceptions to Lingua::EN::Inflect::PL().
105Useful for foreign language column names.
106
457eb8a6 107=head2 user
a78e3fed 108
109Username.
110
457eb8a6 111=head1 METHODS
2a4b8262 112
a78e3fed 113=cut
114
3980d69c 115# ensure that a peice of object data is a valid arrayref, creating
116# an empty one or encapsulating whatever's there.
117sub _ensure_arrayref {
118 my $self = shift;
e26a4023 119
3980d69c 120 foreach (@_) {
121 $self->{$_} ||= [];
122 $self->{$_} = [ $self->{$_} ]
123 unless ref $self->{$_} eq 'ARRAY';
124 }
a78e3fed 125}
126
457eb8a6 127=head2 new
128
129Constructor for L<DBIx::Class::Schema::Loader::Generic>, used internally
130by L<DBIx::Class::Schema::Loader>.
131
132=cut
133
3980d69c 134sub new {
135 my ( $class, %args ) = @_;
a78e3fed 136
3980d69c 137 my $self = { %args };
fbd83464 138
3980d69c 139 bless $self => $class;
fbd83464 140
3980d69c 141 $self->{db_schema} ||= '';
142 $self->{constraint} ||= '.*';
143 $self->{inflect} ||= {};
144 $self->_ensure_arrayref(qw/additional_classes
145 additional_base_classes
c2849787 146 left_base_classes
147 components
148 resultset_components/);
149
150 push(@{$self->{components}}, 'ResultSetManager')
151 if @{$self->{resultset_components}};
fbd83464 152
3980d69c 153 $self->{monikers} = {};
154 $self->{classes} = {};
a78e3fed 155
2a4b8262 156 $self;
157}
158
457eb8a6 159=head2 load
160
161Does the actual schema-construction work, used internally by
162L<DBIx::Class::Schema::Loader> right after object construction.
163
164=cut
165
2a4b8262 166sub load {
167 my $self = shift;
168
3980d69c 169 $self->schema->connection($self->dsn, $self->user,
170 $self->password, $self->options);
a78e3fed 171
3980d69c 172 warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/
173 if $self->debug;
a78e3fed 174
3980d69c 175 $self->_load_classes;
176 $self->_load_relationships if $self->relationships;
a78e3fed 177
3980d69c 178 warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/
179 if $self->debug;
52bf3f26 180 $self->schema->storage->disconnect;
a78e3fed 181
3980d69c 182 $self;
a78e3fed 183}
184
185# Overload in your driver class
3980d69c 186sub _db_classes { croak "ABSTRACT METHOD" }
66742793 187
16f6b6ac 188# Inflect a relationship name
189# XXX (should pluralize, but currently also tends to de-pluralize plurals)
3980d69c 190sub _inflect_relname {
191 my ($self, $relname) = @_;
708c0939 192
3980d69c 193 return $self->inflect->{$relname} if exists $self->inflect->{$relname};
194 return Lingua::EN::Inflect::PL($relname);
16f6b6ac 195}
a78e3fed 196
16f6b6ac 197# Set up a simple relation with just a local col and foreign table
3980d69c 198sub _make_simple_rel {
199 my ($self, $table, $other, $col) = @_;
708c0939 200
3980d69c 201 my $table_class = $self->classes->{$table};
202 my $other_class = $self->classes->{$other};
203 my $table_relname = $self->_inflect_relname(lc $table);
66742793 204
3980d69c 205 warn qq/\# Belongs_to relationship\n/ if $self->debug;
16f6b6ac 206 warn qq/$table_class->belongs_to( '$col' => '$other_class' );\n\n/
3980d69c 207 if $self->debug;
16f6b6ac 208 $table_class->belongs_to( $col => $other_class );
708c0939 209
3980d69c 210 warn qq/\# Has_many relationship\n/ if $self->debug;
16f6b6ac 211 warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
212 . qq/$col);\n\n/
3980d69c 213 if $self->debug;
708c0939 214
16f6b6ac 215 $other_class->has_many( $table_relname => $table_class, $col);
216}
a78e3fed 217
3980d69c 218# not a class method, just a helper for cond_rel XXX
219sub _stringify_hash {
220 my $href = shift;
221
222 return '{ ' .
223 join(q{, }, map("$_ => $href->{$_}", keys %$href))
224 . ' }';
225}
226
16f6b6ac 227# Set up a complex relation based on a hashref condition
3980d69c 228sub _make_cond_rel {
229 my ( $self, $table, $other, $cond ) = @_;
a78e3fed 230
3980d69c 231 my $table_class = $self->classes->{$table};
232 my $other_class = $self->classes->{$other};
233 my $table_relname = $self->_inflect_relname(lc $table);
16f6b6ac 234 my $other_relname = lc $other;
708c0939 235
16f6b6ac 236 # for single-column case, set the relname to the column name,
237 # to make filter accessors work
238 if(scalar keys %$cond == 1) {
239 my ($col) = keys %$cond;
240 $other_relname = $cond->{$col};
4ce22656 241 }
16f6b6ac 242
243 my $rev_cond = { reverse %$cond };
244
c5de0b0c 245 for (keys %$rev_cond) {
c2849787 246 $rev_cond->{"foreign.$_"} = "self.".$rev_cond->{$_};
247 delete $rev_cond->{$_};
c5de0b0c 248 }
249
3980d69c 250 my $cond_printable = _stringify_hash($cond)
251 if $self->debug;
252 my $rev_cond_printable = _stringify_hash($rev_cond)
253 if $self->debug;
16f6b6ac 254
3980d69c 255 warn qq/\# Belongs_to relationship\n/ if $self->debug;
16f6b6ac 256
257 warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/
258 . qq/$cond_printable);\n\n/
3980d69c 259 if $self->debug;
16f6b6ac 260
261 $table_class->belongs_to( $other_relname => $other_class, $cond);
262
3980d69c 263 warn qq/\# Has_many relationship\n/ if $self->debug;
16f6b6ac 264
265 warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
266 . qq/$rev_cond_printable);\n\n/
267 . qq/);\n\n/
3980d69c 268 if $self->debug;
16f6b6ac 269
270 $other_class->has_many( $table_relname => $table_class, $rev_cond);
a78e3fed 271}
272
42c0680e 273sub _use {
274 my $self = shift;
275 my $target = shift;
276
277 foreach (@_) {
278 $_->require or croak ($_ . "->require: $@");
279 eval "package $target; use $_;";
280 croak "use $_: $@" if $@;
281 }
282}
283
284sub _inject {
285 my $self = shift;
286 my $target = shift;
287 my $schema = $self->schema;
288
289 foreach (@_) {
290 $_->require or croak ($_ . "->require: $@");
291 $schema->inject_base($target, $_);
292 }
293}
294
a78e3fed 295# Load and setup classes
3980d69c 296sub _load_classes {
297 my $self = shift;
af6c2665 298
3980d69c 299 my @tables = $self->_tables();
300 my @db_classes = $self->_db_classes();
301 my $schema = $self->schema;
a78e3fed 302
a78e3fed 303 foreach my $table (@tables) {
3980d69c 304 my $constraint = $self->constraint;
305 my $exclude = $self->exclude;
306
307 next unless $table =~ /$constraint/;
308 next if defined $exclude && $table =~ /$exclude/;
af6c2665 309
af6c2665 310 my ($db_schema, $tbl) = split /\./, $table;
af96f52e 311 my $tablename = lc $table;
a78e3fed 312 if($tbl) {
3980d69c 313 $tablename = $self->drop_db_schema ? $tbl : lc $table;
af6c2665 314 }
3980d69c 315 my $lc_tblname = lc $tablename;
af6c2665 316
3980d69c 317 my $table_moniker = $self->_table2moniker($db_schema, $tbl);
318 my $table_class = $schema . q{::} . $table_moniker;
af6c2665 319
42c0680e 320 $self->_inject($table_class, 'DBIx::Class::Core');
321 $self->_inject($table_class, @db_classes);
322 $self->_inject($table_class, @{$self->additional_base_classes});
323 $self->_use ($table_class, @{$self->additional_classes});
324 $self->_inject($table_class, @{$self->left_base_classes});
c2849787 325 $table_class->load_components(@{$self->components});
326 $table_class->load_resultset_components(@{$self->resultset_components})
327 if @{$self->resultset_components};
3980d69c 328
329 warn qq/\# Initializing table "$tablename" as "$table_class"\n/
330 if $self->debug;
e26a4023 331 $table_class->table($lc_tblname);
af6c2665 332
3980d69c 333 my ( $cols, $pks ) = $self->_table_info($table);
a78e3fed 334 carp("$table has no primary key") unless @$pks;
a4a19f3c 335 $table_class->add_columns(@$cols);
336 $table_class->set_primary_key(@$pks) if @$pks;
af6c2665 337
3980d69c 338 warn qq/$table_class->table('$tablename');\n/ if $self->debug;
a78e3fed 339 my $columns = join "', '", @$cols;
3980d69c 340 warn qq/$table_class->add_columns('$columns')\n/ if $self->debug;
a78e3fed 341 my $primaries = join "', '", @$pks;
3980d69c 342 warn qq/$table_class->set_primary_key('$primaries')\n/
343 if $self->debug && @$pks;
af6c2665 344
c2849787 345 $table_class->require;
346 if($@ && $@ !~ /^Can't locate /) {
347 croak "Failed to load external class definition"
348 . "for '$table_class': $@";
349 }
350
351 warn qq/# Loaded external class definition for '$table_class'\n/
352 if $self->debug;
353
3980d69c 354 $schema->register_class($table_moniker, $table_class);
355 $self->classes->{$lc_tblname} = $table_class;
356 $self->monikers->{$lc_tblname} = $table_moniker;
a78e3fed 357 }
358}
359
457eb8a6 360=head2 tables
3980d69c 361
8a6b44ef 362Returns a sorted list of loaded tables, using the original database table
363names. Actually generated from the keys of the C<monikers> hash below.
3980d69c 364
457eb8a6 365 my @tables = $schema->loader->tables;
3980d69c 366
367=cut
368
369sub tables {
370 my $self = shift;
371
372 return sort keys %{ $self->monikers };
373}
374
a78e3fed 375# Find and setup relationships
3980d69c 376sub _load_relationships {
377 my $self = shift;
378
379 my $dbh = $self->schema->storage->dbh;
708c0939 380 my $quoter = $dbh->get_info(29) || q{"};
3980d69c 381 foreach my $table ( $self->tables ) {
708c0939 382 my $rels = {};
383 my $sth = $dbh->foreign_key_info( '',
3980d69c 384 $self->db_schema, '', '', '', $table );
708c0939 385 next if !$sth;
386 while(my $raw_rel = $sth->fetchrow_hashref) {
387 my $uk_tbl = lc $raw_rel->{UK_TABLE_NAME};
388 my $uk_col = lc $raw_rel->{UK_COLUMN_NAME};
389 my $fk_col = lc $raw_rel->{FK_COLUMN_NAME};
4ce22656 390 my $relid = lc $raw_rel->{UK_NAME};
708c0939 391 $uk_tbl =~ s/$quoter//g;
392 $uk_col =~ s/$quoter//g;
393 $fk_col =~ s/$quoter//g;
4ce22656 394 $relid =~ s/$quoter//g;
395 $rels->{$relid}->{tbl} = $uk_tbl;
396 $rels->{$relid}->{cols}->{$uk_col} = $fk_col;
708c0939 397 }
398
4ce22656 399 foreach my $relid (keys %$rels) {
400 my $reltbl = $rels->{$relid}->{tbl};
401 my $cond = $rels->{$relid}->{cols};
3980d69c 402 eval { $self->_make_cond_rel( $table, $reltbl, $cond ) };
708c0939 403 warn qq/\# belongs_to_many failed "$@"\n\n/
3980d69c 404 if $@ && $self->debug;
a78e3fed 405 }
406 }
407}
408
65644119 409# Make a moniker from a table
3980d69c 410sub _table2moniker {
411 my ( $self, $db_schema, $table ) = @_;
af6c2665 412
af96f52e 413 my $db_schema_ns;
af6c2665 414
af96f52e 415 if($table) {
416 $db_schema = ucfirst lc $db_schema;
3980d69c 417 $db_schema_ns = $db_schema if(!$self->drop_db_schema);
af96f52e 418 } else {
419 $table = $db_schema;
a78e3fed 420 }
af6c2665 421
65644119 422 my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table;
423 $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
af96f52e 424
65644119 425 return $moniker;
a78e3fed 426}
427
428# Overload in driver class
3980d69c 429sub _tables { croak "ABSTRACT METHOD" }
a78e3fed 430
3980d69c 431sub _table_info { croak "ABSTRACT METHOD" }
a78e3fed 432
457eb8a6 433=head2 monikers
434
435Returns a hashref of loaded table-to-moniker mappings for the original
436database table names.
437
438 my $monikers = $schema->loader->monikers;
439 my $foo_tbl_moniker = $monikers->{foo_tbl};
440 # -or-
441 my $foo_tbl_moniker = $schema->loader->monikers->{foo_tbl};
442 # $foo_tbl_moniker would look like "FooTbl"
443
444=head2 classes
445
446Returns a hashref of table-to-classname mappings for the original database
447table names. You probably shouldn't be using this for any normal or simple
448usage of your Schema. The usual way to run queries on your tables is via
449C<$schema-E<gt>resultset('FooTbl')>, where C<FooTbl> is a moniker as
450returned by C<monikers> above.
451
452 my $classes = $schema->loader->classes;
453 my $foo_tbl_class = $classes->{foo_tbl};
454 # -or-
455 my $foo_tbl_class = $schema->loader->classes->{foo_tbl};
456 # $foo_tbl_class would look like "My::Schema::FooTbl",
457 # assuming the schema class is "My::Schema"
458
a78e3fed 459=head1 SEE ALSO
460
18fca96a 461L<DBIx::Class::Schema::Loader>
a78e3fed 462
463=cut
464
4651;