Cold Hard Code

Initializing a database with DBIx::Class and CSV Files.

Here is a recipe I cooked up a while back and have been refining it ever since.  The general idea is to use CSV files to populate your database.  DBIx::Class, with SQL::Translator, can generate your table definitions and populate the data.  It's very easy to use, you just have to call $schema->deploy on a connected schema.

This works great, but I really hated having to have the populate code in various .pm files.  It seems much more natural to use external data files and point to them when you deploy.

The usage is very easy, you just modify your existing deploy call and point it to the CSV files:
$schema->deploy({
    import => {
        'SomeRS' => 'data/somefile.csv',
        'OneMoreRS' => 'data/moredata.csv',
        'YetAnotherRS' => 'data/moar.csv',
    }
});


The code is quite simply, and you just have to place it in your Schema.pm file:

use Text::xSV;

sub deploy {
    my ( $self, $properties ) = @_;

    my $data_import = delete $properties->{import};

    my $key_check_off;
    my $key_check_on;
    if ( $self->storage->isa('DBIx::Class::Storage::DBI::mysql') ) {
        $key_check_off = "SET FOREIGN_KEY_CHECKS = 0";
        $key_check_on  = "SET FOREIGN_KEY_CHECKS = 1";
    }

    my $populate_txn = sub {
        # It is better to use Class::C3's ->next::method here, but
        # since that confuses people who haven't seen it before I
        # opted to use SUPER::
        $self->SUPER::deploy($properties, @_);

        return unless $data_import and ref $data_import eq 'HASH';
        $self->storage->dbh->do($key_check_off) if $key_check_off;

        foreach my $data ( keys %$data_import ) {
            my $rs = $self->resultset($data);
            unless ( $rs ) {
                carp "Unknown result set in import: $data"
            }
            my $csv = Text::xSV->new;
            $csv->open_file($data_import->{$data});
            $csv->read_header;
            foreach my $field ( $csv->get_fields ) {
                if ( lc($field) ne $field ) {
                    $csv->alias($field, lc($field));
                }
            }  

            while ( my $row = $csv->fetchrow_hash ) {
                eval { $rs->create($row); };
                if ( $@ ) {
                    die "Unable to insert row from data: " . join(', ', values %$row) . "\n";
                }
            }
        }
        $self->storage->dbh->do($key_check_on) if $key_check_on;
    };
    $self->txn_do( $populate_txn );
    if ( $@ ) {
        die "Unable to deploy and populate data: $@";
    }
}


(As a note, I opted to not use populate but the slower 'create' because if you have an error in your CSV file it is easier to debug.  If someone has an idea on how to do this better please comment!)
jshirley

Written by Jay Shirley

Jay Shirley combines technical fundamentals with modern, practical savvy. An open source veteran with plenty of notches in his personal and professional belt, the combination of his work and his field vision (soccer metaphor!) has few rivals.

Comments