#!/usr/bin/env perl
#
# File bootstrapped by RapidApp [% ra_ver %]
#
use strict;
use warnings;
use RapidApp::Util ':all';
use Getopt::Long;
use Pod::Usage;
use DBIx::Class::Schema::Loader;
use Module::Runtime;
use IPC::Cmd qw[can_run run_forked];
use Path::Class qw(file dir);
require Module::Locate;
use RapidApp::Util::RapidDbic::CfgWriter;
my($from_ddl,$schema,$cfg,$all,$Go);
GetOptions(
[%- IF from_ddl -%] 'from-ddl+' => \$from_ddl, [%- END -%]
'schema+' => \$schema,
'cfg+' => \$cfg,
'all+' => \$all,
'go+' => \$Go
);
if($all) {
$cfg = 1;
$schema = 1;
[%- IF from_ddl -%] $from_ddl = 1; [%- END -%]
}
$schema = 1 if ($from_ddl);
pod2usage(1) unless ($schema || $cfg);
use FindBin;
use lib "$FindBin::Bin/../lib";
my $app_class = '[%- name -%]';
my $model_class = '[%- model_class -%]';
my $approot = "$FindBin::Bin/..";
my $applib = "$approot/lib";
# make an $INC{ $key } style string from the class name
(my $pm = "$app_class.pm") =~ s{::}{/}g;
my $appfile = file($applib,$pm)->absolute->resolve;
# This is purely for Catalyst::Utils::home() which will be invoked when
# we require the model class in the next statement so it can find the
# home directory w/o having to actually use/load the app class:
$INC{ $pm } = "$appfile";
Module::Runtime::require_module($model_class);
my ($schema_class,$dsn,$user,$pass) = (
$model_class->config->{schema_class},
$model_class->config->{connect_info}{dsn},
$model_class->config->{connect_info}{user},
$model_class->config->{connect_info}{password}
);
[% IF from_ddl %]
if($from_ddl) { # from_ddl logic
my $sqlite3 = can_run('sqlite3') or die 'FATAL: sqlite3 command not available!';
my $ddl = file($approot,'[%- from_ddl -%]')->resolve;
my ($dbi,$driver,$db_path) = split(/\:/,$dsn,3);
die "malformed dsn '$dsn' - expected it to start with 'dbi:'" unless ($dbi eq 'dbi');
die join('',
"cannot use dbi driver '$driver' when generating from DDL",
" - only 'SQLite' is supported"
) unless ($driver eq 'SQLite');
# Here we are parsing the sqlite db path in one of two ways, both specific to
# the way the RapidApp bootstrap process generated the model class. The first
# way is to look for the special method _sqlt_db_path which is generated by
# ForRapidDbic during bootstrap, and the second is to attempt to parse directly
# from the dsn, which relies on the 3rd argument being the path (which isn't the
# only possible config, which is why _sqlt_db_path was added, in case the user
# needs to change the dsn, add an option, etc)
my $sqlt = file( $model_class->can('_sqlt_db_path')
? $model_class->_sqlt_db_path()
: $db_path
);
print join("\n",'',
"Regenerating SQLite database from DDL - EXISTING DATA WILL BE DESTROYED!",'',
" Source DDL : $ddl",
" Target DB : $sqlt",''
);
if($Go) {
print "\nCalled with --go - proceeding without confirmation...\n";
}
else {
print join("\n",'',
"Type in 'go' and hit ENTER to proceed, anything else to abort",
"(tip: call with --go to skip this check)",
' [type "go" to proceed]> '
);
my $in = <STDIN>;
chomp($in);
unless ($in && lc($in) eq 'go') {
print "Didn't get 'go' -- exiting.\n";
exit;
}
}
my $ddl_text = $ddl->slurp;
my $i = 0;
my $sqlt_tmp = file($sqlt->parent, join('','.',$sqlt->basename,'.tmp'));
while(-e $sqlt_tmp) {
$sqlt_tmp = file($sqlt->parent, join('','.',$sqlt->basename,'.tmp.',++$i));
}
print "\n-->> calling system command: sqlite3 " . $sqlt_tmp->relative . ' < ' . $ddl->relative . ' ';
my $result = run_forked([$sqlite3,$sqlt_tmp], { child_stdin => $ddl_text });
my $exit = $result->{exit_code};
print " [exit: $exit]\n";
if ($exit) {
print " -- non-zero exit, removing temp file " . $sqlt_tmp->relative . " -- \n";
$sqlt_tmp->remove;
die $result->{err_msg};
}
else {
print join(' ',"-->> mv",$sqlt_tmp->relative,$sqlt->relative);
$sqlt->remove if (-f $sqlt);
$sqlt_tmp->move_to($sqlt);
}
print "\n";
# for good measure, make sure we're generating from $sqlt in the next step
($dsn,$user,$pass) = ("dbi:SQLite:$sqlt",'','');
}
[% END %]
if($schema) {
my @connect = ($dsn,$user,$pass);
print "\nDumping schema \"$schema_class\" to \"" . file($applib)->resolve->relative . "\"\n";
print "[ " . join(' ',map { $_||"''" } @connect) . " ]\n\n";
DBIx::Class::Schema::Loader::make_schema_at(
$schema_class,
{
debug => 1,
dump_directory => $applib,
use_moose => 1, generate_pod => 0,
components => ["InflateColumn::DateTime"],
},
[
@connect,
{ loader_class => 'RapidApp::Util::MetaKeys::Loader' }
]
);
print "\n";
}
if($cfg) {
my $pm_path = file( scalar Module::Locate::locate($model_class) );
dir($applib)->contains($pm_path) or die "$pm_path is not within the local dir!";
$pm_path = $pm_path->resolve->absolute;
my $appdir = dir($approot)->resolve->absolute;
print join('',
"\n==> Updating TableSpecs configs in ",
$appdir->basename,'/',$pm_path->relative($appdir),
" ... "
);
my $CfgW = RapidApp::Util::RapidDbic::CfgWriter->new({ pm_file => "$pm_path" });
$CfgW->save_to( "$pm_path" );
print "done.\n";
}
1;
__END__
=head1 NAME
[%- updater_script_name -%] - updater script for RapidDbic model '[%- model_class -%]'
=head1 SYNOPSIS
perl devel/[%- updater_script_name -%] [options]
Options:
--schema Regenerate DBIx::Class schema [% IF from_ddl %]
--from-ddl Recreate db from DDL (implies --schema)
--go Do not prompt before overwriting db from DDL [% END %]
--cfg Update TableSpec configs for new defaults (nondestructive)
[% IF from_ddl %]
--all Shortcut for: --from-ddl --schema --cfg [% ELSE %]
--all Shortcut for: --schema --cfg [% END %]
TableSpec Config Update (--cfg):
When called with the --cfg option, the TableSpec configs within the RapidDbic section of
the model will be updated to match the schema class. This is a non-destructive operation,
it will not change any existing configs, just add defaults that do not already exist. This
adds boilerplate for new tables and columns which were added after the app was bootstrapped.