Merge 'DBIx-Class-current' into 'bulk_create'
Matt S Trout [Mon, 28 May 2007 21:57:08 +0000 (21:57 +0000)]
r51652@cain (orig r3338):  matthewt | 2007-05-20 00:05:23 +0000
find/next change to return undef rather than () on fail from Bernhard Graf
r51653@cain (orig r3339):  matthewt | 2007-05-20 00:09:33 +0000
deprecation warning for compose_connection now caught and tested
r51654@cain (orig r3340):  ilmari | 2007-05-20 00:10:20 +0000
add tests for nested prefetch with many_to_many and chained search_related
r51667@cain (orig r3341):  matthewt | 2007-05-21 14:22:52 +0000
fixed WhereJoins to handle conditions edge cases
r51760@cain (orig r3352):  groditi | 2007-05-21 22:29:11 +0000
result_class is getting leaked somewhere in related_resultset, failing test included
r51762@cain (orig r3354):  claco | 2007-05-22 00:32:35 +0000
added get_inflated_columns to Row

r51764@cain (orig r3356):  blblack | 2007-05-22 01:40:14 +0000
 r30913@brandon-blacks-computer (orig r3263):  matthewt | 2007-05-06 11:52:36 -0500
 patch from soulchild (thanks!)
 r31140@brandon-blacks-computer (orig r3318):  castaway | 2007-05-17 07:56:49 -0500
 Applied patch from Pedro Melo to fix order of components in the example

 r31938@brandon-blacks-computer (orig r3348):  ilmari | 2007-05-21 15:23:36 -0500
 Copy the working mk_hash from HashRefInflator in -current into Cookbook

 r31946@brandon-blacks-computer (orig r3355):  blblack | 2007-05-21 20:21:42 -0500
 connect_info should return the same data it was given

r51765@cain (orig r3357):  blblack | 2007-05-22 01:44:57 +0000
cleanup on aisle 7
r51766@cain (orig r3358):  claco | 2007-05-22 02:36:28 +0000
%colinfo accessor and inflate_column now work together

r51772@cain (orig r3364):  groditi | 2007-05-22 16:24:15 +0000
fix to t/97result_class.t and fixed tests too
r51820@cain (orig r3383):  matthewt | 2007-05-25 16:00:52 +0000
prefetch/cache fixes for all but find

lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Row.pm
t/101populate_rs.t [new file with mode: 0644]
t/96file_column.t
t/96multi_create.t [new file with mode: 0644]

index f31e685..1fbcf52 100644 (file)
@@ -324,12 +324,16 @@ sub update_or_create_related {
 =head2 set_from_related
 
   $book->set_from_related('author', $author_obj);
+  $book->author($author_obj);                      ## same thing
 
 Set column values on the current object, using related values from the given
 related object. This is used to associate previously separate objects, for
 example, to set the correct author for a book, find the Author object, then
 call set_from_related on the book.
 
+This is called internally when you pass existing objects as values to
+L<DBIx::Class::ResultSet/create>, or pass an object to a belongs_to acessor.
+
 The columns are only set in the local copy of the object, call L</update> to
 set them in the storage.
 
index 64d71f9..111e038 100644 (file)
@@ -34,7 +34,7 @@ In the examples below, the following table classes are used:
 
   package MyApp::Schema::Artist;
   use base qw/DBIx::Class/;
-  __PACKAGE__->load_components(qw/Core/);
+  __PACKAGE__->load_components(qw/Core/)
   __PACKAGE__->table('artist');
   __PACKAGE__->add_columns(qw/artistid name/);
   __PACKAGE__->set_primary_key('artistid');
@@ -350,11 +350,13 @@ sub find {
 
   my (%related, $info);
 
-  foreach my $key (keys %$input_query) {
+  KEY: foreach my $key (keys %$input_query) {
     if (ref($input_query->{$key})
         && ($info = $self->result_source->relationship_info($key))) {
+      my $val = delete $input_query->{$key};
+      next KEY if (ref($val) eq 'ARRAY'); # has_many for multi_create
       my $rel_q = $self->result_source->resolve_condition(
-                    $info->{cond}, delete $input_query->{$key}, $key
+                    $info->{cond}, $val, $key
                   );
       die "Can't handle OR join condition in find" if ref($rel_q) eq 'ARRAY';
       @related{keys %$rel_q} = values %$rel_q;
@@ -1238,6 +1240,135 @@ sub delete_all {
   return 1;
 }
 
+=head2 populate
+
+=over 4
+
+=item Arguments: $source_name, \@data;
+
+=back
+
+Pass an arrayref of hashrefs. Each hashref should be a structure suitable for
+submitting to a $resultset->create(...) method.
+
+In void context, C<insert_bulk> in L<DBIx::Class::Storage::DBI> is used
+to insert the data, as this is a faster method.
+
+Otherwise, each set of data is inserted into the database using
+L<DBIx::Class::ResultSet/create>, and a arrayref of the resulting row
+objects is returned.
+
+Example:  Assuming an Artist Class that has many CDs Classes relating:
+
+  my $Artist_rs = $schema->resultset("Artist");
+  
+  ## Void Context Example 
+  $Artist_rs->populate([
+     { artistid => 4, name => 'Manufactured Crap', cds => [ 
+        { title => 'My First CD', year => 2006 },
+        { title => 'Yet More Tweeny-Pop crap', year => 2007 },
+      ],
+     },
+     { artistid => 5, name => 'Angsty-Whiny Girl', cds => [
+        { title => 'My parents sold me to a record company' ,year => 2005 },
+        { title => 'Why Am I So Ugly?', year => 2006 },
+        { title => 'I Got Surgery and am now Popular', year => 2007 }
+      ],
+     },
+  ]);
+  
+  ## Array Context Example
+  my ($ArtistOne, $ArtistTwo, $ArtistThree) = $Artist_rs->populate([
+    { name => "Artist One"},
+       { name => "Artist Two"},
+       { name => "Artist Three", cds=> [
+         { title => "First CD", year => 2007},
+         { title => "Second CD", year => 2008},
+       ]}
+  ]);
+  
+  print $ArtistOne->name; ## response is 'Artist One'
+  print $ArtistThree->cds->count ## reponse is '2'
+
+=cut
+use Data::Dump qw/dump/;
+sub populate {
+  my ($self, $data) = @_;
+  
+  if(defined wantarray) {
+    my @created;
+    foreach my $item (@$data) {
+      push(@created, $self->create($item));
+    }
+    return @created;
+  } else {
+    my ($first, @rest) = @$data;
+
+       my @names = grep {!ref $first->{$_}} keys %$first;
+    my @rels = grep { $self->result_source->has_relationship($_) } keys %$first;
+    my @pks = $self->result_source->primary_columns;   
+
+       ## do the belongs_to relationships      
+    foreach my $index (0..$#{@$data})
+       {
+               foreach my $rel (@rels)
+               {
+                       next unless $data->[$index]->{$rel} && ref $data->[$index]->{$rel} eq "HASH";
+                       
+                       my $result = $self->related_resultset($rel)->create($data->[$index]->{$rel});
+                       
+                       my ($reverse) = keys %{$self->result_source->reverse_relationship_info($rel)};
+                       
+                       my $related = $result->result_source->resolve_condition(
+
+                               $result->result_source->relationship_info($reverse)->{cond},
+                               $self,                          
+                               $result,                                
+                       );
+
+                       delete $data->[$index]->{$rel};
+                       $data->[$index] = {%{$data->[$index]}, %$related};
+                       
+                       push @names, keys %$related if $index == 0;
+               }
+       }
+       
+    my @values = map {
+      [ map {
+         defined $_ ? $_ : $self->throw_exception("Undefined value for column!")
+      } @$_{@names} ]
+    } @$data;
+
+    $self->result_source->storage->insert_bulk(
+      $self->result_source, 
+      \@names, 
+      \@values,
+    );
+
+       ## do the has_many relationships
+    foreach my $item (@$data) {
+
+      foreach my $rel (@rels) {
+        next unless $item->{$rel} && ref $item->{$rel} eq "ARRAY";
+
+        my $parent = $self->find(map {{$_=>$item->{$_}} } @pks) || next;
+        my $child = $parent->$rel;
+               
+        my $related = $child->result_source->resolve_condition(
+          $parent->result_source->relationship_info($rel)->{cond},
+          $child,
+          $parent,
+        );
+
+        my @rows_to_add = ref $item->{$rel} eq 'ARRAY' ? @{$item->{$rel}} : ($item->{$rel});
+        my @populate = map { {%$_, %$related} } @rows_to_add;
+
+        $child->populate( \@populate );
+      }
+    }
+  }
+}
+
 =head2 pager
 
 =over 4
index e4d30e9..9a2e061 100644 (file)
@@ -771,6 +771,8 @@ sub resolve_condition {
         #warn %ret;
       } elsif (!defined $for) { # undef, i.e. "no object"
         $ret{$k} = undef;
+      } elsif (ref $as eq 'HASH') { # reverse hashref
+        $ret{$v} = $as->{$k};
       } elsif (ref $as) { # reverse object
         $ret{$v} = $as->get_column($k);
       } elsif (!defined $as) { # undef, i.e. "no reverse object"
index 2165801..19b1a76 100644 (file)
@@ -5,6 +5,7 @@ use warnings;
 
 use base qw/DBIx::Class/;
 use Carp::Clan qw/^DBIx::Class/;
+use Scalar::Util ();
 
 __PACKAGE__->mk_group_accessors('simple' => qw/_source_handle/);
 
@@ -27,8 +28,21 @@ derived from L<DBIx::Class::ResultSource> objects.
 
 Creates a new row object from column => value mappings passed as a hash ref
 
+Passing an object, or an arrayref of objects as a value will call
+L<DBIx::Class::Relationship::Base/set_from_related> for you. When
+passed a hashref or an arrayref of hashrefs as the value, these will
+be turned into objects via new_related, and treated as if you had
+passed objects.
+
 =cut
 
+## It needs to store the new objects somewhere, and call insert on that list later when insert is called on this object. We may need an accessor for these so the user can retrieve them, if just doing ->new().
+## This only works because DBIC doesnt yet care to check whether the new_related objects have been passed all their mandatory columns
+## When doing the later insert, we need to make sure the PKs are set.
+## using _relationship_data in new and funky ways..
+## check Relationship::CascadeActions and Relationship::Accessor for compat
+## tests!
+
 sub new {
   my ($class, $attrs) = @_;
   $class = ref $class if ref $class;
@@ -48,23 +62,54 @@ sub new {
       unless ref($attrs) eq 'HASH';
     
     my ($related,$inflated);
+    ## Pretend all the rels are actual objects, unset below if not, for insert() to fix
+    $new->{_rel_in_storage} = 1;
+
     foreach my $key (keys %$attrs) {
       if (ref $attrs->{$key}) {
+        ## Can we extract this lot to use with update(_or .. ) ?
         my $info = $class->relationship_info($key);
         if ($info && $info->{attrs}{accessor}
           && $info->{attrs}{accessor} eq 'single')
         {
-          $new->set_from_related($key, $attrs->{$key});        
-          $related->{$key} = $attrs->{$key};
+          my $rel_obj = delete $attrs->{$key};
+          if(!Scalar::Util::blessed($rel_obj)) {
+            $rel_obj = $new->find_or_new_related($key, $rel_obj);
+            $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
+          }
+          $new->set_from_related($key, $rel_obj);        
+          $related->{$key} = $rel_obj;
           next;
-        }
-        elsif ($class->has_column($key)
-          && exists $class->column_info($key)->{_inflate_info})
+        } elsif ($info && $info->{attrs}{accessor}
+            && $info->{attrs}{accessor} eq 'multi'
+            && ref $attrs->{$key} eq 'ARRAY') {
+          my $others = delete $attrs->{$key};
+          foreach my $rel_obj (@$others) {
+            if(!Scalar::Util::blessed($rel_obj)) {
+              $rel_obj = $new->new_related($key, $rel_obj);
+              $new->{_rel_in_storage} = 0;
+            }
+          }
+          $related->{$key} = $others;
+          next;
+        } elsif ($info && $info->{attrs}{accessor}
+          && $info->{attrs}{accessor} eq 'filter')
         {
+          ## 'filter' should disappear and get merged in with 'single' above!
+          my $rel_obj = delete $attrs->{$key};
+          if(!Scalar::Util::blessed($rel_obj)) {
+            $rel_obj = $new->find_or_new_related($key, $rel_obj);
+            $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
+          }
+          $inflated->{$key} = $rel_obj;
+          next;
+        } elsif ($class->has_column($key)
+            && $class->column_info($key)->{_inflate_info}) {
           $inflated->{$key} = $attrs->{$key};
           next;
         }
       }
+      use Data::Dumper;
       $new->throw_exception("No such column $key on $class")
         unless $class->has_column($key);
       $new->store_column($key => $attrs->{$key});          
@@ -98,7 +143,60 @@ sub insert {
   $self->throw_exception("No result_source set on this object; can't insert")
     unless $source;
 
+  # Check if we stored uninserted relobjs here in new()
+  my %related_stuff = (%{$self->{_relationship_data} || {}}, 
+                       %{$self->{_inflated_column} || {}});
+  if(!$self->{_rel_in_storage})
+  {
+    $source->storage->txn_begin;
+
+    ## Should all be in relationship_data, but we need to get rid of the
+    ## 'filter' reltype..
+    ## These are the FK rels, need their IDs for the insert.
+    foreach my $relname (keys %related_stuff) {
+      my $rel_obj = $related_stuff{$relname};
+      if(Scalar::Util::blessed($rel_obj) && $rel_obj->isa('DBIx::Class::Row')) {
+        $rel_obj->insert();
+        $self->set_from_related($relname, $rel_obj);
+      }
+    }
+  }
+
   $source->storage->insert($source, { $self->get_columns });
+
+  ## PK::Auto
+  my ($pri, $too_many) = grep { !defined $self->get_column($_) || 
+                                    ref($self->get_column($_)) eq 'SCALAR'} $self->primary_columns;
+  if(defined $pri) {
+    $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self )
+      if defined $too_many;
+
+    my $storage = $self->result_source->storage;
+    $self->throw_exception( "Missing primary key but Storage doesn't support last_insert_id" )
+      unless $storage->can('last_insert_id');
+    my $id = $storage->last_insert_id($self->result_source,$pri);
+    $self->throw_exception( "Can't get last insert id" ) unless $id;
+    $self->store_column($pri => $id);
+  }
+
+  if(!$self->{_rel_in_storage})
+  {
+    ## Now do the has_many rels, that need $selfs ID.
+    foreach my $relname (keys %related_stuff) {
+      my $relobj = $related_stuff{$relname};
+      if(ref $relobj eq 'ARRAY') {
+        foreach my $obj (@$relobj) {
+          my $info = $self->relationship_info($relname);
+          ## What about multi-col FKs ?
+          my $key = $1 if($info && (keys %{$info->{cond}})[0] =~ /^foreign\.(\w+)/);
+          $obj->set_from_related($key, $self);
+          $obj->insert() if(!$obj->in_storage);
+        }
+      }
+    }
+    $source->storage->txn_commit;
+  }
+
   $self->in_storage(1);
   $self->{_dirty_columns} = {};
   $self->{related_resultsets} = {};
@@ -152,7 +250,19 @@ sub update {
           my $rel = delete $upd->{$key};
           $self->set_from_related($key => $rel);
           $self->{_relationship_data}{$key} = $rel;          
-        } 
+        } elsif ($info && $info->{attrs}{accessor}
+            && $info->{attrs}{accessor} eq 'multi'
+            && ref $upd->{$key} eq 'ARRAY') {
+            my $others = delete $upd->{$key};
+            foreach my $rel_obj (@$others) {
+              if(!Scalar::Util::blessed($rel_obj)) {
+                $rel_obj = $self->create_related($key, $rel_obj);
+              }
+            }
+            $self->{_relationship_data}{$key} = $others; 
+#            $related->{$key} = $others;
+            next;
+        }
         elsif ($self->has_column($key)
           && exists $self->column_info($key)->{_inflate_info})
         {
diff --git a/t/101populate_rs.t b/t/101populate_rs.t
new file mode 100644 (file)
index 0000000..aedff87
--- /dev/null
@@ -0,0 +1,527 @@
+## ----------------------------------------------------------------------------
+## Tests for the $resultset->populate method.
+##
+## GOALS:  We need to test the method for both void and array context for all
+## the following relationship types: belongs_to, has_many.  Additionally we
+## need to each each of those for both specified PK's and autogenerated PK's
+##
+## Also need to test some stuff that should generate errors.
+## ----------------------------------------------------------------------------
+
+use strict;
+use warnings;
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+
+plan tests => 98;
+
+
+## ----------------------------------------------------------------------------
+## Get a Schema and some ResultSets we can play with.
+## ----------------------------------------------------------------------------
+
+my $schema     = DBICTest->init_schema();
+my $art_rs     = $schema->resultset('Artist');
+my $cd_rs      = $schema->resultset('CD');
+
+ok( $schema, 'Got a Schema object');
+ok( $art_rs, 'Got Good Artist Resultset');
+ok( $cd_rs, 'Got Good CD Resultset');
+
+
+## ----------------------------------------------------------------------------
+## Array context tests
+## ----------------------------------------------------------------------------
+
+ARRAY_CONTEXT: {
+
+       ## These first set of tests are cake because array context just delegates
+       ## all it's processing to $resultset->create
+       
+       HAS_MANY_NO_PKS: {
+       
+               ## This first group of tests checks to make sure we can call populate
+               ## with the parent having many children and let the keys be automatic
+
+               my $artists = [
+                       {       
+                               name => 'Angsty-Whiny Girl',
+                               cds => [
+                                       { title => 'My First CD', year => 2006 },
+                                       { title => 'Yet More Tweeny-Pop crap', year => 2007 },
+                               ],                                      
+                       },              
+                       {
+                               name => 'Manufactured Crap',
+                       },
+                       {
+                               name => 'Like I Give a Damn',
+                               cds => [
+                                       { title => 'My parents sold me to a record company' ,year => 2005 },
+                                       { title => 'Why Am I So Ugly?', year => 2006 },
+                                       { title => 'I Got Surgery and am now Popular', year => 2007 }                           
+                               ],
+                       },
+                       {       
+                               name => 'Formerly Named',
+                               cds => [
+                                       { title => 'One Hit Wonder', year => 2006 },
+                               ],                                      
+                       },                      
+               ];
+               
+               ## Get the result row objects.
+               
+               my ($girl, $crap, $damn, $formerly) = $art_rs->populate($artists);
+               
+               ## Do we have the right object?
+               
+               isa_ok( $crap, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $girl, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $damn, 'DBICTest::Artist', "Got 'Artist'");     
+               isa_ok( $formerly, 'DBICTest::Artist', "Got 'Artist'"); 
+               
+               ## Find the expected information?
+
+               ok( $crap->name eq 'Manufactured Crap', "Got Correct name for result object");
+               ok( $girl->name eq 'Angsty-Whiny Girl', "Got Correct name for result object");
+               ok( $damn->name eq 'Like I Give a Damn', "Got Correct name for result object"); 
+               ok( $formerly->name eq 'Formerly Named', "Got Correct name for result object");
+               
+               ## Create the expected children sub objects?
+               
+               ok( $crap->cds->count == 0, "got Expected Number of Cds");
+               ok( $girl->cds->count == 2, "got Expected Number of Cds");      
+               ok( $damn->cds->count == 3, "got Expected Number of Cds");
+               ok( $formerly->cds->count == 1, "got Expected Number of Cds");
+
+               ## Did the cds get expected information?
+               
+               my ($cd1, $cd2) = $girl->cds->search({},{order_by=>'year ASC'});
+               
+               ok( $cd1->title eq "My First CD", "Got Expected CD Title");
+               ok( $cd2->title eq "Yet More Tweeny-Pop crap", "Got Expected CD Title");
+       }
+       
+       HAS_MANY_WITH_PKS: {
+       
+               ## This group tests the ability to specify the PK in the parent and let
+               ## DBIC transparently pass the PK down to the Child and also let's the
+               ## child create any other needed PK's for itself.
+               
+               my $aid         =  $art_rs->get_column('artistid')->max || 0;
+               
+               my $first_aid = ++$aid;
+               
+               my $artists = [
+                       {
+                               artistid => $first_aid,
+                               name => 'PK_Angsty-Whiny Girl',
+                               cds => [
+                                       { artist => $first_aid, title => 'PK_My First CD', year => 2006 },
+                                       { artist => $first_aid, title => 'PK_Yet More Tweeny-Pop crap', year => 2007 },
+                               ],                                      
+                       },              
+                       {
+                               artistid => ++$aid,
+                               name => 'PK_Manufactured Crap',
+                       },
+                       {
+                               artistid => ++$aid,
+                               name => 'PK_Like I Give a Damn',
+                               cds => [
+                                       { title => 'PK_My parents sold me to a record company' ,year => 2005 },
+                                       { title => 'PK_Why Am I So Ugly?', year => 2006 },
+                                       { title => 'PK_I Got Surgery and am now Popular', year => 2007 }                                
+                               ],
+                       },
+                       {
+                               artistid => ++$aid,
+                               name => 'PK_Formerly Named',
+                               cds => [
+                                       { title => 'PK_One Hit Wonder', year => 2006 },
+                               ],                                      
+                       },                      
+               ];
+               
+               ## Get the result row objects.
+               
+               my ($girl, $crap, $damn, $formerly) = $art_rs->populate($artists);
+               
+               ## Do we have the right object?
+               
+               isa_ok( $crap, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $girl, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $damn, 'DBICTest::Artist', "Got 'Artist'");     
+               isa_ok( $formerly, 'DBICTest::Artist', "Got 'Artist'"); 
+               
+               ## Find the expected information?
+
+               ok( $crap->name eq 'PK_Manufactured Crap', "Got Correct name for result object");
+               ok( $girl->name eq 'PK_Angsty-Whiny Girl', "Got Correct name for result object");
+               ok( $girl->artistid == $first_aid, "Got Correct artist PK for result object");          
+               ok( $damn->name eq 'PK_Like I Give a Damn', "Got Correct name for result object");      
+               ok( $formerly->name eq 'PK_Formerly Named', "Got Correct name for result object");
+               
+               ## Create the expected children sub objects?
+               
+               ok( $crap->cds->count == 0, "got Expected Number of Cds");
+               ok( $girl->cds->count == 2, "got Expected Number of Cds");      
+               ok( $damn->cds->count == 3, "got Expected Number of Cds");
+               ok( $formerly->cds->count == 1, "got Expected Number of Cds");
+
+               ## Did the cds get expected information?
+               
+               my ($cd1, $cd2) = $girl->cds->search({},{order_by=>'year ASC'});
+               
+               ok( $cd1->title eq "PK_My First CD", "Got Expected CD Title");
+               ok( $cd2->title eq "PK_Yet More Tweeny-Pop crap", "Got Expected CD Title");
+       }
+       
+       BELONGS_TO_NO_PKs: {
+
+               ## Test from a belongs_to perspective, should create artist first, 
+               ## then CD with artistid.  This test we let the system automatically
+               ## create the PK's.  Chances are good you'll use it this way mostly.
+               
+               my $cds = [
+                       {
+                               title => 'Some CD3',
+                               year => '1997',
+                               artist => { name => 'Fred BloggsC'},
+                       },
+                       {
+                               title => 'Some CD4',
+                               year => '1997',
+                               artist => { name => 'Fred BloggsD'},
+                       },              
+               ];
+               
+               my ($cdA, $cdB) = $cd_rs->populate($cds);
+               
+
+               isa_ok($cdA, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdA->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdA->artist->name, 'Fred BloggsC', 'Set Artist to FredC');
+
+               
+               isa_ok($cdB, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdB->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdB->artist->name, 'Fred BloggsD', 'Set Artist to FredD');
+       }
+
+       BELONGS_TO_WITH_PKs: {
+
+               ## Test from a belongs_to perspective, should create artist first, 
+               ## then CD with artistid.  This time we try setting the PK's
+               
+               my $aid = $art_rs->get_column('artistid')->max || 0;
+
+               my $cds = [
+                       {
+                               title => 'Some CD3',
+                               year => '1997',
+                               artist => { artistid=> ++$aid, name => 'Fred BloggsC'},
+                       },
+                       {
+                               title => 'Some CD4',
+                               year => '1997',
+                               artist => { artistid=> ++$aid, name => 'Fred BloggsD'},
+                       },              
+               ];
+               
+               my ($cdA, $cdB) = $cd_rs->populate($cds);
+               
+               isa_ok($cdA, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdA->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdA->artist->name, 'Fred BloggsC', 'Set Artist to FredC');
+               
+               isa_ok($cdB, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdB->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdB->artist->name, 'Fred BloggsD', 'Set Artist to FredD');
+               ok($cdB->artist->artistid == $aid, "Got Expected Artist ID");
+       }
+}
+
+
+## ----------------------------------------------------------------------------
+## Void context tests
+## ----------------------------------------------------------------------------
+
+VOID_CONTEXT: {
+
+       ## All these tests check the ability to use populate without asking for 
+       ## any returned resultsets.  This uses bulk_insert as much as possible
+       ## in order to increase speed.
+       
+       HAS_MANY_WITH_PKS: {
+       
+               ## This first group of tests checks to make sure we can call populate
+               ## with the parent having many children and the parent PK is set
+
+               my $aid         =  $art_rs->get_column('artistid')->max || 0;
+               
+               my $first_aid = ++$aid;
+               
+               my $artists = [
+                       {
+                               artistid => $first_aid,
+                               name => 'VOID_PK_Angsty-Whiny Girl',
+                               cds => [
+                                       { artist => $first_aid, title => 'VOID_PK_My First CD', year => 2006 },
+                                       { artist => $first_aid, title => 'VOID_PK_Yet More Tweeny-Pop crap', year => 2007 },
+                               ],                                      
+                       },              
+                       {
+                               artistid => ++$aid,
+                               name => 'VOID_PK_Manufactured Crap',
+                       },
+                       {
+                               artistid => ++$aid,
+                               name => 'VOID_PK_Like I Give a Damn',
+                               cds => [
+                                       { title => 'VOID_PK_My parents sold me to a record company' ,year => 2005 },
+                                       { title => 'VOID_PK_Why Am I So Ugly?', year => 2006 },
+                                       { title => 'VOID_PK_I Got Surgery and am now Popular', year => 2007 }                           
+                               ],
+                       },
+                       {
+                               artistid => ++$aid,
+                               name => 'VOID_PK_Formerly Named',
+                               cds => [
+                                       { title => 'VOID_PK_One Hit Wonder', year => 2006 },
+                               ],                                      
+                       },                      
+               ];
+               
+               ## Get the result row objects.
+               
+               $art_rs->populate($artists);
+               
+               my ($girl, $formerly, $damn, $crap) = $art_rs->search(
+                       {name=>[sort map {$_->{name}} @$artists]},
+                       {order_by=>'name ASC'},
+               );
+               
+               ## Do we have the right object?
+               
+               isa_ok( $crap, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $girl, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $damn, 'DBICTest::Artist', "Got 'Artist'");     
+               isa_ok( $formerly, 'DBICTest::Artist', "Got 'Artist'"); 
+               
+               ## Find the expected information?
+
+               ok( $crap->name eq 'VOID_PK_Manufactured Crap', "Got Correct name for result object");
+               ok( $girl->name eq 'VOID_PK_Angsty-Whiny Girl', "Got Correct name for result object");
+               ok( $damn->name eq 'VOID_PK_Like I Give a Damn', "Got Correct name for result object"); 
+               ok( $formerly->name eq 'VOID_PK_Formerly Named', "Got Correct name for result object");
+               
+               ## Create the expected children sub objects?
+               ok( $crap->can('cds'), "Has cds relationship");
+               ok( $girl->can('cds'), "Has cds relationship");
+               ok( $damn->can('cds'), "Has cds relationship");
+               ok( $formerly->can('cds'), "Has cds relationship");
+               
+               ok( $crap->cds->count == 0, "got Expected Number of Cds");
+               ok( $girl->cds->count == 2, "got Expected Number of Cds");      
+               ok( $damn->cds->count == 3, "got Expected Number of Cds");
+               ok( $formerly->cds->count == 1, "got Expected Number of Cds");
+
+               ## Did the cds get expected information?
+               
+               my ($cd1, $cd2) = $girl->cds->search({},{order_by=>'year ASC'});
+               
+               ok( $cd1->title eq "VOID_PK_My First CD", "Got Expected CD Title");
+               ok( $cd2->title eq "VOID_PK_Yet More Tweeny-Pop crap", "Got Expected CD Title");
+       }
+       
+       
+       BELONGS_TO_WITH_PKs: {
+
+               ## Test from a belongs_to perspective, should create artist first, 
+               ## then CD with artistid.  This time we try setting the PK's
+               
+               my $aid = $art_rs->get_column('artistid')->max || 0;
+
+               my $cds = [
+                       {
+                               title => 'Some CD3B',
+                               year => '1997',
+                               artist => { artistid=> ++$aid, name => 'Fred BloggsCB'},
+                       },
+                       {
+                               title => 'Some CD4B',
+                               year => '1997',
+                               artist => { artistid=> ++$aid, name => 'Fred BloggsDB'},
+                       },              
+               ];
+               
+               $cd_rs->populate($cds);
+               
+               my ($cdA, $cdB) = $cd_rs->search(
+                       {title=>[sort map {$_->{title}} @$cds]},
+                       {order_by=>'title ASC'},
+               );
+               
+               isa_ok($cdA, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdA->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdA->artist->name, 'Fred BloggsCB', 'Set Artist to FredCB');
+               
+               isa_ok($cdB, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdB->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdB->artist->name, 'Fred BloggsDB', 'Set Artist to FredDB');
+               ok($cdB->artist->artistid == $aid, "Got Expected Artist ID");
+       }
+
+       BELONGS_TO_NO_PKs: {
+
+               ## Test from a belongs_to perspective, should create artist first, 
+               ## then CD with artistid.
+               
+               diag("Starting Void Context BelongsTO with No PKs");
+               
+               my $cds = [
+                       {
+                               title => 'Some CD3BB',
+                               year => '1997',
+                               artist => { name => 'Fred BloggsCBB'},
+                       },
+                       {
+                               title => 'Some CD4BB',
+                               year => '1997',
+                               artist => { name => 'Fred BloggsDBB'},
+                       },              
+               ];
+               
+               $cd_rs->populate($cds);
+               
+               my ($cdA, $cdB) = $cd_rs->search(
+                       {title=>[sort map {$_->{title}} @$cds]},
+                       {order_by=>'title ASC'},
+               );
+               
+               isa_ok($cdA, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdA->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdA->title, 'Some CD3BB', 'Found Expected title');
+               is($cdA->artist->name, 'Fred BloggsCBB', 'Set Artist to FredCBB');
+               
+               isa_ok($cdB, 'DBICTest::CD', 'Created CD');
+               isa_ok($cdB->artist, 'DBICTest::Artist', 'Set Artist');
+               is($cdB->title, 'Some CD4BB', 'Found Expected title');
+               is($cdB->artist->name, 'Fred BloggsDBB', 'Set Artist to FredDBB');
+       }
+       
+       
+       HAS_MANY_NO_PKS: {
+       
+               ## This first group of tests checks to make sure we can call populate
+               ## with the parent having many children and let the keys be automatic
+               
+               diag("Starting Void Context Has Many with No PKs");
+
+               my $artists = [
+                       {       
+                               name => 'VOID_Angsty-Whiny Girl',
+                               cds => [
+                                       { title => 'VOID_My First CD', year => 2006 },
+                                       { title => 'VOID_Yet More Tweeny-Pop crap', year => 2007 },
+                               ],                                      
+                       },              
+                       {
+                               name => 'VOID_Manufactured Crap',
+                       },
+                       {
+                               name => 'VOID_Like I Give a Damn',
+                               cds => [
+                                       { title => 'VOID_My parents sold me to a record company' ,year => 2005 },
+                                       { title => 'VOID_Why Am I So Ugly?', year => 2006 },
+                                       { title => 'VOID_I Got Surgery and am now Popular', year => 2007 }                              
+                               ],
+                       },
+                       {       
+                               name => 'VOID_Formerly Named',
+                               cds => [
+                                       { title => 'VOID_One Hit Wonder', year => 2006 },
+                               ],                                      
+                       },                      
+               ];
+               
+               ## Get the result row objects.
+               
+               $art_rs->populate($artists);
+               
+               my ($girl, $formerly, $damn, $crap) = $art_rs->search(
+                       {name=>[sort map {$_->{name}} @$artists]},
+                       {order_by=>'name ASC'},
+               );
+               
+               ## Do we have the right object?
+               
+               isa_ok( $crap, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $girl, 'DBICTest::Artist', "Got 'Artist'");
+               isa_ok( $damn, 'DBICTest::Artist', "Got 'Artist'");     
+               isa_ok( $formerly, 'DBICTest::Artist', "Got 'Artist'"); 
+               
+               ## Find the expected information?
+
+               ok( $crap->name eq 'VOID_Manufactured Crap', "Got Correct name for result object");
+               ok( $girl->name eq 'VOID_Angsty-Whiny Girl', "Got Correct name for result object");
+               ok( $damn->name eq 'VOID_Like I Give a Damn', "Got Correct name for result object");    
+               ok( $formerly->name eq 'VOID_Formerly Named', "Got Correct name for result object");
+               
+               ## Create the expected children sub objects?
+               ok( $crap->can('cds'), "Has cds relationship");
+               ok( $girl->can('cds'), "Has cds relationship");
+               ok( $damn->can('cds'), "Has cds relationship");
+               ok( $formerly->can('cds'), "Has cds relationship");
+               
+               ok( $crap->cds->count == 0, "got Expected Number of Cds");
+               ok( $girl->cds->count == 2, "got Expected Number of Cds");      
+               ok( $damn->cds->count == 3, "got Expected Number of Cds");
+               ok( $formerly->cds->count == 1, "got Expected Number of Cds");
+
+               ## Did the cds get expected information?
+               
+               my ($cd1, $cd2) = $girl->cds->search({},{order_by=>'year ASC'});
+
+               ok($cd1, "Got a got CD");
+               ok($cd2, "Got a got CD");
+               
+               SKIP:{
+               
+                       skip "Can't Test CD because we failed to create it", 1 unless $cd1;
+                       ok( $cd1->title eq "VOID_My First CD", "Got Expected CD Title");
+               }
+               
+               SKIP:{
+               
+                       skip "Can't Test CD because we failed to create it", 1 unless $cd2;
+                       ok( $cd2->title eq "VOID_Yet More Tweeny-Pop crap", "Got Expected CD Title");
+               }
+       }
+
+}
+
+__END__
+## ----------------------------------------------------------------------------
+## Error cases
+## ----------------------------------------------------------------------------
+
+SHOULD_CAUSE_ERRORS: {
+
+       ## bad or missing PKs
+       ## changing columns
+       ## basically errors for non well formed data
+       ## check for the first incomplete problem
+       ## can we solve the problem of void context and no PKs?
+
+}
+
+
+
+
+
+
index 4773861..d32e373 100644 (file)
@@ -12,4 +12,4 @@ plan tests => 1;
 
 my $fh = new IO::File('t/96file_column.t','r');
 eval { $schema->resultset('FileColumn')->create({file => {handle => $fh, filename =>'96file_column.t'}})};
-ok(!$@,'FileColumn checking if file handled properly.');
+cmp_ok($@,'eq','','FileColumn checking if file handled properly.');
diff --git a/t/96multi_create.t b/t/96multi_create.t
new file mode 100644 (file)
index 0000000..ac5f219
--- /dev/null
@@ -0,0 +1,122 @@
+use strict;
+use warnings;
+
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DateTime;
+
+my $schema = DBICTest->init_schema();
+
+plan tests => 17;
+
+my $cd2 = $schema->resultset('CD')->create({ artist => 
+                                   { name => 'Fred Bloggs' },
+                                   title => 'Some CD',
+                                   year => 1996
+                                 });
+
+is(ref $cd2->artist, 'DBICTest::Artist', 'Created CD and Artist object');
+is($cd2->artist->name, 'Fred Bloggs', 'Artist created correctly');
+
+my $artist = $schema->resultset('Artist')->create({ name => 'Fred 2',
+                                                     cds => [
+                                                             { title => 'Music to code by',
+                                                               year => 2007,
+                                                             },
+                                                             ],
+                                                     });
+is(ref $artist->cds->first, 'DBICTest::CD', 'Created Artist with CDs');
+is($artist->cds->first->title, 'Music to code by', 'CD created correctly');
+
+# Add a new CD
+$artist->update({cds => [ $artist->cds, 
+                          { title => 'Yet another CD',
+                            year => 2006,
+                          },
+                        ],
+                });
+is(($artist->cds->search({}, { order_by => 'year' }))[0]->title, 'Yet another CD', 'Updated and added another CD');
+
+my $newartist = $schema->resultset('Artist')->find_or_create({ name => 'Fred 2'});
+
+is($newartist->name, 'Fred 2', 'Retrieved the artist');
+
+
+my $newartist2 = $schema->resultset('Artist')->find_or_create({ name => 'Fred 3',
+                                                                cds => [
+                                                                        { title => 'Noah Act',
+                                                                          year => 2007,
+                                                                        },
+                                                                       ],
+
+                                                              });
+
+is($newartist2->name, 'Fred 3', 'Created new artist with cds via find_or_create');
+
+
+CREATE_RELATED1 :{
+
+       my $artist = $schema->resultset('Artist')->first;
+       
+       my $cd_result = $artist->create_related('cds', {
+       
+               title => 'TestOneCD1',
+               year => 2007,
+               tracks => [
+               
+                       { position=>111,
+                         title => 'TrackOne',
+                       },
+                       { position=>112,
+                         title => 'TrackTwo',
+                       }
+               ],
+
+       });
+       
+       ok( $cd_result && ref $cd_result eq 'DBICTest::CD', "Got Good CD Class");
+       ok( $cd_result->title eq "TestOneCD1", "Got Expected Title");
+       
+       my $tracks = $cd_result->tracks;
+       
+       ok( ref $tracks eq "DBIx::Class::ResultSet", "Got Expected Tracks ResultSet");
+       
+       foreach my $track ($tracks->all)
+       {
+               ok( $track && ref $track eq 'DBICTest::Track', 'Got Expected Track Class');
+       }
+}
+
+CREATE_RELATED2 :{
+
+       my $artist = $schema->resultset('Artist')->first;
+       
+       my $cd_result = $artist->create_related('cds', {
+       
+               title => 'TestOneCD2',
+               year => 2007,
+               tracks => [
+               
+                       { position=>111,
+                         title => 'TrackOne',
+                       },
+                       { position=>112,
+                         title => 'TrackTwo',
+                       }
+               ],
+
+       });
+       
+       ok( $cd_result && ref $cd_result eq 'DBICTest::CD', "Got Good CD Class");
+       ok( $cd_result->title eq "TestOneCD2", "Got Expected Title");
+       
+       my $tracks = $cd_result->tracks;
+       
+       ok( ref $tracks eq "DBIx::Class::ResultSet", "Got Expected Tracks ResultSet");
+       
+       foreach my $track ($tracks->all)
+       {
+               ok( $track && ref $track eq 'DBICTest::Track', 'Got Expected Track Class');
+       }
+}