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