convert to M::I, release 0.03999_02
[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_02';
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->_ensure_arrayref(qw/additional_classes
213                                additional_base_classes
214                                left_base_classes
215                                components
216                                resultset_components
217                               /);
218
219     push(@{$self->{components}}, 'ResultSetManager')
220         if @{$self->{resultset_components}};
221
222     $self->{monikers} = {};
223     $self->{classes} = {};
224
225     $self->{schema_class} ||= ( ref $self->{schema} || $self->{schema} );
226     $self->{schema} ||= $self->{schema_class};
227
228     $self->{relbuilder} = DBIx::Class::Schema::Loader::RelBuilder->new(
229         $self->schema_class, $self->inflect_plural, $self->inflect_singular
230     ) if !$self->{skip_relationships};
231
232     $self;
233 }
234
235 sub _find_file_in_inc {
236     my ($self, $file) = @_;
237
238     foreach my $prefix (@INC) {
239         my $fullpath = $prefix . '/' . $file;
240         return $fullpath if -f $fullpath;
241     }
242
243     return;
244 }
245
246 sub _load_external {
247     my ($self, $class) = @_;
248
249     my $class_path = $class;
250     $class_path =~ s{::}{/}g;
251     $class_path .= '.pm';
252
253     my $inc_path = $self->_find_file_in_inc($class_path);
254
255     return if !$inc_path;
256
257     my $real_dump_path = $self->dump_directory
258         ? Cwd::abs_path(
259               File::Spec->catfile($self->dump_directory, $class_path)
260           )
261         : '';
262     my $real_inc_path = Cwd::abs_path($inc_path);
263     return if $real_inc_path eq $real_dump_path;
264
265     $class->require;
266     croak "Failed to load external class definition"
267         . " for '$class': $@"
268             if $@;
269
270     # If we make it to here, we loaded an external definition
271     warn qq/# Loaded external class definition for '$class'\n/
272         if $self->debug;
273
274     # The rest is only relevant when dumping
275     return if !$self->dump_directory;
276
277     croak 'Failed to locate actual external module file for '
278           . "'$class'"
279               if !$real_inc_path;
280     open(my $fh, '<', $real_inc_path)
281         or croak "Failed to open '$real_inc_path' for reading: $!";
282     $self->_ext_stmt($class,
283         qq|# These lines were loaded from '$real_inc_path' found in \@INC.|
284         .q|# They are now part of the custom portion of this file|
285         .q|# for you to hand-edit.  If you do not either delete|
286         .q|# this section or remove that file from @INC, this section|
287         .q|# will be repeated redundantly when you re-create this|
288         .q|# file again via Loader!|
289     );
290     while(<$fh>) {
291         chomp;
292         $self->_ext_stmt($class, $_);
293     }
294     $self->_ext_stmt($class,
295         q|# End of lines loaded from '$real_inc_path' |
296     );
297     close($fh)
298         or croak "Failed to close $real_inc_path: $!";
299 }
300
301 =head2 load
302
303 Does the actual schema-construction work.
304
305 =cut
306
307 sub load {
308     my $self = shift;
309
310     $self->_load_tables($self->_tables_list);
311 }
312
313 =head2 rescan
314
315 Arguments: schema
316
317 Rescan the database for newly added tables.  Does
318 not process drops or changes.  Returns a list of
319 the newly added table monikers.
320
321 The schema argument should be the schema class
322 or object to be affected.  It should probably
323 be derived from the original schema_class used
324 during L</load>.
325
326 =cut
327
328 sub rescan {
329     my ($self, $schema) = @_;
330
331     $self->{schema} = $schema;
332
333     my @created;
334     my @current = $self->_tables_list;
335     foreach my $table ($self->_tables_list) {
336         if(!exists $self->{_tables}->{$table}) {
337             push(@created, $table);
338         }
339     }
340
341     $self->_load_tables(@created);
342
343     return map { $self->monikers->{$_} } @created;
344 }
345
346 sub _load_tables {
347     my ($self, @tables) = @_;
348
349     # First, use _tables_list with constraint and exclude
350     #  to get a list of tables to operate on
351
352     my $constraint   = $self->constraint;
353     my $exclude      = $self->exclude;
354
355     @tables = grep { /$constraint/ } @tables if $constraint;
356     @tables = grep { ! /$exclude/ } @tables if $exclude;
357
358     # Save the new tables to the tables list
359     foreach (@tables) {
360         $self->{_tables}->{$_} = 1;
361     }
362
363     # Set up classes/monikers
364     {
365         no warnings 'redefine';
366         local *Class::C3::reinitialize = sub { };
367         use warnings;
368
369         $self->_make_src_class($_) for @tables;
370     }
371
372     Class::C3::reinitialize;
373
374     $self->_setup_src_meta($_) for @tables;
375
376     if(!$self->skip_relationships) {
377         $self->_load_relationships($_) for @tables;
378     }
379
380     $self->_load_external($_)
381         for map { $self->classes->{$_} } @tables;
382
383     $self->_dump_to_dir if $self->dump_directory;
384
385     # Drop temporary cache
386     delete $self->{_cache};
387
388     1;
389 }
390
391 sub _get_dump_filename {
392     my ($self, $class) = (@_);
393
394     $class =~ s{::}{/}g;
395     return $self->dump_directory . q{/} . $class . q{.pm};
396 }
397
398 sub _ensure_dump_subdirs {
399     my ($self, $class) = (@_);
400
401     my @name_parts = split(/::/, $class);
402     pop @name_parts; # we don't care about the very last element,
403                      # which is a filename
404
405     my $dir = $self->dump_directory;
406     while (1) {
407         if(!-d $dir) {
408             mkdir($dir) or croak "mkdir('$dir') failed: $!";
409         }
410         last if !@name_parts;
411         $dir = File::Spec->catdir($dir, shift @name_parts);
412     }
413 }
414
415 sub _dump_to_dir {
416     my ($self) = @_;
417
418     my $target_dir = $self->dump_directory;
419
420     my $schema_class = $self->schema_class;
421
422     croak "Must specify target directory for dumping!" if ! $target_dir;
423
424     warn "Dumping manual schema for $schema_class to directory $target_dir ...\n";
425
426     my $schema_text =
427           qq|package $schema_class;\n\n|
428         . qq|use strict;\nuse warnings;\n\n|
429         . qq|use base 'DBIx::Class::Schema';\n\n|
430         . qq|__PACKAGE__->load_classes;\n|;
431
432     $self->_write_classfile($schema_class, $schema_text);
433
434     foreach my $src_class (sort keys %{$self->{_dump_storage}}) {
435         my $src_text = 
436               qq|package $src_class;\n\n|
437             . qq|use strict;\nuse warnings;\n\n|
438             . qq|use base 'DBIx::Class';\n\n|;
439
440         $self->_write_classfile($src_class, $src_text);
441     }
442
443     warn "Schema dump completed.\n";
444 }
445
446 sub _write_classfile {
447     my ($self, $class, $text) = @_;
448
449     my $filename = $self->_get_dump_filename($class);
450     $self->_ensure_dump_subdirs($class);
451
452     if (-f $filename && $self->dump_overwrite) {
453         warn "Deleting existing file '$filename' due to "
454             . "'dump_overwrite' setting\n";
455         unlink($filename);
456     }    
457
458     my $custom_content = $self->_get_custom_content($class, $filename);
459
460     $custom_content ||= qq|\n# You can replace this text with custom|
461         . qq| content, and it will be preserved on regeneration|
462         . qq|\n1;\n|;
463
464     $text .= qq|$_\n|
465         for @{$self->{_dump_storage}->{$class} || []};
466
467     $text .= qq|\n\n# Created by DBIx::Class::Schema::Loader|
468         . qq| v| . $DBIx::Class::Schema::Loader::VERSION
469         . q| @ | . POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime)
470         . qq|\n# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:|;
471
472     open(my $fh, '>', $filename)
473         or croak "Cannot open '$filename' for writing: $!";
474
475     # Write the top half and its MD5 sum
476     print $fh $text . Digest::MD5::md5_base64($text) . "\n\n";
477
478     # Write out anything loaded via external partial class file in @INC
479     print $fh qq|$_\n|
480         for @{$self->{_ext_storage}->{$class} || []};
481
482     print $fh $custom_content;
483
484     close($fh)
485         or croak "Cannot close '$filename': $!";
486 }
487
488 sub _get_custom_content {
489     my ($self, $class, $filename) = @_;
490
491     return if ! -f $filename;
492     open(my $fh, '<', $filename)
493         or croak "Cannot open '$filename' for reading: $!";
494
495     my $mark_re = 
496         qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
497
498     my $found = 0;
499     my $buffer = '';
500     while(<$fh>) {
501         if(!$found && /$mark_re/) {
502             $found = 1;
503             $buffer .= $1;
504             croak "Checksum mismatch in '$filename'"
505                 if Digest::MD5::md5_base64($buffer) ne $2;
506
507             $buffer = '';
508         }
509         else {
510             $buffer .= $_;
511         }
512     }
513
514     croak "Cannot not overwrite '$filename' without 'dump_overwrite',"
515         . " it does not appear to have been generated by Loader"
516             if !$found;
517
518     return $buffer;
519 }
520
521 sub _use {
522     my $self = shift;
523     my $target = shift;
524     my $evalstr;
525
526     foreach (@_) {
527         warn "$target: use $_;" if $self->debug;
528         $self->_raw_stmt($target, "use $_;");
529         $_->require or croak ($_ . "->require: $@");
530         $evalstr .= "package $target; use $_;";
531     }
532     eval $evalstr if $evalstr;
533     croak $@ if $@;
534 }
535
536 sub _inject {
537     my $self = shift;
538     my $target = shift;
539     my $schema_class = $self->schema_class;
540
541     my $blist = join(q{ }, @_);
542     warn "$target: use base qw/ $blist /;" if $self->debug && @_;
543     $self->_raw_stmt($target, "use base qw/ $blist /;") if @_;
544     foreach (@_) {
545         $_->require or croak ($_ . "->require: $@");
546         $schema_class->inject_base($target, $_);
547     }
548 }
549
550 # Create class with applicable bases, setup monikers, etc
551 sub _make_src_class {
552     my ($self, $table) = @_;
553
554     my $schema       = $self->schema;
555     my $schema_class = $self->schema_class;
556
557     my $table_moniker = $self->_table2moniker($table);
558     my $table_class = $schema_class . q{::} . $table_moniker;
559
560     my $table_normalized = lc $table;
561     $self->classes->{$table} = $table_class;
562     $self->classes->{$table_normalized} = $table_class;
563     $self->monikers->{$table} = $table_moniker;
564     $self->monikers->{$table_normalized} = $table_moniker;
565
566     { no strict 'refs'; @{"${table_class}::ISA"} = qw/DBIx::Class/ }
567
568     $self->_use   ($table_class, @{$self->additional_classes});
569     $self->_inject($table_class, @{$self->additional_base_classes});
570
571     $self->_dbic_stmt($table_class, 'load_components', @{$self->components}, 'Core');
572
573     $self->_dbic_stmt($table_class, 'load_resultset_components', @{$self->resultset_components})
574         if @{$self->resultset_components};
575     $self->_inject($table_class, @{$self->left_base_classes});
576 }
577
578 # Set up metadata (cols, pks, etc) and register the class with the schema
579 sub _setup_src_meta {
580     my ($self, $table) = @_;
581
582     my $schema       = $self->schema;
583     my $schema_class = $self->schema_class;
584
585     my $table_class = $self->classes->{$table};
586     my $table_moniker = $self->monikers->{$table};
587
588     $self->_dbic_stmt($table_class,'table',$table);
589
590     my $cols = $self->_table_columns($table);
591     my $col_info;
592     eval { $col_info = $self->_columns_info_for($table) };
593     if($@) {
594         $self->_dbic_stmt($table_class,'add_columns',@$cols);
595     }
596     else {
597         my %col_info_lc = map { lc($_), $col_info->{$_} } keys %$col_info;
598         $self->_dbic_stmt(
599             $table_class,
600             'add_columns',
601             map { $_, ($col_info_lc{$_}||{}) } @$cols
602         );
603     }
604
605     my $pks = $self->_table_pk_info($table) || [];
606     @$pks ? $self->_dbic_stmt($table_class,'set_primary_key',@$pks)
607           : carp("$table has no primary key");
608
609     my $uniqs = $self->_table_uniq_info($table) || [];
610     $self->_dbic_stmt($table_class,'add_unique_constraint',@$_) for (@$uniqs);
611
612     $schema_class->register_class($table_moniker, $table_class);
613     $schema->register_class($table_moniker, $table_class) if $schema ne $schema_class;
614 }
615
616 =head2 tables
617
618 Returns a sorted list of loaded tables, using the original database table
619 names.
620
621 =cut
622
623 sub tables {
624     my $self = shift;
625
626     return keys %{$self->_tables};
627 }
628
629 # Make a moniker from a table
630 sub _table2moniker {
631     my ( $self, $table ) = @_;
632
633     my $moniker;
634
635     if( ref $self->moniker_map eq 'HASH' ) {
636         $moniker = $self->moniker_map->{$table};
637     }
638     elsif( ref $self->moniker_map eq 'CODE' ) {
639         $moniker = $self->moniker_map->($table);
640     }
641
642     $moniker ||= join '', map ucfirst, split /[\W_]+/, lc $table;
643
644     return $moniker;
645 }
646
647 sub _load_relationships {
648     my ($self, $table) = @_;
649
650     my $tbl_fk_info = $self->_table_fk_info($table);
651     foreach my $fkdef (@$tbl_fk_info) {
652         $fkdef->{remote_source} =
653             $self->monikers->{delete $fkdef->{remote_table}};
654     }
655
656     my $local_moniker = $self->monikers->{$table};
657     my $rel_stmts = $self->{relbuilder}->generate_code($local_moniker, $tbl_fk_info);
658
659     foreach my $src_class (sort keys %$rel_stmts) {
660         my $src_stmts = $rel_stmts->{$src_class};
661         foreach my $stmt (@$src_stmts) {
662             $self->_dbic_stmt($src_class,$stmt->{method},@{$stmt->{args}});
663         }
664     }
665 }
666
667 # Overload these in driver class:
668
669 # Returns an arrayref of column names
670 sub _table_columns { croak "ABSTRACT METHOD" }
671
672 # Returns arrayref of pk col names
673 sub _table_pk_info { croak "ABSTRACT METHOD" }
674
675 # Returns an arrayref of uniqs [ [ foo => [ col1, col2 ] ], [ bar => [ ... ] ] ]
676 sub _table_uniq_info { croak "ABSTRACT METHOD" }
677
678 # Returns an arrayref of foreign key constraints, each
679 #   being a hashref with 3 keys:
680 #   local_columns (arrayref), remote_columns (arrayref), remote_table
681 sub _table_fk_info { croak "ABSTRACT METHOD" }
682
683 # Returns an array of lower case table names
684 sub _tables_list { croak "ABSTRACT METHOD" }
685
686 # Execute a constructive DBIC class method, with debug/dump_to_dir hooks.
687 sub _dbic_stmt {
688     my $self = shift;
689     my $class = shift;
690     my $method = shift;
691
692     if(!$self->debug && !$self->dump_directory) {
693         $class->$method(@_);
694         return;
695     }
696
697     my $args = dump(@_);
698     $args = '(' . $args . ')' if @_ < 2;
699     my $stmt = $method . $args . q{;};
700
701     warn qq|$class\->$stmt\n| if $self->debug;
702     $class->$method(@_);
703     $self->_raw_stmt($class, '__PACKAGE__->' . $stmt);
704 }
705
706 # Store a raw source line for a class (for dumping purposes)
707 sub _raw_stmt {
708     my ($self, $class, $stmt) = @_;
709     push(@{$self->{_dump_storage}->{$class}}, $stmt) if $self->dump_directory;
710 }
711
712 # Like above, but separately for the externally loaded stuff
713 sub _ext_stmt {
714     my ($self, $class, $stmt) = @_;
715     push(@{$self->{_ext_storage}->{$class}}, $stmt) if $self->dump_directory;
716 }
717
718 =head2 monikers
719
720 Returns a hashref of loaded table to moniker mappings.  There will
721 be two entries for each table, the original name and the "normalized"
722 name, in the case that the two are different (such as databases
723 that like uppercase table names, or preserve your original mixed-case
724 definitions, or what-have-you).
725
726 =head2 classes
727
728 Returns a hashref of table to class mappings.  In some cases it will
729 contain multiple entries per table for the original and normalized table
730 names, as above in L</monikers>.
731
732 =head1 SEE ALSO
733
734 L<DBIx::Class::Schema::Loader>
735
736 =cut
737
738 1;