fix column name case bug
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Base.pm
index a95ba83..84cffc0 100644 (file)
@@ -36,10 +36,10 @@ __PACKAGE__->mk_group_ro_accessors('simple', qw/
                                 additional_base_classes
                                 left_base_classes
                                 components
-                                resultset_components
                                 skip_relationships
                                 skip_load_external
                                 moniker_map
+                                column_accessor_map
                                 custom_column_info
                                 inflect_singular
                                 inflect_plural
@@ -85,6 +85,7 @@ __PACKAGE__->mk_group_accessors('simple', qw/
                                 pod_comment_spillover_length
                                 preserve_case
                                 col_collision_map
+                                real_dump_directory
 /);
 
 =head1 NAME
@@ -305,6 +306,22 @@ together. Examples:
     stations_visited | StationVisited
     routeChange      | RouteChange
 
+=head2 column_accessor_map
+
+Same as moniker_map, but for column accessor names.  If a coderef is
+passed, the code is called with arguments of
+
+   the name of the column in the underlying database,
+   default accessor name that DBICSL would ordinarily give this column,
+   {
+      table_class     => name of the DBIC class we are building,
+      table_moniker   => calculated moniker for this table (after moniker_map if present),
+      table_name      => name of the database table,
+      full_table_name => schema-qualified name of the database table (RDBMS specific),
+      schema_class    => name of the schema class we are building,
+      column_info     => hashref of column info (data_type, is_nullable, etc),
+    }
+
 =head2 inflect_plural
 
 Just like L</moniker_map> above (can be hash/code-ref, falls back to default
@@ -345,13 +362,6 @@ List of additional components to be loaded into all of your table
 classes.  A good example would be
 L<InflateColumn::DateTime|DBIx::Class::InflateColumn::DateTime>
 
-=head2 resultset_components
-
-List of additional ResultSet components to be loaded into your table
-classes.  A good example would be C<AlwaysRS>.  Component
-C<ResultSetManager> will be automatically added to the above
-C<components> list if this option is set.
-
 =head2 use_namespaces
 
 This is now the default, to go back to L<DBIx::Class::Schema/load_classes> pass
@@ -366,13 +376,9 @@ to the call (and the generated result class names adjusted appropriately).
 
 =head2 dump_directory
 
-This option is designed to be a tool to help you transition from this
-loader to a manually-defined schema when you decide it's time to do so.
-
 The value of this option is a perl libdir pathname.  Within
 that directory this module will create a baseline manual
-L<DBIx::Class::Schema> module set, based on what it creates at runtime
-in memory.
+L<DBIx::Class::Schema> module set, based on what it creates at runtime.
 
 The created schema class will have the same classname as the one on
 which you are setting this option (and the ResultSource classes will be
@@ -512,7 +518,7 @@ my $CURRENT_V = 'v7';
 
 my @CLASS_ARGS = qw(
     schema_base_class result_base_class additional_base_classes
-    left_base_classes additional_classes components resultset_components
+    left_base_classes additional_classes components
 );
 
 # ensure that a peice of object data is a valid arrayref, creating
@@ -562,7 +568,6 @@ sub new {
                                additional_base_classes
                                left_base_classes
                                components
-                               resultset_components
                               /);
 
     $self->_validate_class_args;
@@ -574,9 +579,6 @@ sub new {
         }
     }
 
-    push(@{$self->{components}}, 'ResultSetManager')
-        if @{$self->{resultset_components}};
-
     $self->{monikers} = {};
     $self->{classes} = {};
     $self->{_upgrading_classes} = {};
@@ -596,6 +598,8 @@ sub new {
 
     $self->{dump_directory} ||= $self->{temp_directory};
 
+    $self->real_dump_directory($self->{dump_directory});
+
     $self->version_to_dump($DBIx::Class::Schema::Loader::VERSION);
     $self->schema_version_to_dump($DBIx::Class::Schema::Loader::VERSION);
 
@@ -794,8 +798,8 @@ sub _find_file_in_inc {
         my $fullpath = File::Spec->catfile($prefix, $file);
         return $fullpath if -f $fullpath
             # abs_path throws on Windows for nonexistant files
-            and eval { Cwd::abs_path($fullpath) } ne
-               (eval { Cwd::abs_path(File::Spec->catfile($self->dump_directory, $file)) } || '');
+            and (try { Cwd::abs_path($fullpath) }) ne
+               ((try { Cwd::abs_path(File::Spec->catfile($self->dump_directory, $file)) }) || '');
     }
 
     return;
@@ -865,12 +869,7 @@ sub _load_external {
         warn qq/# Loaded external class definition for '$class'\n/
             if $self->debug;
 
-        open(my $fh, '<', $real_inc_path)
-            or croak "Failed to open '$real_inc_path' for reading: $!";
-        my $code = do { local $/; <$fh> };
-        close($fh)
-            or croak "Failed to close $real_inc_path: $!";
-        $code = $self->_rewrite_old_classnames($code);
+        my $code = $self->_rewrite_old_classnames(scalar slurp $real_inc_path);
 
         if ($self->dynamic) { # load the class too
             eval_without_redefine_warnings($code);
@@ -1049,6 +1048,7 @@ sub _load_tables {
         local $self->{dump_directory} = $self->{temp_directory};
         $self->_reload_classes(\@tables);
         $self->_load_relationships($_) for @tables;
+        $self->_relbuilder->cleanup;
         $self->{quiet} = 0;
 
         # Remove that temp dir from INC so it doesn't get reloaded
@@ -1060,7 +1060,7 @@ sub _load_tables {
 
     # Reload without unloading first to preserve any symbols from external
     # packages.
-    $self->_reload_classes(\@tables, 0);
+    $self->_reload_classes(\@tables, { unload => 0 });
 
     # Drop temporary cache
     delete $self->{_cache};
@@ -1069,9 +1069,11 @@ sub _load_tables {
 }
 
 sub _reload_classes {
-    my ($self, $tables, $unload) = @_;
+    my ($self, $tables, $opts) = @_;
 
     my @tables = @$tables;
+
+    my $unload = $opts->{unload};
     $unload = 1 unless defined $unload;
 
     # so that we don't repeat custom sections
@@ -1125,7 +1127,9 @@ sub _reload_classes {
 sub _moose_metaclass {
   return undef unless $INC{'Class/MOP.pm'};   # if CMOP is not loaded the class could not have loaded in the 1st place
 
-  my $mc = Class::MOP::class_of($_[1])
+  my $class = $_[1];
+
+  my $mc = try { Class::MOP::class_of($class) }
     or return undef;
 
   return $mc->isa('Moose::Meta::Class') ? $mc : undef;
@@ -1140,10 +1144,13 @@ sub _reload_class {
     delete $INC{ $class_path };
 
 # kill redefined warnings
-    eval {
+    try {
         eval_without_redefine_warnings ("require $class");
+    }
+    catch {
+        my $source = slurp $self->_get_dump_filename($class);
+        die "Failed to reload class $class: $_.\n\nCLASS SOURCE:\n\n$source";
     };
-    die "Failed to reload class $class: $@" if $@;
 }
 
 sub _get_dump_filename {
@@ -1153,6 +1160,23 @@ sub _get_dump_filename {
     return $self->dump_directory . q{/} . $class . q{.pm};
 }
 
+=head2 get_dump_filename
+
+Arguments: class
+
+Returns the full path to the file for a class that the class has been or will
+be dumped to. This is a file in a temp dir for a dynamic schema.
+
+=cut
+
+sub get_dump_filename {
+    my ($self, $class) = (@_);
+
+    local $self->{dump_directory} = $self->real_dump_directory;
+
+    return $self->_get_dump_filename($class);
+}
+
 sub _ensure_dump_subdirs {
     my ($self, $class) = (@_);
 
@@ -1183,13 +1207,13 @@ sub _dump_to_dir {
     my $schema_text =
           qq|package $schema_class;\n\n|
         . qq|# Created by DBIx::Class::Schema::Loader\n|
-        . qq|# DO NOT MODIFY THE FIRST PART OF THIS FILE\n\n|
-        . qq|use strict;\nuse warnings;\n\n|;
+        . qq|# DO NOT MODIFY THE FIRST PART OF THIS FILE\n\n|;
+
     if ($self->use_moose) {
         $schema_text.= qq|use Moose;\nuse MooseX::NonMoose;\nuse namespace::autoclean;\nextends '$schema_base_class';\n\n|;
     }
     else {
-        $schema_text .= qq|use base '$schema_base_class';\n\n|;
+        $schema_text .= qq|use strict;\nuse warnings;\n\nuse base '$schema_base_class';\n\n|;
     }
 
     if ($self->use_namespaces) {
@@ -1523,8 +1547,6 @@ sub _make_src_class {
         $self->_dbic_stmt($table_class, 'load_components', @components);
     }
 
-    $self->_dbic_stmt($table_class, 'load_resultset_components', @{$self->resultset_components})
-        if @{$self->resultset_components};
     $self->_inject($table_class, @{$self->additional_base_classes});
 }
 
@@ -1543,9 +1565,10 @@ sub _resolve_col_accessor_collisions {
         die $@ if $@;
 
         push @methods, @{ Class::Inspector->methods($class) || [] };
-        push @methods, @{ Class::Inspector->methods('UNIVERSAL') || [] };
     }
 
+    push @methods, @{ Class::Inspector->methods('UNIVERSAL') };
+
     my %methods;
     @methods{@methods} = ();
 
@@ -1580,10 +1603,51 @@ EOF
     }
 }
 
-sub _make_column_accessor_name {
-    my ($self, $column_name) = @_;
+# use the same logic to run moniker_map, column_accessor_map, and
+# relationship_name_map
+sub _run_user_map {
+    my ( $self, $map, $default_code, $ident, @extra ) = @_;
+
+    my $default_ident = $default_code->( $ident, @extra );
+    my $new_ident;
+    if( $map && ref $map eq 'HASH' ) {
+        $new_ident = $map->{ $ident };
+    }
+    elsif( $map && ref $map eq 'CODE' ) {
+        $new_ident = $map->( $ident, $default_ident, @extra );
+    }
+
+    $new_ident ||= $default_ident;
+
+    return $new_ident;
+}
+
+sub _default_column_accessor_name {
+    my ( $self, $column_name ) = @_;
+
+    my $accessor_name = $column_name;
+    $accessor_name =~ s/\W+/_/g;
+
+    if ((($self->naming->{column_accessors}||'') =~ /(\d+)/ && $1 < 7) || (not $self->preserve_case)) {
+        # older naming just lc'd the col accessor and that's all.
+        return lc $accessor_name;
+    }
 
     return join '_', map lc, split_name $column_name;
+
+}
+
+sub _make_column_accessor_name {
+    my ($self, $column_name, $column_context_info ) = @_;
+
+    my $accessor = $self->_run_user_map(
+        $self->column_accessor_map,
+        sub { $self->_default_column_accessor_name( shift ) },
+        $column_name,
+        $column_context_info,
+       );
+
+    return $accessor;
 }
 
 # Set up metadata (cols, pks, etc)
@@ -1610,34 +1674,33 @@ sub _setup_src_meta {
 
     $self->_dbic_stmt($table_class, 'table', $full_table_name);
 
-    my $cols = $self->_table_columns($table);
+    my $cols     = $self->_table_columns($table);
     my $col_info = $self->__columns_info_for($table);
 
+    ### generate all the column accessor names
     while (my ($col, $info) = each %$col_info) {
-        if ($col =~ /\W/) {
-            ($info->{accessor} = $col) =~ s/\W+/_/g;
-        }
-    }
+        # hashref of other info that could be used by
+        # user-defined accessor map functions
+        my $context = {
+            table_class     => $table_class,
+            table_moniker   => $table_moniker,
+            table_name      => $table_name,
+            full_table_name => $full_table_name,
+            schema_class    => $schema_class,
+            column_info     => $info,
+        };
 
-    if ($self->preserve_case) {
-        while (my ($col, $info) = each %$col_info) {
-            if ($col ne lc($col)) {
-                if ((not exists $self->naming->{column_accessors}) || (($self->naming->{column_accessors} =~ /(\d+)/)[0] >= 7)) {
-                    $info->{accessor} = $self->_make_column_accessor_name($info->{accessor} || $col);
-                }
-                else {
-                    $info->{accessor} = lc($info->{accessor} || $col);
-                }
-            }
-        }
-    }
-    else {
-        # XXX this needs to go away
-        $col_info = { map { lc($_), $col_info->{$_} } keys %$col_info };
+        $info->{accessor} = $self->_make_column_accessor_name( $col, $context );
     }
 
     $self->_resolve_col_accessor_collisions($full_table_name, $col_info);
 
+    # prune any redundant accessor names
+    while (my ($col, $info) = each %$col_info) {
+        no warnings 'uninitialized';
+        delete $info->{accessor} if $info->{accessor} eq $col;
+    }
+
     my $fks = $self->_table_fk_info($table);
 
     foreach my $fkdef (@$fks) {
@@ -1731,18 +1794,11 @@ sub _default_table2moniker {
 sub _table2moniker {
     my ( $self, $table ) = @_;
 
-    my $moniker;
-
-    if( ref $self->moniker_map eq 'HASH' ) {
-        $moniker = $self->moniker_map->{$table};
-    }
-    elsif( ref $self->moniker_map eq 'CODE' ) {
-        $moniker = $self->moniker_map->($table);
-    }
-
-    $moniker ||= $self->_default_table2moniker($table);
-
-    return $moniker;
+    $self->_run_user_map(
+        $self->moniker_map,
+        sub { $self->_default_table2moniker( shift ) },
+        $table
+       );
 }
 
 sub _load_relationships {
@@ -1966,7 +2022,7 @@ sub _uc {
 sub _unregister_source_for_table {
     my ($self, $table) = @_;
 
-    eval {
+    try {
         local $@;
         my $schema = $self->schema;
         # in older DBIC it's a private method