6de7d2f2a93566eef195d7c302935b636f0ca371
[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;
230 }
231
232 sub _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
243 sub _load_external {
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, $_);
290     }
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: $!";
296 }
297
298 =head2 load
299
300 Does the actual schema-construction work.
301
302 =cut
303
304 sub load {
305     my $self = shift;
306
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
341     $self->_load_relationships if ! $self->skip_relationships;
342     $self->_load_external($_)
343         for ($self->schema_class, values %{$self->classes});
344
345     $self->_dump_to_dir if $self->dump_directory;
346
347     # Drop temporary cache
348     delete $self->{_cache};
349
350     1;
351 }
352
353 sub _get_dump_filename {
354     my ($self, $class) = (@_);
355
356     $class =~ s{::}{/}g;
357     return $self->dump_directory . q{/} . $class . q{.pm};
358 }
359
360 sub _ensure_dump_subdirs {
361     my ($self, $class) = (@_);
362
363     my @name_parts = split(/::/, $class);
364     pop @name_parts; # we don't care about the very last element,
365                      # which is a filename
366
367     my $dir = $self->dump_directory;
368     while (1) {
369         if(!-d $dir) {
370             mkdir($dir) or croak "mkdir('$dir') failed: $!";
371         }
372         last if !@name_parts;
373         $dir = File::Spec->catdir($dir, shift @name_parts);
374     }
375 }
376
377 sub _dump_to_dir {
378     my ($self) = @_;
379
380     my $target_dir = $self->dump_directory;
381
382     my $schema_class = $self->schema_class;
383
384     croak "Must specify target directory for dumping!" if ! $target_dir;
385
386     warn "Dumping manual schema for $schema_class to directory $target_dir ...\n";
387
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|;
393
394     $self->_write_classfile($schema_class, $schema_text);
395
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|;
401
402         $self->_write_classfile($src_class, $src_text);
403     }
404
405     warn "Schema dump completed.\n";
406 }
407
408 sub _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
420     my $custom_content = $self->_get_custom_content($class, $filename);
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
450 sub _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 = 
458         qr{^(# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:)([A-Za-z0-9/+]{22})\n};
459
460     my $found = 0;
461     my $buffer = '';
462     while(<$fh>) {
463         if(!$found && /$mark_re/) {
464             $found = 1;
465             $buffer .= $1;
466             croak "Checksum mismatch in '$filename'"
467                 if Digest::MD5::md5_base64($buffer) ne $2;
468
469             $buffer = '';
470         }
471         else {
472             $buffer .= $_;
473         }
474     }
475
476     croak "Cannot not overwrite '$filename' without 'dump_overwrite',"
477         . " it does not appear to have been generated by Loader"
478             if !$found;
479
480     return $buffer;
481 }
482
483 sub _use {
484     my $self = shift;
485     my $target = shift;
486     my $evalstr;
487
488     foreach (@_) {
489         warn "$target: use $_;" if $self->debug;
490         $self->_raw_stmt($target, "use $_;");
491         $_->require or croak ($_ . "->require: $@");
492         $evalstr .= "package $target; use $_;";
493     }
494     eval $evalstr if $evalstr;
495     croak $@ if $@;
496 }
497
498 sub _inject {
499     my $self = shift;
500     my $target = shift;
501     my $schema_class = $self->schema_class;
502
503     my $blist = join(q{ }, @_);
504     warn "$target: use base qw/ $blist /;" if $self->debug && @_;
505     $self->_raw_stmt($target, "use base qw/ $blist /;") if @_;
506     foreach (@_) {
507         $_->require or croak ($_ . "->require: $@");
508         $schema_class->inject_base($target, $_);
509     }
510 }
511
512 # Create class with applicable bases, setup monikers, etc
513 sub _make_src_class {
514     my ($self, $table) = @_;
515
516     my $schema       = $self->schema;
517     my $schema_class = $self->schema_class;
518
519     my $table_moniker = $self->_table2moniker($table);
520     my $table_class = $schema_class . q{::} . $table_moniker;
521
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;
527
528     { no strict 'refs'; @{"${table_class}::ISA"} = qw/DBIx::Class/ }
529
530     $self->_use   ($table_class, @{$self->additional_classes});
531     $self->_inject($table_class, @{$self->additional_base_classes});
532
533     $self->_dbic_stmt($table_class, 'load_components', @{$self->components}, qw/PK::Auto Core/);
534
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 }
539
540 # Set up metadata (cols, pks, etc) and register the class with the schema
541 sub _setup_src_meta {
542     my ($self, $table) = @_;
543
544     my $schema       = $self->schema;
545     my $schema_class = $self->schema_class;
546
547     my $table_class = $self->classes->{$table};
548     my $table_moniker = $self->monikers->{$table};
549
550     $self->_dbic_stmt($table_class,'table',$table);
551
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         );
565     }
566
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");
570
571     my $uniqs = $self->_table_uniq_info($table) || [];
572     $self->_dbic_stmt($table_class,'add_unique_constraint',@$_) for (@$uniqs);
573
574     $schema_class->register_class($table_moniker, $table_class);
575     $schema->register_class($table_moniker, $table_class) if $schema ne $schema_class;
576 }
577
578 =head2 tables
579
580 Returns a sorted list of loaded tables, using the original database table
581 names.
582
583 =cut
584
585 sub tables {
586     my $self = shift;
587
588     return @{$self->_tables};
589 }
590
591 # Make a moniker from a table
592 sub _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
609 sub _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
642 sub _table_columns { croak "ABSTRACT METHOD" }
643
644 # Returns arrayref of pk col names
645 sub _table_pk_info { croak "ABSTRACT METHOD" }
646
647 # Returns an arrayref of uniqs [ [ foo => [ col1, col2 ] ], [ bar => [ ... ] ] ]
648 sub _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
653 sub _table_fk_info { croak "ABSTRACT METHOD" }
654
655 # Returns an array of lower case table names
656 sub _tables_list { croak "ABSTRACT METHOD" }
657
658 # Execute a constructive DBIC class method, with debug/dump_to_dir hooks.
659 sub _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)
679 sub _raw_stmt {
680     my ($self, $class, $stmt) = @_;
681     push(@{$self->{_dump_storage}->{$class}}, $stmt) if $self->dump_directory;
682 }
683
684 # Like above, but separately for the externally loaded stuff
685 sub _ext_stmt {
686     my ($self, $class, $stmt) = @_;
687     push(@{$self->{_ext_storage}->{$class}}, $stmt) if $self->dump_directory;
688 }
689
690 =head2 monikers
691
692 Returns a hashref of loaded table to moniker mappings.  There will
693 be two entries for each table, the original name and the "normalized"
694 name, in the case that the two are different (such as databases
695 that like uppercase table names, or preserve your original mixed-case
696 definitions, or what-have-you).
697
698 =head2 classes
699
700 Returns a hashref of table to class mappings.  In some cases it will
701 contain multiple entries per table for the original and normalized table
702 names, as above in L</monikers>.
703
704 =head1 SEE ALSO
705
706 L<DBIx::Class::Schema::Loader>
707
708 =cut
709
710 1;