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