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