diff --git a/AUTHORS b/AUTHORS index 90f30335b..fca9f9066 100644 --- a/AUTHORS +++ b/AUTHORS @@ -110,6 +110,7 @@ jshirley: J. Shirley kaare: Kaare Rasmussen kd: Kieren Diment kentnl: Kent Fredric +KES777: Eugen Konkov kkane: Kevin L. Kane konobi: Scott McWhirter Lasse Makholm diff --git a/lib/DBIx/Class/Row.pm b/lib/DBIx/Class/Row.pm index a4a18b97f..e3727daba 100644 --- a/lib/DBIx/Class/Row.pm +++ b/lib/DBIx/Class/Row.pm @@ -176,6 +176,45 @@ sub __their_pk_needs_us { # this should maybe be in resultsource. return 0; } + +my $create_related_rows = sub { + my( $new, $key, $type, $others ) = @_; + + if( ref $others ne 'ARRAY' ) { + $others = [ $others ]; + #assert $type must be 'multi' + } + + my $total = @$others; + my @objects; + foreach my $idx ( 0 .. $#$others ) { + my $rel_obj = $others->[$idx]; + if( !blessed $rel_obj ) { + $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); + } + + if( $rel_obj->in_storage ) { + $type eq 'multi' + and $rel_obj->throw_exception( 'A multi relationship can not be pre-existing when doing multicreate. Something went wrong' ); + + $new->{_rel_in_storage}{$key} = 1; + $new->set_from_related( $key, $rel_obj ) if $type eq 'single'; + } else { + my $tmp = $type eq 'multi' ? "(${\($idx+1)} of $total)" : ''; + MULTICREATE_DEBUG + and print STDERR "MC $new uninserted $key $rel_obj$tmp\n"; + } + push( @objects, $rel_obj ); + } + + return $type eq 'multi' + ? \@objects + : shift @objects + ; +}; + + + sub new { my ($class, $attrs) = @_; $class = ref $class if ref $class; @@ -197,8 +236,7 @@ sub new { @{$new->{_ignore_at_insert}={}}{@$col_from_rel} = (); } - my ($related,$inflated); - + my( $postponed, $inflated ); foreach my $key (keys %$attrs) { if (ref $attrs->{$key} and ! is_literal_value($attrs->{$key}) ) { ## Can we extract this lot to use with update(_or .. ) ? @@ -206,56 +244,13 @@ sub new { unless $rsrc; my $info = $rsrc->relationship_info($key); my $acc_type = $info->{attrs}{accessor} || ''; - if ($acc_type eq 'single') { - my $rel_obj = delete $attrs->{$key}; - if(!blessed $rel_obj) { - $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); - } - - if ($rel_obj->in_storage) { - $new->{_rel_in_storage}{$key} = 1; - $new->set_from_related($key, $rel_obj); - } else { - MULTICREATE_DEBUG and print STDERR "MC $new uninserted $key $rel_obj\n"; - } - - $related->{$key} = $rel_obj; - next; - } - elsif ($acc_type eq 'multi' && ref $attrs->{$key} eq 'ARRAY' ) { - my $others = delete $attrs->{$key}; - my $total = @$others; - my @objects; - foreach my $idx (0 .. $#$others) { - my $rel_obj = $others->[$idx]; - if(!blessed $rel_obj) { - $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); - } - - if ($rel_obj->in_storage) { - $rel_obj->throw_exception ('A multi relationship can not be pre-existing when doing multicreate. Something went wrong'); - } else { - MULTICREATE_DEBUG and - print STDERR "MC $new uninserted $key $rel_obj (${\($idx+1)} of $total)\n"; - } - push(@objects, $rel_obj); - } - $related->{$key} = \@objects; - next; - } - elsif ($acc_type eq 'filter') { - ## 'filter' should disappear and get merged in with 'single' above! - my $rel_obj = delete $attrs->{$key}; - if(!blessed $rel_obj) { - $rel_obj = $new->__new_related_find_or_new_helper($key, $rel_obj); - } - if ($rel_obj->in_storage) { - $new->{_rel_in_storage}{$key} = 1; - } - else { - MULTICREATE_DEBUG and print STDERR "MC $new uninserted $key $rel_obj\n"; - } - $inflated->{$key} = $rel_obj; + if( $acc_type eq 'single' + || $acc_type eq 'multi' && ref $attrs->{$key} eq 'ARRAY' + || $acc_type eq 'filter' + ) { + # We can add related (children) row *ONLY AFTER* main (parent) row is created!!! + # So we postpone creation (see below) + $postponed->{$key} = [ $acc_type, delete $attrs->{$key} ]; next; } elsif ( @@ -269,6 +264,18 @@ sub new { } $new->store_column($key => $attrs->{$key}); } + # After main (master) row's columns are stored (new row is created) + # we can add related (children) rows + + my $related; + foreach my $key ( keys %$postponed ) { + if( $postponed->{$key}[0] ne 'filter' ) { + $related->{$key} = $create_related_rows->( $new, $key, $postponed->{$key}[0], $postponed->{$key}[1] ); + } + else { + $inflated->{$key} = $create_related_rows->( $new, $key, $postponed->{$key}[0], $postponed->{$key}[1] ); + } + } $new->{_relationship_data} = $related if $related; $new->{_inflated_column} = $inflated if $inflated; diff --git a/t/multi_create/parent_children.t b/t/multi_create/parent_children.t new file mode 100644 index 000000000..f2caf62c3 --- /dev/null +++ b/t/multi_create/parent_children.t @@ -0,0 +1,48 @@ +use strict; +use warnings; + +use Test::More; +use Test::Exception; +use lib qw(t/lib); +use DBICTest; + +plan tests => 4; + +my $schema = DBICTest->init_schema(); + +lives_ok ( sub { + + my $artist_rs = $schema->resultset ('Artist'); + my $artist_count = $artist_rs->count(); + my $cd_rs = $schema->resultset ('CD'); + my $cd_count = $cd_rs->count(); + $artist_rs->create({ + name => 'parent child relation', + cds => [ {}, {} ], #CD's 'title' field are autofilled at CD::new + }); + + is ($artist_rs->count, $artist_count + 1, 'New artist was created'); + ok ($artist_rs->find ({name => 'parent child relation'}), 'Artist was created with correct name'); + + is ($cd_rs->count, $cd_count + 2, 'New cds were created'); + is ($cd_rs->search({name => 'parent child relation'})->count, 2, 'CDs were created with correct name'); + +}, 'Parent row must exist before child row is created'); + +1; + +package # hide from PAUSE + DBICTest::Schema::CD; + +sub new { + my ( $class, $attrs ) = @_; + +my $self = $class->next::method($attrs); + +$self->title( $self->artist->name ); + +return $self; +} + + +1;