From: Luke Saunders Date: Wed, 30 Jan 2008 23:05:33 +0000 (+0000) Subject: dump functionality added X-Git-Tag: v1.001002~57 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=0fc424b7172bca931bd693485e4a4fb6ad8079b6;p=dbsrgits%2FDBIx-Class-Fixtures.git dump functionality added --- diff --git a/Makefile.PL b/Makefile.PL index 78ecf59..65229ec 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -9,6 +9,13 @@ WriteMakefile( PL_FILES => {}, PREREQ_PM => { 'Test::More' => 0, + 'DBIx::Class' => 0, + 'Class::Accessor' => 0, + 'Path::Class' => 0, + 'Config::Any' => 0, + 'Data::Dump::Streamer' => 0, + 'File::Slurp' => 0, + 'Hash::Merge' => 0, }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, clean => { FILES => 'DBIx-Class-Fixtures-*' }, diff --git a/lib/DBIx/Class/Fixtures.pm b/lib/DBIx/Class/Fixtures.pm index f9af4e4..ee4fbeb 100644 --- a/lib/DBIx/Class/Fixtures.pm +++ b/lib/DBIx/Class/Fixtures.pm @@ -6,13 +6,16 @@ use warnings; use DBIx::Class::Exception; use Class::Accessor; use Path::Class qw(dir file); -use FindBin; -use JSON::Syck qw(LoadFile); +use Config::Any::JSON; +use Data::Dump::Streamer; +use File::Slurp; +use File::Path; +use Hash::Merge qw( merge ); use Data::Dumper; use base qw(Class::Accessor); -__PACKAGE__->mk_accessors(qw(config_dir)); +__PACKAGE__->mk_accessors(qw(config_dir _inherited_attributes debug)); =head1 VERSION @@ -50,8 +53,11 @@ our $VERSION = '1.000'; =head1 CONTRIBUTORS -=cut +=head1 METHODS + +=head2 new +=cut sub new { my $class = shift; @@ -71,7 +77,9 @@ sub new { } my $self = { - config_dir => $config_dir + config_dir => $config_dir, + _inherited_attributes => [qw/datetime_relative might_have rules/], + debug => $params->{debug} }; bless $self, $class; @@ -79,4 +87,228 @@ sub new { return $self; } +=head2 dump + +=cut + +sub dump { + my $self = shift; + + my ($params) = @_; + unless (ref $params eq 'HASH') { + return DBIx::Class::Exception->throw('first arg to dump must be hash ref'); + } + + foreach my $param (qw/config schema directory/) { + unless ($params->{$param}) { + return DBIx::Class::Exception->throw($param . ' param not specified'); + } + } + + my $config_file = file($self->config_dir, $params->{config}); + unless (-e $config_file) { + return DBIx::Class::Exception->throw('config does not exist at ' . $config_file); + } + + my $config = Config::Any::JSON->load($config_file); + unless ($config && $config->{sets} && ref $config->{sets} eq 'ARRAY' && scalar(@{$config->{sets}})) { + return DBIx::Class::Exception->throw('config has no sets'); + } + + my $output_dir = dir($params->{directory}); + unless (-e $output_dir) { + return DBIx::Class::Exception->throw('output directory does not exist at ' . $output_dir); + } + + my $schema = $params->{schema}; + + $self->msg("generating fixtures\n"); + my $tmp_output_dir = dir($output_dir, '-~dump~-'); + + unless (-e $tmp_output_dir) { + $self->msg("- creating $tmp_output_dir"); + mkdir($tmp_output_dir, 0777); + }else { + $self->msg("- clearing existing $tmp_output_dir"); + # delete existing fixture set + system("rm -rf $tmp_output_dir/*"); + } + + # write version file (for the potential benefit of populate) + my $version_file = file($tmp_output_dir, '_dumper_version'); + write_file($version_file->stringify, $VERSION); + + $config->{rules} ||= {}; + my @sources = sort { $a->{class} cmp $b->{class} } @{delete $config->{sets}}; + my %options = ( is_root => 1 ); + foreach my $source (@sources) { + # apply rule to set if specified + my $rule = $config->{rules}->{$source->{class}}; + $source = merge( $source, $rule ) if ($rule); + + # fetch objects + my $rs = $schema->resultset($source->{class}); + $rs = $rs->search($source->{cond}, { join => $source->{join} }) if ($source->{cond}); + $self->msg("- dumping $source->{class}"); + my @objects; + my %source_options = ( set => { %{$config}, %{$source} } ); + if ($source->{quantity}) { + $rs = $rs->search({}, { order_by => $source->{order_by} }) if ($source->{order_by}); + if ($source->{quantity} eq 'all') { + push (@objects, $rs->all); + } elsif ($source->{quantity} =~ /^\d+$/) { + push (@objects, $rs->search({}, { rows => $source->{quantity} })); + } else { + DBIx::Class::Exception->throw('invalid value for quantity - ' . $source->{quantity}); + } + } + if ($source->{ids}) { + my @ids = @{$source->{ids}}; + my @id_objects = grep { $_ } map { $rs->find($_) } @ids; + push (@objects, @id_objects); + } + unless ($source->{quantity} || $source->{ids}) { + DBIx::Class::Exception->throw('must specify either quantity or ids'); + } + + # dump objects + foreach my $object (@objects) { + $source_options{set_dir} = $tmp_output_dir; + $self->dump_object($object, { %options, %source_options } ); + next; + } + } + + foreach my $dir ($output_dir->children) { + next if ($dir eq $tmp_output_dir); + $dir->remove || $dir->rmtree; + } + + $self->msg("- moving temp dir to $output_dir"); + system("mv $tmp_output_dir/* $output_dir/"); + if (-e $output_dir) { + $self->msg("- clearing tmp dir $tmp_output_dir"); + # delete existing fixture set + $tmp_output_dir->remove; + } + + $self->msg("done"); + + return 1; +} + +sub dump_object { + my ($self, $object, $params, $rr_info) = @_; + my $set = $params->{set}; + die 'no dir passed to dump_object' unless $params->{set_dir}; + die 'no object passed to dump_object' unless $object; + + my @inherited_attrs = @{$self->_inherited_attributes}; + + # write dir and gen filename + my $source_dir = dir($params->{set_dir}, lc($object->result_source->from)); + mkdir($source_dir->stringify, 0777); + my $file = file($source_dir, join('-', map { $object->get_column($_) } sort $object->primary_columns) . '.fix'); + + # write file + my $exists = (-e $file->stringify) ? 1 : 0; + unless ($exists) { + $self->msg('-- dumping ' . $file->stringify, 2); + my %ds = $object->get_columns; + + # mess with dates if specified + if ($set->{datetime_relative}) { + my $dt; + if ($set->{datetime_relative} eq 'today') { + $dt = DateTime->today; + } else { + require DateTime::Format::MySQL; + $dt = DateTime::Format::MySQL->parse_datetime($set->{datetime_relative}); + } + + while (my ($col, $value) = each %ds) { + my $col_info = $object->result_source->column_info($col); + + next unless $value + && $col_info->{_inflate_info} + && uc($col_info->{data_type}) eq 'DATETIME'; + + $ds{$col} = $object->get_inflated_column($col)->subtract_datetime($dt); + } + } + + # do the actual dumping + my $serialized = Dump(\%ds)->Out(); + write_file($file->stringify, $serialized); + my $mode = 0777; chmod $mode, $file->stringify; + } + + # dump rels of object + my $s = $object->result_source; + unless ($exists) { + foreach my $name (sort $s->relationships) { + my $info = $s->relationship_info($name); + my $r_source = $s->related_source($name); + # if belongs_to or might_have with might_have param set or has_many with has_many param set then + if (($info->{attrs}{accessor} eq 'single' && (!$info->{attrs}{join_type} || ($set->{might_have} && $set->{might_have}->{fetch}))) || $info->{attrs}{accessor} eq 'filter' || ($info->{attrs}{accessor} eq 'multi' && ($set->{has_many} && $set->{has_many}->{fetch}))) { + my $related_rs = $object->related_resultset($name); + my $rule = $set->{rules}->{$related_rs->result_source->source_name}; + # these parts of the rule only apply to has_many rels + if ($rule && $info->{attrs}{accessor} eq 'multi') { + $related_rs = $related_rs->search($rule->{cond}, { join => $rule->{join} }) if ($rule->{cond}); + $related_rs = $related_rs->search({}, { rows => $rule->{quantity} }) if ($rule->{quantity} && $rule->{quantity} ne 'all'); + $related_rs = $related_rs->search({}, { order_by => $rule->{order_by} }) if ($rule->{order_by}); + } + if ($set->{has_many}->{quantity} && $set->{has_many}->{quantity} =~ /^\d+$/) { + $related_rs = $related_rs->search({}, { rows => $set->{has_many}->{quantity} }); + } + my %c_params = %{$params}; + # inherit date param + my %mock_set = map { $_ => $set->{$_} } grep { $set->{$_} } @inherited_attrs; + $c_params{set} = \%mock_set; + # use Data::Dumper; print ' -- ' . Dumper($c_params{set}, $rule->{fetch}) if ($rule && $rule->{fetch}); + $c_params{set} = merge( $c_params{set}, $rule) if ($rule && $rule->{fetch}); + # use Data::Dumper; print ' -- ' . Dumper(\%c_params) if ($rule && $rule->{fetch}); + dump_object($_, \%c_params) foreach $related_rs->all; + } + } + } + + return unless $set && $set->{fetch}; + foreach my $fetch (@{$set->{fetch}}) { + # inherit date param + $fetch->{$_} = $set->{$_} foreach grep { !$fetch->{$_} && $set->{$_} } @inherited_attrs; + my $related_rs = $object->related_resultset($fetch->{rel}); + my $rule = $set->{rules}->{$related_rs->result_source->source_name}; + if ($rule) { + my $info = $object->result_source->relationship_info($fetch->{rel}); + if ($info->{attrs}{accessor} eq 'multi') { + $fetch = merge( $fetch, $rule ); + } elsif ($rule->{fetch}) { + $fetch = merge( $fetch, { fetch => $rule->{fetch} } ); + } + } + die "relationship " . $fetch->{rel} . " does not exist for " . $s->source_name unless ($related_rs); + if ($fetch->{cond} and ref $fetch->{cond} eq 'HASH') { + # if value starts with / assume it's meant to be passed as a scalar ref to dbic + # ideally this would substitute deeply + $fetch->{cond} = { map { $_ => ($fetch->{cond}->{$_} =~ s/^\\//) ? \$fetch->{cond}->{$_} : $fetch->{cond}->{$_} } keys %{$fetch->{cond}} }; + } + $related_rs = $related_rs->search($fetch->{cond}, { join => $fetch->{join} }) if ($fetch->{cond}); + $related_rs = $related_rs->search({}, { rows => $fetch->{quantity} }) if ($fetch->{quantity} && $fetch->{quantity} ne 'all'); + $related_rs = $related_rs->search({}, { order_by => $fetch->{order_by} }) if ($fetch->{order_by}); + dump_object($_, { %{$params}, set => $fetch }) foreach $related_rs->all; + } +} + +sub msg { + my $self = shift; + my $subject = shift || return; + return unless $self->debug; + if (ref $subject) { + print Dumper($subject); + } else { + print $subject . "\n"; + } +} 1;