1 package DBIx::Class::Fixtures;
6 use DBIx::Class 0.08100;
7 use DBIx::Class::Exception;
8 use Class::Accessor::Grouped;
9 use Path::Class qw(dir file);
10 use Config::Any::JSON;
11 use Data::Dump::Streamer;
12 use Data::Visitor::Callback;
14 use File::Copy::Recursive qw/dircopy/;
15 use File::Copy qw/move/;
16 use Hash::Merge qw( merge );
18 use Class::C3::Componentised;
20 use base qw(Class::Accessor::Grouped);
22 our $namespace_counter = 0;
24 __PACKAGE__->mk_group_accessors( 'simple' => qw/config_dir
25 _inherited_attributes debug schema_class dumped_objects/);
33 our $VERSION = '1.001013';
37 DBIx::Class::Fixtures - Dump data and repopulate a database using rules
41 use DBIx::Class::Fixtures;
45 my $fixtures = DBIx::Class::Fixtures->new({
46 config_dir => '/home/me/app/fixture_configs'
50 config => 'set_config.json',
51 schema => $source_dbic_schema,
52 directory => '/home/me/app/fixtures'
56 directory => '/home/me/app/fixtures',
57 ddl => '/home/me/app/sql/ddl.sql',
58 connection_details => ['dbi:mysql:dbname=app_dev', 'me', 'password'],
59 post_ddl => '/home/me/app/sql/post_ddl.sql',
64 Dump fixtures from source database to filesystem then import to another
65 database (with same schema) at any time. Use as a constant dataset for running
66 tests against or for populating development databases when impractical to use
67 production clones. Describe fixture set using relations and conditions based on
68 your DBIx::Class schema.
70 =head1 DEFINE YOUR FIXTURE SET
72 Fixture sets are currently defined in .json files which must reside in your
73 config_dir (e.g. /home/me/app/fixture_configs/a_fixture_set.json). They
74 describe which data to pull and dump from the source database.
97 This will fetch artists with primary keys 1 and 3, the producer with primary
98 key 5 and two of producer 5's artists where 'artists' is a has_many DBIx::Class
99 rel from Producer to Artist.
101 The top level attributes are as follows:
105 Sets must be an array of hashes, as in the example given above. Each set
106 defines a set of objects to be included in the fixtures. For details on valid
107 set attributes see L</SET ATTRIBUTES> below.
111 Rules place general conditions on classes. For example if whenever an artist
112 was dumped you also wanted all of their cds dumped too, then you could use a
113 rule to specify this. For example:
142 In this case all the cds of artists 1, 3 and all producer 5's artists will be
143 dumped as well. Note that 'cds' is a has_many DBIx::Class relation from Artist
144 to CD. This is eqivalent to:
171 rules must be a hash keyed by class name.
177 To prevent repetition between configs you can include other configs. For
186 { "file": "base.json" }
190 Includes must be an arrayref of hashrefs where the hashrefs have key 'file'
191 which is the name of another config file in the same directory. The original
192 config is merged with its includes using L<Hash::Merge>.
194 =head2 datetime_relative
196 Only available for MySQL and PostgreSQL at the moment, must be a value that
197 DateTime::Format::* can parse. For example:
201 "class": "RecentItems",
204 "datetime_relative": "2007-10-30 00:00:00"
207 This will work when dumping from a MySQL database and will cause any datetime
208 fields (where datatype => 'datetime' in the column def of the schema class) to
209 be dumped as a DateTime::Duration object relative to the date specified in the
210 datetime_relative value. For example if the RecentItem object had a date field
211 set to 2007-10-25, then when the fixture is imported the field will be set to 5
212 days in the past relative to the current time.
216 Specifies whether to automatically dump might_have relationships. Should be a
217 hash with one attribute - fetch. Set fetch to 1 or 0.
220 "might_have": { "fetch": 1 },
233 Note: belongs_to rels are automatically dumped whether you like it or not, this
234 is to avoid FKs to nowhere when importing. General rules on has_many rels are
235 not accepted at this top level, but you can turn them on for individual sets -
236 see L</SET ATTRIBUTES>.
238 =head1 SET ATTRIBUTES
242 Required attribute. Specifies the DBIx::Class object class you wish to dump.
246 Array of primary key ids to fetch, basically causing an $rs->find($_) for each.
247 If the id is not in the source db then it just won't get dumped, no warnings or
252 Must be either an integer or the string 'all'. Specifying an integer will
253 effectively set the 'rows' attribute on the resultset clause, specifying 'all'
254 will cause the rows attribute to be left off and for all matching rows to be
255 dumped. There's no randomising here, it's just the first x rows.
259 A hash specifying the conditions dumped objects must match. Essentially this is
260 a JSON representation of a DBIx::Class search clause. For example:
266 "cond": { "name": "Dave" }
270 This will dump all artists whose name is 'dave'. Essentially
271 $artist_rs->search({ name => 'Dave' })->all.
273 Sometimes in a search clause it's useful to use scalar refs to do things like:
275 $artist_rs->search({ no1_singles => \'> no1_albums' })
277 This could be specified in the cond hash like so:
283 "cond": { "no1_singles": "\> no1_albums" }
287 So if the value starts with a backslash the value is made a scalar ref before
288 being passed to search.
292 An array of relationships to be used in the cond clause.
298 "cond": { "cds.position": { ">": 4 } },
303 Fetch all artists who have cds with position greater than 4.
307 Must be an array of hashes. Specifies which rels to also dump. For example:
316 "cond": { "position": "2" }
321 Will cause the cds of artists 1 and 3 to be dumped where the cd position is 2.
323 Valid attributes are: 'rel', 'quantity', 'cond', 'has_many', 'might_have' and
324 'join'. rel is the name of the DBIx::Class rel to follow, the rest are the same
325 as in the set attributes. quantity is necessary for has_many relationships, but
326 not if using for belongs_to or might_have relationships.
330 Specifies whether to fetch has_many rels for this set. Must be a hash
331 containing keys fetch and quantity.
333 Set fetch to 1 if you want to fetch them, and quantity to either 'all' or an
336 Be careful here, dumping has_many rels can lead to a lot of data being dumped.
340 As with has_many but for might_have relationships. Quantity doesn't do anything
343 This value will be inherited by all fetches in this set. This is not true for
344 the has_many attribute.
346 =head1 RULE ATTRIBUTES
350 Same as with L</SET ATTRIBUTES>
354 Same as with L</SET ATTRIBUTES>
358 Same as with L</SET ATTRIBUTES>
362 Same as with L</SET ATTRIBUTES>
366 Same as with L</SET ATTRIBUTES>
374 =item Arguments: \%$attrs
376 =item Return Value: $fixture_object
380 Returns a new DBIx::Class::Fixture object. %attrs can have the following
387 required. must contain a valid path to the directory in which your .json
392 determines whether to be verbose
394 =item ignore_sql_errors:
396 ignore errors on import of DDL etc
400 my $fixtures = DBIx::Class::Fixtures->new( {
401 config_dir => '/home/me/app/fixture_configs'
410 unless (ref $params eq 'HASH') {
411 return DBIx::Class::Exception->throw('first arg to DBIx::Class::Fixtures->new() must be hash ref');
414 unless ($params->{config_dir}) {
415 return DBIx::Class::Exception->throw('config_dir param not specified');
418 my $config_dir = dir($params->{config_dir});
419 unless (-e $params->{config_dir}) {
420 return DBIx::Class::Exception->throw('config_dir directory doesn\'t exist');
424 config_dir => $config_dir,
425 _inherited_attributes => [qw/datetime_relative might_have rules belongs_to/],
426 debug => $params->{debug} || 0,
427 ignore_sql_errors => $params->{ignore_sql_errors},
428 dumped_objects => {},
429 use_create => $params->{use_create} || 0
437 =head2 available_config_sets
439 Returns a list of all the config sets found in the L</config_dir>. These will
440 be a list of the json based files containing dump rules.
445 sub available_config_sets {
446 @config_sets = scalar(@config_sets) ? @config_sets : map {
449 -f $_ && $_=~/json$/;
450 } dir((shift)->config_dir)->children;
457 =item Arguments: \%$attrs
459 =item Return Value: 1
464 config => 'set_config.json', # config file to use. must be in the config
465 # directory specified in the constructor
466 schema => $source_dbic_schema,
467 directory => '/home/me/app/fixtures' # output directory
473 all => 1, # just dump everything that's in the schema
474 schema => $source_dbic_schema,
475 directory => '/home/me/app/fixtures' # output directory
478 In this case objects will be dumped to subdirectories in the specified
479 directory. For example:
481 /home/me/app/fixtures/artist/1.fix
482 /home/me/app/fixtures/artist/3.fix
483 /home/me/app/fixtures/producer/5.fix
485 schema and directory are required attributes. also, one of config or all must
488 Lastly, the C<config> parameter can be a Perl HashRef instead of a file name.
489 If this form is used your HashRef should conform to the structure rules defined
490 for the JSON representations.
498 unless (ref $params eq 'HASH') {
499 return DBIx::Class::Exception->throw('first arg to dump must be hash ref');
502 foreach my $param (qw/schema directory/) {
503 unless ($params->{$param}) {
504 return DBIx::Class::Exception->throw($param . ' param not specified');
508 if($params->{excludes} && !$params->{all}) {
509 return DBIx::Class::Exception->throw("'excludes' param only works when using the 'all' param");
512 my $schema = $params->{schema};
514 if ($params->{config}) {
515 $config = ref $params->{config} eq 'HASH' ?
519 my $config_file = $self->config_dir->file($params->{config});
520 $self->load_config_file($config_file);
522 } elsif ($params->{all}) {
523 my %excludes = map {$_=>1} @{$params->{excludes}||[]};
525 might_have => { fetch => 0 },
526 has_many => { fetch => 0 },
527 belongs_to => { fetch => 0 },
530 { class => $_, quantity => 'all' };
536 DBIx::Class::Exception->throw('must pass config or set all');
539 my $output_dir = dir($params->{directory});
540 unless (-e $output_dir) {
541 $output_dir->mkpath ||
542 DBIx::Class::Exception->throw("output directory does not exist at $output_dir");
545 $self->msg("generating fixtures");
546 my $tmp_output_dir = dir($output_dir, '-~dump~-' . $<);
548 if (-e $tmp_output_dir) {
549 $self->msg("- clearing existing $tmp_output_dir");
550 $tmp_output_dir->rmtree;
552 $self->msg("- creating $tmp_output_dir");
553 $tmp_output_dir->mkpath;
555 # write version file (for the potential benefit of populate)
556 $tmp_output_dir->file('_dumper_version')
560 $config->{rules} ||= {};
561 my @sources = sort { $a->{class} cmp $b->{class} } @{delete $config->{sets}};
563 while ( my ($k,$v) = each %{ $config->{rules} } ) {
564 if ( my $source = eval { $schema->source($k) } ) {
565 $config->{rules}{$source->source_name} = $v;
569 foreach my $source (@sources) {
570 # apply rule to set if specified
571 my $rule = $config->{rules}->{$source->{class}};
572 $source = merge( $source, $rule ) if ($rule);
575 my $rs = $schema->resultset($source->{class});
577 if ($source->{cond} and ref $source->{cond} eq 'HASH') {
578 # if value starts with \ assume it's meant to be passed as a scalar ref
579 # to dbic. ideally this would substitute deeply
582 $_ => ($source->{cond}->{$_} =~ s/^\\//) ? \$source->{cond}->{$_}
583 : $source->{cond}->{$_}
584 } keys %{$source->{cond}}
588 $rs = $rs->search($source->{cond}, { join => $source->{join} })
591 $self->msg("- dumping $source->{class}");
593 my %source_options = ( set => { %{$config}, %{$source} } );
594 if ($source->{quantity}) {
595 $rs = $rs->search({}, { order_by => $source->{order_by} })
596 if $source->{order_by};
598 if ($source->{quantity} =~ /^\d+$/) {
599 $rs = $rs->search({}, { rows => $source->{quantity} });
600 } elsif ($source->{quantity} ne 'all') {
601 DBIx::Class::Exception->throw("invalid value for quantity - $source->{quantity}");
604 elsif ($source->{ids} && @{$source->{ids}}) {
605 my @ids = @{$source->{ids}};
606 my (@pks) = $rs->result_source->primary_columns;
607 die "Can't dump multiple col-pks using 'id' option" if @pks > 1;
608 $rs = $rs->search_rs( { $pks[0] => { -in => \@ids } } );
611 DBIx::Class::Exception->throw('must specify either quantity or ids');
614 $source_options{set_dir} = $tmp_output_dir;
615 $self->dump_rs($rs, \%source_options );
618 # clear existing output dir
619 foreach my $child ($output_dir->children) {
620 if ($child->is_dir) {
621 next if ($child eq $tmp_output_dir);
622 if (grep { $_ =~ /\.fix/ } $child->children) {
625 } elsif ($child =~ /_dumper_version$/) {
630 $self->msg("- moving temp dir to $output_dir");
631 move($_, dir($output_dir, $_->relative($_->parent)->stringify))
632 for $tmp_output_dir->children;
634 if (-e $output_dir) {
635 $self->msg("- clearing tmp dir $tmp_output_dir");
636 # delete existing fixture set
637 $tmp_output_dir->remove;
645 sub load_config_file {
646 my ($self, $config_file) = @_;
647 DBIx::Class::Exception->throw("config does not exist at $config_file")
648 unless -e $config_file;
650 my $config = Config::Any::JSON->load($config_file);
653 if (my $incs = $config->{includes}) {
655 DBIx::Class::Exception->throw(
656 'includes params of config must be an array ref of hashrefs'
657 ) unless ref $incs eq 'ARRAY';
659 foreach my $include_config (@$incs) {
660 DBIx::Class::Exception->throw(
661 'includes params of config must be an array ref of hashrefs'
662 ) unless (ref $include_config eq 'HASH') && $include_config->{file};
664 my $include_file = $self->config_dir->file($include_config->{file});
666 DBIx::Class::Exception->throw("config does not exist at $include_file")
667 unless -e $include_file;
669 my $include = Config::Any::JSON->load($include_file);
670 $self->msg($include);
671 $config = merge( $config, $include );
673 delete $config->{includes};
677 return DBIx::Class::Exception->throw('config has no sets')
678 unless $config && $config->{sets} &&
679 ref $config->{sets} eq 'ARRAY' && scalar @{$config->{sets}};
681 $config->{might_have} = { fetch => 0 } unless exists $config->{might_have};
682 $config->{has_many} = { fetch => 0 } unless exists $config->{has_many};
683 $config->{belongs_to} = { fetch => 1 } unless exists $config->{belongs_to};
689 my ($self, $rs, $params) = @_;
691 while (my $row = $rs->next) {
692 $self->dump_object($row, $params);
697 my ($self, $object, $params) = @_;
698 my $set = $params->{set};
699 die 'no dir passed to dump_object' unless $params->{set_dir};
700 die 'no object passed to dump_object' unless $object;
702 my @inherited_attrs = @{$self->_inherited_attributes};
705 $object->get_column($_)
706 } $object->primary_columns;
708 my $key = join("\0", @pk_vals);
710 my $src = $object->result_source;
711 my $exists = $self->dumped_objects->{$src->name}{$key}++;
714 # write dir and gen filename
715 my $source_dir = $params->{set_dir}->subdir(lc $src->from);
716 $source_dir->mkpath(0, 0777);
718 # strip dir separators from file name
719 my $file = $source_dir->file(
720 join('-', map { s|[/\\]|_|g; $_; } @pk_vals) . '.fix'
726 $self->msg('-- dumping ' . $file->stringify, 2);
727 my %ds = $object->get_columns;
729 # mess with dates if specified
730 if ($set->{datetime_relative}) {
731 my $formatter= $object->result_source->schema->storage->datetime_parser;
732 unless ($@ || !$formatter) {
734 if ($set->{datetime_relative} eq 'today') {
735 $dt = DateTime->today;
737 $dt = $formatter->parse_datetime($set->{datetime_relative}) unless ($@);
740 while (my ($col, $value) = each %ds) {
741 my $col_info = $object->result_source->column_info($col);
744 && $col_info->{_inflate_info}
745 && uc($col_info->{data_type}) eq 'DATETIME';
747 $ds{$col} = $object->get_inflated_column($col)->subtract_datetime($dt);
750 warn "datetime_relative not supported for this db driver at the moment";
754 # do the actual dumping
755 my $serialized = Dump(\%ds)->Out();
756 $file->openw->print($serialized);
759 # don't bother looking at rels unless we are actually planning to dump at least one type
760 my ($might_have, $belongs_to, $has_many) = map {
761 $set->{$_}{fetch} || $set->{rules}{$src->source_name}{$_}{fetch}
762 } qw/might_have belongs_to has_many/;
764 return unless $might_have
769 # dump rels of object
771 foreach my $name (sort $src->relationships) {
772 my $info = $src->relationship_info($name);
773 my $r_source = $src->related_source($name);
774 # if belongs_to or might_have with might_have param set or has_many with
775 # has_many param set then
777 ( $info->{attrs}{accessor} eq 'single' &&
778 (!$info->{attrs}{join_type} || $might_have)
780 || $info->{attrs}{accessor} eq 'filter'
782 ($info->{attrs}{accessor} eq 'multi' && $has_many)
784 my $related_rs = $object->related_resultset($name);
785 my $rule = $set->{rules}->{$related_rs->result_source->source_name};
786 # these parts of the rule only apply to has_many rels
787 if ($rule && $info->{attrs}{accessor} eq 'multi') {
788 $related_rs = $related_rs->search(
790 { join => $rule->{join} }
791 ) if ($rule->{cond});
793 $related_rs = $related_rs->search(
795 { rows => $rule->{quantity} }
796 ) if ($rule->{quantity} && $rule->{quantity} ne 'all');
798 $related_rs = $related_rs->search(
800 { order_by => $rule->{order_by} }
801 ) if ($rule->{order_by});
804 if ($set->{has_many}{quantity} &&
805 $set->{has_many}{quantity} =~ /^\d+$/) {
806 $related_rs = $related_rs->search(
808 { rows => $set->{has_many}->{quantity} }
812 my %c_params = %{$params};
816 } grep { $set->{$_} } @inherited_attrs;
818 $c_params{set} = \%mock_set;
819 $c_params{set} = merge( $c_params{set}, $rule)
820 if $rule && $rule->{fetch};
822 $self->dump_rs($related_rs, \%c_params);
827 return unless $set && $set->{fetch};
828 foreach my $fetch (@{$set->{fetch}}) {
830 $fetch->{$_} = $set->{$_} foreach
831 grep { !$fetch->{$_} && $set->{$_} } @inherited_attrs;
832 my $related_rs = $object->related_resultset($fetch->{rel});
833 my $rule = $set->{rules}->{$related_rs->result_source->source_name};
836 my $info = $object->result_source->relationship_info($fetch->{rel});
837 if ($info->{attrs}{accessor} eq 'multi') {
838 $fetch = merge( $fetch, $rule );
839 } elsif ($rule->{fetch}) {
840 $fetch = merge( $fetch, { fetch => $rule->{fetch} } );
844 die "relationship $fetch->{rel} does not exist for " . $src->source_name
845 unless ($related_rs);
847 if ($fetch->{cond} and ref $fetch->{cond} eq 'HASH') {
848 # if value starts with \ assume it's meant to be passed as a scalar ref
849 # to dbic. ideally this would substitute deeply
850 $fetch->{cond} = { map {
851 $_ => ($fetch->{cond}->{$_} =~ s/^\\//) ? \$fetch->{cond}->{$_}
852 : $fetch->{cond}->{$_}
853 } keys %{$fetch->{cond}} };
856 $related_rs = $related_rs->search(
858 { join => $fetch->{join} }
861 $related_rs = $related_rs->search(
863 { rows => $fetch->{quantity} }
864 ) if $fetch->{quantity} && $fetch->{quantity} ne 'all';
865 $related_rs = $related_rs->search(
867 { order_by => $fetch->{order_by} }
868 ) if $fetch->{order_by};
870 $self->dump_rs($related_rs, { %{$params}, set => $fetch });
874 sub _generate_schema {
876 my $params = shift || {};
878 $self->msg("\ncreating schema");
880 my $schema_class = $self->schema_class || "DBIx::Class::Fixtures::Schema";
881 eval "require $schema_class";
885 my $connection_details = $params->{connection_details};
887 $namespace_counter++;
889 my $namespace = "DBIx::Class::Fixtures::GeneratedSchema_$namespace_counter";
890 Class::C3::Componentised->inject_base( $namespace => $schema_class );
892 $pre_schema = $namespace->connect(@{$connection_details});
893 unless( $pre_schema ) {
894 return DBIx::Class::Exception->throw('connection details not valid');
896 my @tables = map { $pre_schema->source($_)->from } $pre_schema->sources;
897 $self->msg("Tables to drop: [". join(', ', sort @tables) . "]");
898 my $dbh = $pre_schema->storage->dbh;
901 $self->msg("- clearing DB of existing tables");
902 $pre_schema->storage->txn_do(sub {
903 $pre_schema->storage->with_deferred_fk_checks(sub {
904 foreach my $table (@tables) {
906 $dbh->do("drop table $table" . ($params->{cascade} ? ' cascade' : '') )
912 # import new ddl file to db
913 my $ddl_file = $params->{ddl};
914 $self->msg("- deploying schema using $ddl_file");
915 my $data = _read_sql($ddl_file);
917 eval { $dbh->do($_) or warn "SQL was:\n $_"};
918 if ($@ && !$self->{ignore_sql_errors}) { die "SQL was:\n $_\n$@"; }
920 $self->msg("- finished importing DDL into DB");
922 # load schema object from our new DB
923 $namespace_counter++;
924 my $namespace2 = "DBIx::Class::Fixtures::GeneratedSchema_$namespace_counter";
925 Class::C3::Componentised->inject_base( $namespace2 => $schema_class );
926 my $schema = $namespace2->connect(@{$connection_details});
931 my $ddl_file = shift;
933 open $fh, "<$ddl_file" or die ("Can't open DDL file, $ddl_file ($!)");
934 my @data = split(/\n/, join('', <$fh>));
935 @data = grep(!/^--/, @data);
936 @data = split(/;/, join('', @data));
938 @data = grep { $_ && $_ !~ /^-- / } @data;
942 =head2 dump_config_sets
944 Works just like L</dump> but instead of specifying a single json config set
945 located in L</config_dir> we dump each set named in the C<configs> parameter.
947 The parameters are the same as for L</dump> except instead of a C<directory>
948 parameter we have a C<directory_template> which is a coderef expected to return
949 a scalar that is a root directory where we will do the actual dumping. This
950 coderef get three arguments: C<$self>, C<$params> and C<$set_name>. For
953 $fixture->dump_all_config_sets({
955 configs => [qw/one.json other.json/],
956 directory_template => sub {
957 my ($fixture, $params, $set) = @_;
958 return File::Spec->catdir('var', 'fixtures', $params->{schema}->version, $set);
964 sub dump_config_sets {
965 my ($self, $params) = @_;
966 my $available_config_sets = delete $params->{configs};
967 my $directory_template = delete $params->{directory_template} ||
968 DBIx::Class::Exception->throw("'directory_template is required parameter");
970 for my $set (@$available_config_sets) {
971 my $localparams = $params;
972 $localparams->{directory} = $directory_template->($self, $localparams, $set);
973 $localparams->{config} = $set;
974 $self->dump($localparams);
975 $self->dumped_objects({}); ## Clear dumped for next go, if there is one!
979 =head2 dump_all_config_sets
981 my %local_params = %$params;
982 my $local_self = bless { %$self }, ref($self);
983 $local_params{directory} = $directory_template->($self, \%local_params, $set);
984 $local_params{config} = $set;
985 $self->dump(\%local_params);
988 Works just like L</dump> but instead of specifying a single json config set
989 located in L</config_dir> we dump each set in turn to the specified directory.
991 The parameters are the same as for L</dump> except instead of a C<directory>
992 parameter we have a C<directory_template> which is a coderef expected to return
993 a scalar that is a root directory where we will do the actual dumping. This
994 coderef get three arguments: C<$self>, C<$params> and C<$set_name>. For
997 $fixture->dump_all_config_sets({
999 directory_template => sub {
1000 my ($fixture, $params, $set) = @_;
1001 return File::Spec->catdir('var', 'fixtures', $params->{schema}->version, $set);
1007 sub dump_all_config_sets {
1008 my ($self, $params) = @_;
1009 $self->dump_config_sets({
1011 configs=>[$self->available_config_sets],
1019 =item Arguments: \%$attrs
1021 =item Return Value: 1
1025 $fixtures->populate( {
1026 # directory to look for fixtures in, as specified to dump
1027 directory => '/home/me/app/fixtures',
1030 ddl => '/home/me/app/sql/ddl.sql',
1032 # database to clear, deploy and then populate
1033 connection_details => ['dbi:mysql:dbname=app_dev', 'me', 'password'],
1035 # DDL to deploy after populating records, ie. FK constraints
1036 post_ddl => '/home/me/app/sql/post_ddl.sql',
1038 # use CASCADE option when dropping tables
1041 # optional, set to 1 to run ddl but not populate
1044 # optional, set to 1 to run each fixture through ->create rather than have
1045 # each $rs populated using $rs->populate. Useful if you have overridden new() logic
1046 # that effects the value of column(s).
1049 # Dont try to clean the database, just populate over whats there. Requires
1050 # schema option. Use this if you want to handle removing old data yourself
1055 In this case the database app_dev will be cleared of all tables, then the
1056 specified DDL deployed to it, then finally all fixtures found in
1057 /home/me/app/fixtures will be added to it. populate will generate its own
1058 DBIx::Class schema from the DDL rather than being passed one to use. This is
1059 better as custom insert methods are avoided which can to get in the way. In
1060 some cases you might not have a DDL, and so this method will eventually allow a
1061 $schema object to be passed instead.
1063 If needed, you can specify a post_ddl attribute which is a DDL to be applied
1064 after all the fixtures have been added to the database. A good use of this
1065 option would be to add foreign key constraints since databases like Postgresql
1066 cannot disable foreign key checks.
1068 If your tables have foreign key constraints you may want to use the cascade
1069 attribute which will make the drop table functionality cascade, ie 'DROP TABLE
1072 C<directory> is a required attribute.
1074 If you wish for DBIx::Class::Fixtures to clear the database for you pass in
1075 C<dll> (path to a DDL sql file) and C<connection_details> (array ref of DSN,
1078 If you wish to deal with cleaning the schema yourself, then pass in a C<schema>
1079 attribute containing the connected schema you wish to operate on and set the
1080 C<no_deploy> attribute.
1087 DBIx::Class::Exception->throw('first arg to populate must be hash ref')
1088 unless ref $params eq 'HASH';
1090 DBIx::Class::Exception->throw('directory param not specified')
1091 unless $params->{directory};
1093 my $fixture_dir = dir(delete $params->{directory});
1094 DBIx::Class::Exception->throw("fixture directory '$fixture_dir' does not exist")
1095 unless -d $fixture_dir;
1100 if ($params->{ddl} && $params->{connection_details}) {
1101 $ddl_file = file(delete $params->{ddl});
1102 unless (-e $ddl_file) {
1103 return DBIx::Class::Exception->throw('DDL does not exist at ' . $ddl_file);
1105 unless (ref $params->{connection_details} eq 'ARRAY') {
1106 return DBIx::Class::Exception->throw('connection details must be an arrayref');
1108 $schema = $self->_generate_schema({
1110 connection_details => delete $params->{connection_details},
1113 } elsif ($params->{schema} && $params->{no_deploy}) {
1114 $schema = $params->{schema};
1116 DBIx::Class::Exception->throw('you must set the ddl and connection_details params');
1120 return 1 if $params->{no_populate};
1122 $self->msg("\nimporting fixtures");
1123 my $tmp_fixture_dir = dir($fixture_dir, "-~populate~-" . $<);
1124 my $version_file = file($fixture_dir, '_dumper_version');
1125 # DBIx::Class::Exception->throw('no version file found');
1126 # unless -e $version_file;
1128 if (-e $tmp_fixture_dir) {
1129 $self->msg("- deleting existing temp directory $tmp_fixture_dir");
1130 $tmp_fixture_dir->rmtree;
1132 $self->msg("- creating temp dir");
1133 $tmp_fixture_dir->mkpath();
1134 for ( map { $schema->source($_)->from } $schema->sources) {
1135 my $from_dir = $fixture_dir->subdir($_);
1136 next unless -e $from_dir;
1137 dircopy($from_dir, $tmp_fixture_dir->subdir($_) );
1140 unless (-d $tmp_fixture_dir) {
1141 DBIx::Class::Exception->throw("Unable to create temporary fixtures dir: $tmp_fixture_dir: $!");
1145 my $formatter = $schema->storage->datetime_parser;
1146 unless ($@ || !$formatter) {
1148 if ($params->{datetime_relative_to}) {
1149 $callbacks{'DateTime::Duration'} = sub {
1150 $params->{datetime_relative_to}->clone->add_duration($_);
1153 $callbacks{'DateTime::Duration'} = sub {
1154 $formatter->format_datetime(DateTime->today->add_duration($_))
1157 $callbacks{object} ||= "visit_ref";
1158 $fixup_visitor = new Data::Visitor::Callback(%callbacks);
1161 $schema->storage->txn_do(sub {
1162 $schema->storage->with_deferred_fk_checks(sub {
1163 foreach my $source (sort $schema->sources) {
1164 $self->msg("- adding " . $source);
1165 my $rs = $schema->resultset($source);
1166 my $source_dir = $tmp_fixture_dir->subdir( lc $rs->result_source->from );
1167 next unless (-e $source_dir);
1169 while (my $file = $source_dir->next) {
1170 next unless ($file =~ /\.fix$/);
1171 next if $file->is_dir;
1172 my $contents = $file->slurp;
1175 $HASH1 = $fixup_visitor->visit($HASH1) if $fixup_visitor;
1176 if ( $params->{use_create} ) {
1177 $rs->create( $HASH1 );
1179 push(@rows, $HASH1);
1182 $rs->populate(\@rows) if scalar(@rows);
1186 $self->do_post_ddl( {
1188 post_ddl=>$params->{post_ddl}
1189 } ) if $params->{post_ddl};
1191 $self->msg("- fixtures imported");
1192 $self->msg("- cleaning up");
1193 $tmp_fixture_dir->rmtree;
1198 my ($self, $params) = @_;
1200 my $schema = $params->{schema};
1201 my $data = _read_sql($params->{post_ddl});
1203 eval { $schema->storage->dbh->do($_) or warn "SQL was:\n $_"};
1204 if ($@ && !$self->{ignore_sql_errors}) { die "SQL was:\n $_\n$@"; }
1206 $self->msg("- finished importing post-populate DDL into DB");
1211 my $subject = shift || return;
1212 my $level = shift || 1;
1213 return unless $self->debug >= $level;
1215 print Dumper($subject);
1217 print $subject . "\n";
1223 Luke Saunders <luke@shadowcatsystems.co.uk>
1225 Initial development sponsored by and (c) Takkle, Inc. 2007
1229 Ash Berlin <ash@shadowcatsystems.co.uk>
1231 Matt S. Trout <mst@shadowcatsystems.co.uk>
1233 Drew Taylor <taylor.andrew.j@gmail.com>
1235 Frank Switalski <fswitalski@gmail.com>
1239 This library is free software under the same license as perl itself