more work on components, base classes, and resultset_components - still broken in...
[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
9fa99683 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
9fa99683 147 components
148 resultset_components/);
c2849787 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
9fa99683 320 { no strict 'refs';
321 @{"${table_class}::ISA"} = ($schema);
322 }
42c0680e 323 $self->_use ($table_class, @{$self->additional_classes});
9fa99683 324 $self->_inject($table_class, @{$self->additional_base_classes});
325 $table_class->load_components(@{$self->components}, @db_classes, 'Core');
326 $table_class->load_resultset_components(@{$self->resultset_components})
327 if @{$self->resultset_components};
42c0680e 328 $self->_inject($table_class, @{$self->left_base_classes});
3980d69c 329
330 warn qq/\# Initializing table "$tablename" as "$table_class"\n/
331 if $self->debug;
e26a4023 332 $table_class->table($lc_tblname);
af6c2665 333
3980d69c 334 my ( $cols, $pks ) = $self->_table_info($table);
a78e3fed 335 carp("$table has no primary key") unless @$pks;
a4a19f3c 336 $table_class->add_columns(@$cols);
337 $table_class->set_primary_key(@$pks) if @$pks;
af6c2665 338
3980d69c 339 warn qq/$table_class->table('$tablename');\n/ if $self->debug;
a78e3fed 340 my $columns = join "', '", @$cols;
3980d69c 341 warn qq/$table_class->add_columns('$columns')\n/ if $self->debug;
a78e3fed 342 my $primaries = join "', '", @$pks;
3980d69c 343 warn qq/$table_class->set_primary_key('$primaries')\n/
344 if $self->debug && @$pks;
af6c2665 345
c2849787 346 $table_class->require;
347 if($@ && $@ !~ /^Can't locate /) {
348 croak "Failed to load external class definition"
349 . "for '$table_class': $@";
350 }
351
352 warn qq/# Loaded external class definition for '$table_class'\n/
353 if $self->debug;
354
3980d69c 355 $schema->register_class($table_moniker, $table_class);
356 $self->classes->{$lc_tblname} = $table_class;
357 $self->monikers->{$lc_tblname} = $table_moniker;
a78e3fed 358 }
359}
360
457eb8a6 361=head2 tables
3980d69c 362
8a6b44ef 363Returns a sorted list of loaded tables, using the original database table
364names. Actually generated from the keys of the C<monikers> hash below.
3980d69c 365
457eb8a6 366 my @tables = $schema->loader->tables;
3980d69c 367
368=cut
369
370sub tables {
371 my $self = shift;
372
373 return sort keys %{ $self->monikers };
374}
375
a78e3fed 376# Find and setup relationships
3980d69c 377sub _load_relationships {
378 my $self = shift;
379
380 my $dbh = $self->schema->storage->dbh;
708c0939 381 my $quoter = $dbh->get_info(29) || q{"};
3980d69c 382 foreach my $table ( $self->tables ) {
708c0939 383 my $rels = {};
384 my $sth = $dbh->foreign_key_info( '',
3980d69c 385 $self->db_schema, '', '', '', $table );
708c0939 386 next if !$sth;
387 while(my $raw_rel = $sth->fetchrow_hashref) {
388 my $uk_tbl = lc $raw_rel->{UK_TABLE_NAME};
389 my $uk_col = lc $raw_rel->{UK_COLUMN_NAME};
390 my $fk_col = lc $raw_rel->{FK_COLUMN_NAME};
4ce22656 391 my $relid = lc $raw_rel->{UK_NAME};
708c0939 392 $uk_tbl =~ s/$quoter//g;
393 $uk_col =~ s/$quoter//g;
394 $fk_col =~ s/$quoter//g;
4ce22656 395 $relid =~ s/$quoter//g;
396 $rels->{$relid}->{tbl} = $uk_tbl;
397 $rels->{$relid}->{cols}->{$uk_col} = $fk_col;
708c0939 398 }
399
4ce22656 400 foreach my $relid (keys %$rels) {
401 my $reltbl = $rels->{$relid}->{tbl};
402 my $cond = $rels->{$relid}->{cols};
3980d69c 403 eval { $self->_make_cond_rel( $table, $reltbl, $cond ) };
708c0939 404 warn qq/\# belongs_to_many failed "$@"\n\n/
3980d69c 405 if $@ && $self->debug;
a78e3fed 406 }
407 }
408}
409
65644119 410# Make a moniker from a table
3980d69c 411sub _table2moniker {
412 my ( $self, $db_schema, $table ) = @_;
af6c2665 413
af96f52e 414 my $db_schema_ns;
af6c2665 415
af96f52e 416 if($table) {
417 $db_schema = ucfirst lc $db_schema;
3980d69c 418 $db_schema_ns = $db_schema if(!$self->drop_db_schema);
af96f52e 419 } else {
420 $table = $db_schema;
a78e3fed 421 }
af6c2665 422
65644119 423 my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table;
424 $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
af96f52e 425
65644119 426 return $moniker;
a78e3fed 427}
428
429# Overload in driver class
3980d69c 430sub _tables { croak "ABSTRACT METHOD" }
a78e3fed 431
3980d69c 432sub _table_info { croak "ABSTRACT METHOD" }
a78e3fed 433
457eb8a6 434=head2 monikers
435
436Returns a hashref of loaded table-to-moniker mappings for the original
437database table names.
438
439 my $monikers = $schema->loader->monikers;
440 my $foo_tbl_moniker = $monikers->{foo_tbl};
441 # -or-
442 my $foo_tbl_moniker = $schema->loader->monikers->{foo_tbl};
443 # $foo_tbl_moniker would look like "FooTbl"
444
445=head2 classes
446
447Returns a hashref of table-to-classname mappings for the original database
448table names. You probably shouldn't be using this for any normal or simple
449usage of your Schema. The usual way to run queries on your tables is via
450C<$schema-E<gt>resultset('FooTbl')>, where C<FooTbl> is a moniker as
451returned by C<monikers> above.
452
453 my $classes = $schema->loader->classes;
454 my $foo_tbl_class = $classes->{foo_tbl};
455 # -or-
456 my $foo_tbl_class = $schema->loader->classes->{foo_tbl};
457 # $foo_tbl_class would look like "My::Schema::FooTbl",
458 # assuming the schema class is "My::Schema"
459
a78e3fed 460=head1 SEE ALSO
461
18fca96a 462L<DBIx::Class::Schema::Loader>
a78e3fed 463
464=cut
465
4661;