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