refactoring top-level loading code with an eye towards the ability to add new tables...
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Base.pm
CommitLineData
996be9ee 1package DBIx::Class::Schema::Loader::Base;
2
3use strict;
4use warnings;
5use base qw/Class::Accessor::Fast/;
6use Class::C3;
fa994d3c 7use Carp::Clan qw/^DBIx::Class/;
996be9ee 8use UNIVERSAL::require;
9use DBIx::Class::Schema::Loader::RelBuilder;
10use Data::Dump qw/ dump /;
11use POSIX qw//;
dd03ee1a 12use File::Spec qw//;
419a2eeb 13use Cwd qw//;
7cab3ab7 14use Digest::MD5 qw//;
996be9ee 15require DBIx::Class;
16
32f784fc 17our $VERSION = '0.03999_01';
18
996be9ee 19__PACKAGE__->mk_ro_accessors(qw/
20 schema
21 schema_class
22
23 exclude
24 constraint
25 additional_classes
26 additional_base_classes
27 left_base_classes
28 components
29 resultset_components
59cfa251 30 skip_relationships
996be9ee 31 moniker_map
32 inflect_singular
33 inflect_plural
34 debug
35 dump_directory
d65cda9e 36 dump_overwrite
996be9ee 37
996be9ee 38 db_schema
39 _tables
40 classes
41 monikers
42 /);
43
44=head1 NAME
45
46DBIx::Class::Schema::Loader::Base - Base DBIx::Class::Schema::Loader Implementation.
47
48=head1 SYNOPSIS
49
50See L<DBIx::Class::Schema::Loader>
51
52=head1 DESCRIPTION
53
54This is the base class for the storage-specific C<DBIx::Class::Schema::*>
55classes, and implements the common functionality between them.
56
57=head1 CONSTRUCTOR OPTIONS
58
59These constructor options are the base options for
60L<DBIx::Class::Schema::Loader/loader_opts>. Available constructor options are:
61
59cfa251 62=head2 skip_relationships
996be9ee 63
59cfa251 64Skip setting up relationships. The default is to attempt the loading
65of relationships.
996be9ee 66
67=head2 debug
68
69If set to true, each constructive L<DBIx::Class> statement the loader
70decides to execute will be C<warn>-ed before execution.
71
d65cda9e 72=head2 db_schema
73
74Set the name of the schema to load (schema in the sense that your database
75vendor means it). Does not currently support loading more than one schema
76name.
77
996be9ee 78=head2 constraint
79
80Only load tables matching regex. Best specified as a qr// regex.
81
82=head2 exclude
83
84Exclude tables matching regex. Best specified as a qr// regex.
85
86=head2 moniker_map
87
8f9d7ce5 88Overrides the default table name to moniker translation. Can be either
89a hashref of table keys and moniker values, or a coderef for a translator
996be9ee 90function taking a single scalar table name argument and returning
91a scalar moniker. If the hash entry does not exist, or the function
92returns a false value, the code falls back to default behavior
93for that table name.
94
95The default behavior is: C<join '', map ucfirst, split /[\W_]+/, lc $table>,
96which is to say: lowercase everything, split up the table name into chunks
97anywhere a non-alpha-numeric character occurs, change the case of first letter
98of each chunk to upper case, and put the chunks back together. Examples:
99
100 Table Name | Moniker Name
101 ---------------------------
102 luser | Luser
103 luser_group | LuserGroup
104 luser-opts | LuserOpts
105
106=head2 inflect_plural
107
108Just like L</moniker_map> above (can be hash/code-ref, falls back to default
109if hash key does not exist or coderef returns false), but acts as a map
110for pluralizing relationship names. The default behavior is to utilize
111L<Lingua::EN::Inflect::Number/to_PL>.
112
113=head2 inflect_singular
114
115As L</inflect_plural> above, but for singularizing relationship names.
116Default behavior is to utilize L<Lingua::EN::Inflect::Number/to_S>.
117
118=head2 additional_base_classes
119
120List of additional base classes all of your table classes will use.
121
122=head2 left_base_classes
123
124List of additional base classes all of your table classes will use
125that need to be leftmost.
126
127=head2 additional_classes
128
129List of additional classes which all of your table classes will use.
130
131=head2 components
132
133List of additional components to be loaded into all of your table
134classes. A good example would be C<ResultSetManager>.
135
136=head2 resultset_components
137
8f9d7ce5 138List of additional ResultSet components to be loaded into your table
996be9ee 139classes. A good example would be C<AlwaysRS>. Component
140C<ResultSetManager> will be automatically added to the above
141C<components> list if this option is set.
142
996be9ee 143=head2 dump_directory
144
145This option is designed to be a tool to help you transition from this
146loader to a manually-defined schema when you decide it's time to do so.
147
148The value of this option is a perl libdir pathname. Within
149that directory this module will create a baseline manual
150L<DBIx::Class::Schema> module set, based on what it creates at runtime
151in memory.
152
153The created schema class will have the same classname as the one on
154which you are setting this option (and the ResultSource classes will be
7cab3ab7 155based on this name as well).
996be9ee 156
8f9d7ce5 157Normally you wouldn't hard-code this setting in your schema class, as it
996be9ee 158is meant for one-time manual usage.
159
160See L<DBIx::Class::Schema::Loader/dump_to_dir> for examples of the
161recommended way to access this functionality.
162
d65cda9e 163=head2 dump_overwrite
164
7cab3ab7 165Default false. If true, Loader will unconditionally delete any existing
166files before creating the new ones from scratch when dumping a schema to disk.
167
168The default behavior is instead to only replace the top portion of the
169file, up to and including the final stanza which contains
170C<# DO NOT MODIFY THIS OR ANYTHING ABOVE!>
171leaving any customizations you placed after that as they were.
172
173When C<dump_overwrite> is not set, if the output file already exists,
174but the aforementioned final stanza is not found, or the checksum
175contained there does not match the generated contents, Loader will
176croak and not touch the file.
d65cda9e 177
996be9ee 178=head1 METHODS
179
180None of these methods are intended for direct invocation by regular
181users of L<DBIx::Class::Schema::Loader>. Anything you can find here
182can also be found via standard L<DBIx::Class::Schema> methods somehow.
183
184=cut
185
186# ensure that a peice of object data is a valid arrayref, creating
187# an empty one or encapsulating whatever's there.
188sub _ensure_arrayref {
189 my $self = shift;
190
191 foreach (@_) {
192 $self->{$_} ||= [];
193 $self->{$_} = [ $self->{$_} ]
194 unless ref $self->{$_} eq 'ARRAY';
195 }
196}
197
198=head2 new
199
200Constructor for L<DBIx::Class::Schema::Loader::Base>, used internally
201by L<DBIx::Class::Schema::Loader>.
202
203=cut
204
205sub new {
206 my ( $class, %args ) = @_;
207
208 my $self = { %args };
209
210 bless $self => $class;
211
212 $self->{db_schema} ||= '';
213 $self->_ensure_arrayref(qw/additional_classes
214 additional_base_classes
215 left_base_classes
216 components
217 resultset_components
218 /);
219
220 push(@{$self->{components}}, 'ResultSetManager')
221 if @{$self->{resultset_components}};
222
223 $self->{monikers} = {};
224 $self->{classes} = {};
225
996be9ee 226 $self->{schema_class} ||= ( ref $self->{schema} || $self->{schema} );
227 $self->{schema} ||= $self->{schema_class};
228
229 $self;
230}
231
419a2eeb 232sub _find_file_in_inc {
233 my ($self, $file) = @_;
234
235 foreach my $prefix (@INC) {
236 my $fullpath = $prefix . '/' . $file;
237 return $fullpath if -f $fullpath;
238 }
239
240 return;
241}
242
996be9ee 243sub _load_external {
f96ef30f 244 my ($self, $class) = @_;
245
246 my $class_path = $class;
247 $class_path =~ s{::}{/}g;
248 $class_path .= '.pm';
249
250 my $inc_path = $self->_find_file_in_inc($class_path);
251
252 return if !$inc_path;
253
254 my $real_dump_path = $self->dump_directory
255 ? Cwd::abs_path(
256 File::Spec->catfile($self->dump_directory, $class_path)
257 )
258 : '';
259 my $real_inc_path = Cwd::abs_path($inc_path);
260 return if $real_inc_path eq $real_dump_path;
261
262 $class->require;
263 croak "Failed to load external class definition"
264 . " for '$class': $@"
265 if $@;
266
267 # If we make it to here, we loaded an external definition
268 warn qq/# Loaded external class definition for '$class'\n/
269 if $self->debug;
270
271 # The rest is only relevant when dumping
272 return if !$self->dump_directory;
273
274 croak 'Failed to locate actual external module file for '
275 . "'$class'"
276 if !$real_inc_path;
277 open(my $fh, '<', $real_inc_path)
278 or croak "Failed to open '$real_inc_path' for reading: $!";
279 $self->_ext_stmt($class,
280 qq|# These lines were loaded from '$real_inc_path' found in \@INC.|
281 .q|# They are now part of the custom portion of this file|
282 .q|# for you to hand-edit. If you do not either delete|
283 .q|# this section or remove that file from @INC, this section|
284 .q|# will be repeated redundantly when you re-create this|
285 .q|# file again via Loader!|
286 );
287 while(<$fh>) {
288 chomp;
289 $self->_ext_stmt($class, $_);
996be9ee 290 }
f96ef30f 291 $self->_ext_stmt($class,
292 q|# End of lines loaded from '$real_inc_path' |
293 );
294 close($fh)
295 or croak "Failed to close $real_inc_path: $!";
996be9ee 296}
297
298=head2 load
299
300Does the actual schema-construction work.
301
302=cut
303
304sub load {
305 my $self = shift;
306
f96ef30f 307 # First, use _tables_list with constraint and exclude
308 # to get a list of tables to operate on
309
310 my $constraint = $self->constraint;
311 my $exclude = $self->exclude;
312 my @tables = sort $self->_tables_list;
313
314 if(!@tables) {
315 warn "No tables found in database, nothing to load";
316 }
317 else {
318 @tables = grep { /$constraint/ } @tables if $constraint;
319 @tables = grep { ! /$exclude/ } @tables if $exclude;
320
321 warn "All tables excluded by constraint/exclude, nothing to load"
322 if !@tables;
323 }
324
325 # Save the tables list
326 $self->{_tables} = \@tables;
327
328 # Set up classes/monikers
329 {
330 no warnings 'redefine';
331 local *Class::C3::reinitialize = sub { };
332 use warnings;
333
334 $self->_make_src_class($_) for @tables;
335 }
336
337 Class::C3::reinitialize;
338
339 $self->_setup_src_meta($_) for @tables;
340
59cfa251 341 $self->_load_relationships if ! $self->skip_relationships;
f96ef30f 342 $self->_load_external($_)
343 for ($self->schema_class, values %{$self->classes});
344
996be9ee 345 $self->_dump_to_dir if $self->dump_directory;
346
5223f24a 347 # Drop temporary cache
348 delete $self->{_cache};
349
996be9ee 350 1;
351}
352
353sub _get_dump_filename {
354 my ($self, $class) = (@_);
355
356 $class =~ s{::}{/}g;
357 return $self->dump_directory . q{/} . $class . q{.pm};
358}
359
360sub _ensure_dump_subdirs {
361 my ($self, $class) = (@_);
362
363 my @name_parts = split(/::/, $class);
dd03ee1a 364 pop @name_parts; # we don't care about the very last element,
365 # which is a filename
366
996be9ee 367 my $dir = $self->dump_directory;
7cab3ab7 368 while (1) {
369 if(!-d $dir) {
25328cc4 370 mkdir($dir) or croak "mkdir('$dir') failed: $!";
996be9ee 371 }
7cab3ab7 372 last if !@name_parts;
373 $dir = File::Spec->catdir($dir, shift @name_parts);
996be9ee 374 }
375}
376
377sub _dump_to_dir {
378 my ($self) = @_;
379
380 my $target_dir = $self->dump_directory;
d65cda9e 381
fc2b71fd 382 my $schema_class = $self->schema_class;
996be9ee 383
25328cc4 384 croak "Must specify target directory for dumping!" if ! $target_dir;
996be9ee 385
fc2b71fd 386 warn "Dumping manual schema for $schema_class to directory $target_dir ...\n";
996be9ee 387
7cab3ab7 388 my $schema_text =
389 qq|package $schema_class;\n\n|
390 . qq|use strict;\nuse warnings;\n\n|
391 . qq|use base 'DBIx::Class::Schema';\n\n|
392 . qq|__PACKAGE__->load_classes;\n|;
996be9ee 393
7cab3ab7 394 $self->_write_classfile($schema_class, $schema_text);
996be9ee 395
7cab3ab7 396 foreach my $src_class (sort keys %{$self->{_dump_storage}}) {
397 my $src_text =
398 qq|package $src_class;\n\n|
399 . qq|use strict;\nuse warnings;\n\n|
400 . qq|use base 'DBIx::Class';\n\n|;
996be9ee 401
7cab3ab7 402 $self->_write_classfile($src_class, $src_text);
02356864 403 }
996be9ee 404
7cab3ab7 405 warn "Schema dump completed.\n";
406}
407
408sub _write_classfile {
409 my ($self, $class, $text) = @_;
410
411 my $filename = $self->_get_dump_filename($class);
412 $self->_ensure_dump_subdirs($class);
413
414 if (-f $filename && $self->dump_overwrite) {
415 warn "Deleting existing file '$filename' due to "
416 . "'dump_overwrite' setting\n";
417 unlink($filename);
418 }
419
419a2eeb 420 my $custom_content = $self->_get_custom_content($class, $filename);
7cab3ab7 421
422 $custom_content ||= qq|\n# You can replace this text with custom|
423 . qq| content, and it will be preserved on regeneration|
424 . qq|\n1;\n|;
425
426 $text .= qq|$_\n|
427 for @{$self->{_dump_storage}->{$class} || []};
428
429 $text .= qq|\n\n# Created by DBIx::Class::Schema::Loader|
430 . qq| v| . $DBIx::Class::Schema::Loader::VERSION
431 . q| @ | . POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
432 . qq|\n# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:|;
433
434 open(my $fh, '>', $filename)
435 or croak "Cannot open '$filename' for writing: $!";
436
437 # Write the top half and its MD5 sum
438 print $fh $text . Digest::MD5::md5_base64($text) . "\n\n";
439
440 # Write out anything loaded via external partial class file in @INC
441 print $fh qq|$_\n|
442 for @{$self->{_ext_storage}->{$class} || []};
443
444 print $fh $custom_content;
445
446 close($fh)
447 or croak "Cannot close '$filename': $!";
448}
449
450sub _get_custom_content {
451 my ($self, $class, $filename) = @_;
452
453 return if ! -f $filename;
454 open(my $fh, '<', $filename)
455 or croak "Cannot open '$filename' for reading: $!";
456
457 my $mark_re =
419a2eeb 458 qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
7cab3ab7 459
460 my $found = 0;
461 my $buffer = '';
462 while(<$fh>) {
463 if(!$found && /$mark_re/) {
464 $found = 1;
465 $buffer .= $1;
7cab3ab7 466 croak "Checksum mismatch in '$filename'"
419a2eeb 467 if Digest::MD5::md5_base64($buffer) ne $2;
7cab3ab7 468
469 $buffer = '';
470 }
471 else {
472 $buffer .= $_;
473 }
996be9ee 474 }
475
5ef3c771 476 croak "Cannot not overwrite '$filename' without 'dump_overwrite',"
419a2eeb 477 . " it does not appear to have been generated by Loader"
5ef3c771 478 if !$found;
479
7cab3ab7 480 return $buffer;
996be9ee 481}
482
483sub _use {
484 my $self = shift;
485 my $target = shift;
cb54990b 486 my $evalstr;
996be9ee 487
488 foreach (@_) {
cb54990b 489 warn "$target: use $_;" if $self->debug;
996be9ee 490 $self->_raw_stmt($target, "use $_;");
cb54990b 491 $_->require or croak ($_ . "->require: $@");
492 $evalstr .= "package $target; use $_;";
996be9ee 493 }
cb54990b 494 eval $evalstr if $evalstr;
495 croak $@ if $@;
996be9ee 496}
497
498sub _inject {
499 my $self = shift;
500 my $target = shift;
501 my $schema_class = $self->schema_class;
502
503 my $blist = join(q{ }, @_);
cb54990b 504 warn "$target: use base qw/ $blist /;" if $self->debug && @_;
996be9ee 505 $self->_raw_stmt($target, "use base qw/ $blist /;") if @_;
996be9ee 506 foreach (@_) {
507 $_->require or croak ($_ . "->require: $@");
508 $schema_class->inject_base($target, $_);
509 }
510}
511
f96ef30f 512# Create class with applicable bases, setup monikers, etc
513sub _make_src_class {
514 my ($self, $table) = @_;
996be9ee 515
a13b2803 516 my $schema = $self->schema;
517 my $schema_class = $self->schema_class;
996be9ee 518
f96ef30f 519 my $table_moniker = $self->_table2moniker($table);
520 my $table_class = $schema_class . q{::} . $table_moniker;
996be9ee 521
f96ef30f 522 my $table_normalized = lc $table;
523 $self->classes->{$table} = $table_class;
524 $self->classes->{$table_normalized} = $table_class;
525 $self->monikers->{$table} = $table_moniker;
526 $self->monikers->{$table_normalized} = $table_moniker;
996be9ee 527
f96ef30f 528 { no strict 'refs'; @{"${table_class}::ISA"} = qw/DBIx::Class/ }
996be9ee 529
f96ef30f 530 $self->_use ($table_class, @{$self->additional_classes});
531 $self->_inject($table_class, @{$self->additional_base_classes});
996be9ee 532
f96ef30f 533 $self->_dbic_stmt($table_class, 'load_components', @{$self->components}, qw/PK::Auto Core/);
996be9ee 534
f96ef30f 535 $self->_dbic_stmt($table_class, 'load_resultset_components', @{$self->resultset_components})
536 if @{$self->resultset_components};
537 $self->_inject($table_class, @{$self->left_base_classes});
538}
996be9ee 539
f96ef30f 540# Set up metadata (cols, pks, etc) and register the class with the schema
541sub _setup_src_meta {
542 my ($self, $table) = @_;
996be9ee 543
f96ef30f 544 my $schema = $self->schema;
545 my $schema_class = $self->schema_class;
a13b2803 546
f96ef30f 547 my $table_class = $self->classes->{$table};
548 my $table_moniker = $self->monikers->{$table};
996be9ee 549
f96ef30f 550 $self->_dbic_stmt($table_class,'table',$table);
996be9ee 551
f96ef30f 552 my $cols = $self->_table_columns($table);
553 my $col_info;
554 eval { $col_info = $self->_columns_info_for($table) };
555 if($@) {
556 $self->_dbic_stmt($table_class,'add_columns',@$cols);
557 }
558 else {
559 my %col_info_lc = map { lc($_), $col_info->{$_} } keys %$col_info;
560 $self->_dbic_stmt(
561 $table_class,
562 'add_columns',
563 map { $_, ($col_info_lc{$_}||{}) } @$cols
564 );
996be9ee 565 }
566
f96ef30f 567 my $pks = $self->_table_pk_info($table) || [];
568 @$pks ? $self->_dbic_stmt($table_class,'set_primary_key',@$pks)
569 : carp("$table has no primary key");
996be9ee 570
f96ef30f 571 my $uniqs = $self->_table_uniq_info($table) || [];
572 $self->_dbic_stmt($table_class,'add_unique_constraint',@$_) for (@$uniqs);
996be9ee 573
f96ef30f 574 $schema_class->register_class($table_moniker, $table_class);
575 $schema->register_class($table_moniker, $table_class) if $schema ne $schema_class;
996be9ee 576}
577
578=head2 tables
579
580Returns a sorted list of loaded tables, using the original database table
581names.
582
583=cut
584
585sub tables {
586 my $self = shift;
587
588 return @{$self->_tables};
589}
590
591# Make a moniker from a table
592sub _table2moniker {
593 my ( $self, $table ) = @_;
594
595 my $moniker;
596
597 if( ref $self->moniker_map eq 'HASH' ) {
598 $moniker = $self->moniker_map->{$table};
599 }
600 elsif( ref $self->moniker_map eq 'CODE' ) {
601 $moniker = $self->moniker_map->($table);
602 }
603
604 $moniker ||= join '', map ucfirst, split /[\W_]+/, lc $table;
605
606 return $moniker;
607}
608
609sub _load_relationships {
610 my $self = shift;
611
612 # Construct the fk_info RelBuilder wants to see, by
613 # translating table names to monikers in the _fk_info output
614 my %fk_info;
615 foreach my $table ($self->tables) {
616 my $tbl_fk_info = $self->_table_fk_info($table);
617 foreach my $fkdef (@$tbl_fk_info) {
618 $fkdef->{remote_source} =
619 $self->monikers->{delete $fkdef->{remote_table}};
620 }
621 my $moniker = $self->monikers->{$table};
622 $fk_info{$moniker} = $tbl_fk_info;
623 }
624
625 my $relbuilder = DBIx::Class::Schema::Loader::RelBuilder->new(
626 $self->schema_class, \%fk_info, $self->inflect_plural,
627 $self->inflect_singular
628 );
629
630 my $rel_stmts = $relbuilder->generate_code;
631 foreach my $src_class (sort keys %$rel_stmts) {
632 my $src_stmts = $rel_stmts->{$src_class};
633 foreach my $stmt (@$src_stmts) {
634 $self->_dbic_stmt($src_class,$stmt->{method},@{$stmt->{args}});
635 }
636 }
637}
638
639# Overload these in driver class:
640
641# Returns an arrayref of column names
642sub _table_columns { croak "ABSTRACT METHOD" }
643
644# Returns arrayref of pk col names
645sub _table_pk_info { croak "ABSTRACT METHOD" }
646
647# Returns an arrayref of uniqs [ [ foo => [ col1, col2 ] ], [ bar => [ ... ] ] ]
648sub _table_uniq_info { croak "ABSTRACT METHOD" }
649
650# Returns an arrayref of foreign key constraints, each
651# being a hashref with 3 keys:
652# local_columns (arrayref), remote_columns (arrayref), remote_table
653sub _table_fk_info { croak "ABSTRACT METHOD" }
654
655# Returns an array of lower case table names
656sub _tables_list { croak "ABSTRACT METHOD" }
657
658# Execute a constructive DBIC class method, with debug/dump_to_dir hooks.
659sub _dbic_stmt {
660 my $self = shift;
661 my $class = shift;
662 my $method = shift;
663
664 if(!$self->debug && !$self->dump_directory) {
665 $class->$method(@_);
666 return;
667 }
668
669 my $args = dump(@_);
670 $args = '(' . $args . ')' if @_ < 2;
671 my $stmt = $method . $args . q{;};
672
673 warn qq|$class\->$stmt\n| if $self->debug;
674 $class->$method(@_);
675 $self->_raw_stmt($class, '__PACKAGE__->' . $stmt);
676}
677
678# Store a raw source line for a class (for dumping purposes)
679sub _raw_stmt {
680 my ($self, $class, $stmt) = @_;
681 push(@{$self->{_dump_storage}->{$class}}, $stmt) if $self->dump_directory;
682}
683
7cab3ab7 684# Like above, but separately for the externally loaded stuff
685sub _ext_stmt {
686 my ($self, $class, $stmt) = @_;
687 push(@{$self->{_ext_storage}->{$class}}, $stmt) if $self->dump_directory;
688}
689
996be9ee 690=head2 monikers
691
8f9d7ce5 692Returns a hashref of loaded table to moniker mappings. There will
996be9ee 693be two entries for each table, the original name and the "normalized"
694name, in the case that the two are different (such as databases
695that like uppercase table names, or preserve your original mixed-case
696definitions, or what-have-you).
697
698=head2 classes
699
8f9d7ce5 700Returns a hashref of table to class mappings. In some cases it will
996be9ee 701contain multiple entries per table for the original and normalized table
702names, as above in L</monikers>.
703
704=head1 SEE ALSO
705
706L<DBIx::Class::Schema::Loader>
707
708=cut
709
7101;