The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package DBIx::Class::Migration::RunScript;

use Moose;
use Moose::Exporter;
use Text::Brew qw(distance);

Moose::Exporter->setup_import_methods(
  as_is => ['builder', 'migrate']);

with 'MooseX::Traits::Pluggable';

has '+_trait_namespace' => (default=>'+Trait');
has 'dbh' => (is=>'rw', isa=>'Object');
has 'version_set' => (is=>'rw', isa=>'ArrayRef');
has 'runs' => (
  is=>'ro',
  isa=>'CodeRef',
  required=>1);

sub run {
  my ($self) = @_;
  eval { $self->runs->(@_); 1 } ||
    $self->handle_errors($@);
}

sub handle_errors {
  my ($self, $err) = @_;
  if($err =~m/Can't find source for (.+?) at/) {
    my @presentsources = map {
      (distance($_, $1))[0] < 3 ? "$_ <== Possible Match\n" : "$_\n";
    } $self->schema->sources;

    die <<"ERR";
$err
You are probably seeing this error because the DBIC source in your migration
script called "$1" doesn't match a source defined in the schema that
::SchemaLoader has inferred from your existing database.  You may be confused
since that source might exist in your hand coded Schema files.  Since your
migration script doesn't use your hand coded Schema (it can't since we cannot
be sure it is in sync with your database state) but instead uses SchemaLoader
to autogenerate a schema, it uses the default SchemaLoader rules for creating
source names from the database tables it finds.

To help you debug this issue, here's a list of the actual sources that the
schema available to your migration knows about:

 @presentsources
ERR
  } elsif($err =~m/No such column (.+?) on DBIx.+?Result::(.+?) at/) {
    my @presentsources = map {
      (distance($_, $1))[0] < 3 ? "$_ <== Possible Match\n" : "$_\n";
    } ($self->schema->resultset($2)->result_source->columns,
        $self->schema->resultset($2)->result_source->relationships);

    die <<"ERR";
$err
You are probably seeing this error because the DBIC resultset $2 does
not have a column or method called $1 defined in the schema which
::SchemaLoader has inferred from your existing database.  You may be confused
since that method might exist in your hand coded Schema files.  Since your
migration script doesn't use your hand coded Schema (it can't since we cannot
be sure it is in sync with your database state) but instead uses SchemaLoader
to autogenerate a schema, it uses the default SchemaLoader rules for creating
column and method names.

To help you debug this issue, here's a list of the actual columns and methods
that $2 has available:

 @presentsources
ERR
  } else {
    die $err;
  }
}

sub as_coderef {
  my $self = shift;
  return sub {
    my ($schema, $version_set) = @_;
    $self->dbh($schema->storage->dbh);
    $self->version_set($version_set);
    $self->run;
  }
}

sub default_plugins {
  'SchemaLoader',
  'Populate',
  'Dump',
  'TargetPath',
}

sub used_plugins {
  my $path = 'DBIx/Class/Migration/RunScript/Trait/';
  my $match = "$path(.+).pm";
  return map { m[$match]x }
    grep { m[$path]x } keys %INC;
}

sub builder(&) {
  my ($runs, @plugins) = reverse shift->();
  my (@traits, %args);
  foreach my $plugins (@plugins) {
    if(ref $plugins) {
      %args = (%args, %$plugins);
    } else {
      push @traits, $plugins;
    }
  }

  return __PACKAGE__
    ->new_with_traits(traits=>\@traits, runs=>$runs, %args)
    ->as_coderef;
}

sub migrate(&) {
  my $runs = shift;
  builder {
    default_plugins(),
    used_plugins(),
    $runs,
  };
}

__PACKAGE__->meta->make_immutable;

=head1 NAME

DBIx::Class::Migration::RunScript - Control your Perl Migration Run Scripts

=head1 SYNOPSIS

Using the C<builder> exported subroutine:

    use DBIx::Class::Migration::RunScript;

    builder {
      'SchemaLoader',
      sub {
        shift->schema->resultset('Country')
          ->populate([
          ['code'],
          ['bel'],
          ['deu'],
          ['fra'],
        ]);
      };
    };

Alternatively, use the C<migrate> exported subroutine for standard and external
plugins:

    use DBIx::Class::Migration::RunScript;
    use DBIx::Class::Migration::RunScript::Trait::AuthenPassphrase;

    migrate {
      shift->schema
        ->resultset('Country')
        ->populate([
          ['code'],
          ['bel'],
          ['deu'],
          ['fra'],
        ]);
    };

=head1 DESCRIPTION

When using Perl based run files for your migrations, this class lets you
manage that and offers a clean method to add in functionality.

See L<DBIx::Class::Migration::Tutorial> for an extended discussion.

=head1 ATTRIBUTES

This class defines the follow attributes

=head2 version_set

An arrayref of the from / to version you are attempting to migrate.

=head2 dbh

The current database handle to the database you are trying to migrate.

=head1 EXPORTS

This class defines the following exports

=head2 builder

Allows you to construct a migration script from a subroutine and also lets you
specify plugins.

=head2 migrate

Run a migration subref with default plugins (SchemaLoader, Populate, TargetDir
Dump) and any additional plugins that you've used.  For example:

    use DBIx::Class::Migration::RunScript;

    migrate {
      my $runscript = shift;
    }

In this case C<$runscript> is an instance of L<DBIx::Class::Migration::RunScript>
and has the default traits applied (see
L<DBIx::Class::Migration::RunScript::Trait::TargetPath>,
L<DBIx::Class::Migration::RunScript::Trait::Schema>,
L<DBIx::Class::Migration::RunScript::Trait::Populate>,
L<DBIx::Class::Migration::RunScript::Trait::Dump> for more).

Second example:

    use DBIx::Class::Migration::RunScript;
    use DBIx::Class::Migration::RunScript::Trait::AuthenPassphrase;

    migrate {
      my $runscript = shift;
    }

In this case C<$runscript> is an instance of L<DBIx::Class::Migration::RunScript>
with traits applied as above and in addition one more trait,
L<DBIx::Class::Migration::RunScript::Trait::AuthenPassphrase> which is available
on CPAN (is external because it carries a dependency weight I don't want to 
impose on people if they don't need it).

=head1 UTILITY SUBROUTINES

The follow subroutines are available as package method, and not exported

=head2 default_plugins

returns an array of the default plugins.

=head1 SEE ALSO

L<DBIx::Class::Migration>.

=head1 AUTHOR

See L<DBIx::Class::Migration> for author information

=head1 COPYRIGHT & LICENSE

See L<DBIx::Class::Migration> for copyright and license information

=cut