Merge 'trunk' into 'DBIx-Class-current'
Matt S Trout [Fri, 12 May 2006 14:16:48 +0000 (14:16 +0000)]
r5900@cain (orig r1613):  jguenther | 2006-05-11 19:20:59 +0000
Added a couple examples to the cookbook
r5901@cain (orig r1614):  jguenther | 2006-05-11 21:53:25 +0000
Fixed cookbook example to actually work

r5902@cain (orig r1615):  matthewt | 2006-05-12 00:56:54 +0000
performance fix for cascade_update
r5903@cain (orig r1616):  matthewt | 2006-05-12 01:04:37 +0000
fixup to gen-schema.pl
r5904@cain (orig r1617):  matthewt | 2006-05-12 02:17:18 +0000
fixup for stringify that can be false in find_or_create_related

15 files changed:
Changes
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/Relationship/CascadeActions.pm
lib/DBIx/Class/ResultSet.pm
maint/gen-schema.pl
maint/gen-tests.pl
t/basicrels/26might_have.t [new file with mode: 0644]
t/helperrels/26might_have.t [new file with mode: 0644]
t/lib/DBICTest/Schema.pm
t/lib/DBICTest/Schema/Bookmark.pm [new file with mode: 0644]
t/lib/DBICTest/Schema/Link.pm [new file with mode: 0644]
t/lib/DBICTest/Setup.pm
t/run/01core.tl
t/run/26might_have.tl [new file with mode: 0644]

diff --git a/Changes b/Changes
index e94364d..6b40d18 100644 (file)
--- a/Changes
+++ b/Changes
@@ -17,6 +17,8 @@ Revision history for DBIx::Class
           ColumnCase is loaded
 
 0.06003
+        - make find_or_create_related check defined() instead of truth
+        - don't unnecessarily fetch rels for cascade_update
         - don't set_columns explicitly in update_or_create; instead use
           update($hashref) so InflateColumn works
         - fix for has_many prefetch with 0 related rows
index 3518c29..e82426e 100644 (file)
@@ -731,4 +731,59 @@ You can accomplish this by overriding C<insert>:
 where C<fill_from_artist> is a method you specify in C<CD> which sets
 values in C<CD> based on the data in the C<Artist> object you pass in.
 
+=head2 Debugging DBIx::Class objects with Data::Dumper
+
+L<Data::Dumper> can be a very useful tool for debugging, but sometimes it can
+be hard to find the pertinent data in all the data it can generate.
+Specifically, if one naively tries to use it like so,
+
+  use Data::Dumper;
+
+  my $cd = $schema->resultset('CD')->find(1);
+  print Dumper($cd);
+
+several pages worth of data from the CD object's schema and result source will
+be dumped to the screen. Since usually one is only interested in a few column
+values of the object, this is not very helpful.
+
+Luckily, it is possible to modify the data before L<Data::Dumper> outputs
+it. Simply define a hook that L<Data::Dumper> will call on the object before
+dumping it. For example,
+
+  package My::DB::CD;
+
+  sub _dumper_hook {
+    $_[0] = bless {
+      %{ $_[0] },
+      result_source => undef,
+    }, ref($_[0]);
+  }
+
+  [...]
+
+  use Data::Dumper;
+
+  $Data::Dumper::Freezer = '_dumper_hook';
+
+  my $cd = $schema->resultset('CD')->find(1);
+  print Dumper($cd);
+         # dumps $cd without its ResultSource
+
+If the structure of your schema is such that there is a common base class for
+all your table classes, simply put a method similar to C<_dumper_hook> in the
+base class and set C<$Data::Dumper::Freezer> to its name and L<Data::Dumper>
+will automagically clean up your data before printing it. See
+L<Data::Dumper/EXAMPLES> for more information.
+
+=head2 Retrieving a row object's Schema
+
+It is possible to get a Schema object from a row object like so,
+
+  my $schema = $cd->result_source->schema;
+  my $artist_rs = $schema->resultset('Artist');
+           # for example
+
+This can be useful when you don't want to pass around a Schema object to every
+method.
+
 =cut
index bfe63b3..0401c0a 100644 (file)
@@ -293,7 +293,8 @@ L<DBIx::Class::ResultSet/find_or_create> for details.
 
 sub find_or_create_related {
   my $self = shift;
-  return $self->find_related(@_) || $self->create_related(@_);
+  my $obj = $self->find_related(@_);
+  return (defined($obj) ? $obj : $self->create_related(@_));
 }
 
 =head2 update_or_create_related
index aa88043..3d5da76 100644 (file)
@@ -33,6 +33,10 @@ sub update {
   my %rels = map { $_ => $source->relationship_info($_) } $source->relationships;
   my @cascade = grep { $rels{$_}{attrs}{cascade_update} } keys %rels;
   foreach my $rel (@cascade) {
+    next if (
+      $rels{$rel}{attrs}{accessor} eq 'single'
+      && !exists($self->{_relationship_data}{$rel})
+    );
     $_->update for grep defined, $self->$rel;
   }
   return $ret;
index 5b2473a..d6f0dd2 100644 (file)
@@ -464,6 +464,10 @@ sub cursor {
 Inflates the first result without creating a cursor if the resultset has
 any records in it; if not returns nothing. Used by L</find> as an optimisation.
 
+Can optionally take an additional condition *only* - this is a fast-code-path
+method; if you need to add extra joins or similar call ->search and then
+->single without a condition on the $rs returned from that.
+
 =cut
 
 sub single {
@@ -1588,6 +1592,83 @@ C<prefetch> can be used with the following relationship types: C<belongs_to>,
 C<has_one> (or if you're using C<add_relationship>, any relationship declared
 with an accessor type of 'single' or 'filter').
 
+=head2 page
+
+=over 4
+
+=item Value: $page
+
+=back
+
+Makes the resultset paged and specifies the page to retrieve. Effectively
+identical to creating a non-pages resultset and then calling ->page($page)
+on it.
+
+=head2 rows
+
+=over 4
+
+=item Value: $rows
+
+=back
+
+Specifes the maximum number of rows for direct retrieval or the number of
+rows per page if the page attribute or method is used.
+
+=head2 group_by
+
+=over 4
+
+=item Value: \@columns
+
+=back
+
+A arrayref of columns to group by. Can include columns of joined tables.
+
+  group_by => [qw/ column1 column2 ... /]
+
+=head2 having
+
+=over 4
+
+=item Value: $condition
+
+=back
+
+HAVING is a select statement attribute that is applied between GROUP BY and
+ORDER BY. It is applied to the after the grouping calculations have been
+done. 
+
+  having => { 'count(employee)' => { '>=', 100 } }
+
+=head2 distinct
+
+=over 4
+
+=item Value: (0 | 1)
+
+=back
+
+Set to 1 to group by all columns.
+
+=head2 cache
+
+Set to 1 to cache search results. This prevents extra SQL queries if you
+revisit rows in your ResultSet:
+
+  my $resultset = $schema->resultset('Artist')->search( undef, { cache => 1 } );
+  
+  while( my $artist = $resultset->next ) {
+    ... do stuff ...
+  }
+
+  $rs->first; # without cache, this would issue a query
+
+By default, searches are not cached.
+
+For more examples of using these attributes, see
+L<DBIx::Class::Manual::Cookbook>.
+
 =head2 from
 
 =over 4
@@ -1601,21 +1682,35 @@ statements generated by L<DBIx::Class>, allowing you to express custom C<JOIN>
 clauses.
 
 NOTE: Use this on your own risk.  This allows you to shoot off your foot!
+
 C<join> will usually do what you need and it is strongly recommended that you
 avoid using C<from> unless you cannot achieve the desired result using C<join>.
+And we really do mean "cannot", not just tried and failed. Attempting to use
+this because you're having problems with C<join> is like trying to use x86
+ASM because you've got a syntax error in your C. Trust us on this.
+
+Now, if you're still really, really sure you need to use this (and if you're
+not 100% sure, ask the mailing list first), here's an explanation of how this
+works.
 
-In simple terms, C<from> works as follows:
+The syntax is as follows -
 
+  [
+    { <alias1> => <table1> },
     [
-        { <alias> => <table>, -join_type => 'inner|left|right' }
-        [] # nested JOIN (optional)
-        { <table.column> => <foreign_table.foreign_key> }
-    ]
+      { <alias2> => <table2>, -join_type => 'inner|left|right' },
+      [], # nested JOIN (optional)
+      { <table1.column1> => <table2.column2>, ... (more conditions) },
+    ],
+    # More of the above [ ] may follow for additional joins
+  ]
 
-    JOIN
-        <alias> <table>
-        [JOIN ...]
-    ON <table.column> = <foreign_table.foreign_key>
+  <table1> <alias1>
+  JOIN
+    <table2> <alias2>
+    [JOIN ...]
+  ON <table1.column1> = <table2.column2>
+  <more joins may follow>
 
 An easy way to follow the examples below is to remember the following:
 
@@ -1681,83 +1776,6 @@ with a father in the person table, we could explicitly use C<INNER JOIN>:
     # SELECT child.* FROM person child
     # INNER JOIN person father ON child.father_id = father.id
 
-=head2 page
-
-=over 4
-
-=item Value: $page
-
-=back
-
-Makes the resultset paged and specifies the page to retrieve. Effectively
-identical to creating a non-pages resultset and then calling ->page($page)
-on it.
-
-=head2 rows
-
-=over 4
-
-=item Value: $rows
-
-=back
-
-Specifes the maximum number of rows for direct retrieval or the number of
-rows per page if the page attribute or method is used.
-
-=head2 group_by
-
-=over 4
-
-=item Value: \@columns
-
-=back
-
-A arrayref of columns to group by. Can include columns of joined tables.
-
-  group_by => [qw/ column1 column2 ... /]
-
-=head2 having
-
-=over 4
-
-=item Value: $condition
-
-=back
-
-HAVING is a select statement attribute that is applied between GROUP BY and
-ORDER BY. It is applied to the after the grouping calculations have been
-done. 
-
-  having => { 'count(employee)' => { '>=', 100 } }
-
-=head2 distinct
-
-=over 4
-
-=item Value: (0 | 1)
-
-=back
-
-Set to 1 to group by all columns.
-
-=head2 cache
-
-Set to 1 to cache search results. This prevents extra SQL queries if you
-revisit rows in your ResultSet:
-
-  my $resultset = $schema->resultset('Artist')->search( undef, { cache => 1 } );
-  
-  while( my $artist = $resultset->next ) {
-    ... do stuff ...
-  }
-
-  $rs->first; # without cache, this would issue a query
-
-By default, searches are not cached.
-
-For more examples of using these attributes, see
-L<DBIx::Class::Manual::Cookbook>.
-
 =cut
 
 1;
index d8d2ca1..ffd2df7 100755 (executable)
@@ -4,9 +4,8 @@ use strict;
 use warnings;
 use lib qw(lib t/lib);
 
-use DBICTest;
-use DBICTest::Schema::HelperRels;
+use DBICTest::Schema;
 
-my $schema = DBICTest->initialise;
+my $schema = DBICTest::Schema->connect;
 
-print $schema->storage->deployment_statements($schema);
+print $schema->storage->deployment_statements($schema, 'SQLite');
index 0fc6180..48e71a7 100755 (executable)
@@ -22,4 +22,4 @@ run_tests(DBICTest->schema);
 EOF
     close $fh;
     }
-}
\ No newline at end of file
+}
diff --git a/t/basicrels/26might_have.t b/t/basicrels/26might_have.t
new file mode 100644 (file)
index 0000000..f2942e4
--- /dev/null
@@ -0,0 +1,7 @@
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBICTest::BasicRels;
+
+require "t/run/26might_have.tl";
+run_tests(DBICTest->schema);
diff --git a/t/helperrels/26might_have.t b/t/helperrels/26might_have.t
new file mode 100644 (file)
index 0000000..d3ec615
--- /dev/null
@@ -0,0 +1,7 @@
+use Test::More;
+use lib qw(t/lib);
+use DBICTest;
+use DBICTest::HelperRels;
+
+require "t/run/26might_have.tl";
+run_tests(DBICTest->schema);
index e882ee7..d69abc0 100644 (file)
@@ -9,6 +9,8 @@ __PACKAGE__->load_classes(qw/
   Artist
   Employee
   CD
+  Link
+  Bookmark
   #dummy
   Track
   Tag
diff --git a/t/lib/DBICTest/Schema/Bookmark.pm b/t/lib/DBICTest/Schema/Bookmark.pm
new file mode 100644 (file)
index 0000000..4f9ec44
--- /dev/null
@@ -0,0 +1,26 @@
+package # hide from PAUSE
+    DBICTest::Schema::Bookmark;
+
+    use base 'DBIx::Class::Core';
+
+
+use strict;
+use warnings;
+
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('bookmark');
+__PACKAGE__->add_columns(qw/id link/);
+__PACKAGE__->add_columns(
+    'id' => {
+        data_type => 'integer',
+        is_auto_increment => 1
+    },
+    'link' => {
+        data_type => 'integer',
+    },
+);
+
+__PACKAGE__->set_primary_key('id');
+__PACKAGE__->belongs_to(link => 'DBICTest::Schema::Link' );
+
+1;
diff --git a/t/lib/DBICTest/Schema/Link.pm b/t/lib/DBICTest/Schema/Link.pm
new file mode 100644 (file)
index 0000000..72574ea
--- /dev/null
@@ -0,0 +1,31 @@
+package # hide from PAUSE
+    DBICTest::Schema::Link;
+
+use base 'DBIx::Class::Core';
+
+use strict;
+use warnings;
+
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('link');
+__PACKAGE__->add_columns(
+    'id' => {
+        data_type => 'integer',
+        is_auto_increment => 1
+    },
+    'url' => {
+        data_type => 'varchar',
+        size      => 100,
+        is_nullable => 1,
+    },
+    'title' => {
+        data_type => 'varchar',
+        size      => 100,
+        is_nullable => 1,
+    },
+);
+__PACKAGE__->set_primary_key('id');
+
+use overload '""' => sub { shift->url }, fallback=> 1;
+
+1;
index 8bdd756..b493cb6 100755 (executable)
@@ -137,4 +137,14 @@ $schema->populate('Track', [
   [ 18, 1, 3, "Beehind You"],
 ]);
 
+$schema->populate('Link', [
+  [ qw/id title/ ],
+  [ 1, 'aaa' ]
+]);
+
+$schema->populate('Bookmark', [
+  [ qw/id link/ ],
+  [ 1, 1 ]
+]);
+
 1;
index 5287124..05e4dd3 100644 (file)
@@ -1,7 +1,7 @@
 sub run_tests {
 my $schema = shift;
 
-plan tests => 57;
+plan tests => 58;
 
 # figure out if we've got a version of sqlite that is older than 3.2.6, in
 # which case COUNT(DISTINCT()) doesn't work
@@ -237,6 +237,14 @@ ok($schema->storage(), 'Storage available');
   cmp_ok(@artsn, '==', 4, "Four artists returned");
 }
 
+my $newbook = $schema->resultset( 'Bookmark' )->find(1);
+
+$@ = '';
+eval {
+my $newlink = $newbook->link;
+};
+ok(!$@, "stringify to false value doesn't cause error");
+
 # test cascade_delete through many_to_many relations
 {
   my $art_del = $schema->resultset("Artist")->find({ artistid => 1 });
diff --git a/t/run/26might_have.tl b/t/run/26might_have.tl
new file mode 100644 (file)
index 0000000..a1c534e
--- /dev/null
@@ -0,0 +1,43 @@
+sub run_tests {
+my $schema = shift;
+
+my $queries;
+#$schema->storage->debugfh(IO::File->new('t/var/temp.trace', 'w'));
+$schema->storage->debugcb( sub{ $queries++ } );
+
+eval "use DBD::SQLite";
+plan skip_all => 'needs DBD::SQLite for testing' if $@;
+plan tests => 2;
+
+
+my $cd = $schema->resultset("CD")->find(1);
+$cd->title('test');
+
+# SELECT count
+$queries = 0;
+$schema->storage->debug(1);
+
+$cd->update;
+
+is($queries, 1, 'liner_notes (might_have) not prefetched - do not load 
+liner_notes on update');
+
+$schema->storage->debug(0);
+
+
+my $cd2 = $schema->resultset("CD")->find(2, {prefetch => 'liner_notes'});
+$cd2->title('test');
+
+# SELECT count
+$queries = 0;
+$schema->storage->debug(1);
+
+$cd2->update;
+
+is($queries, 1, 'liner_notes (might_have) prefetched - do not load 
+liner_notes on update');
+
+$schema->storage->debug(0);
+}
+
+1;