pass link table details to rel_name_map for many_to_many bridges (RT#81091)
Dagfinn Ilmari Mannsåker [Sat, 10 Nov 2012 23:01:59 +0000 (23:01 +0000)]
If two tables have multiple many-to-many link tables between them, the
rel_name_map coderef needs to know which link table it's naming the rel
for.

Changes
lib/DBIx/Class/Schema/Loader/Base.pm
lib/DBIx/Class/Schema/Loader/RelBuilder.pm
t/46relationships_multi_m2m.t [new file with mode: 0644]
t/lib/make_dbictest_db_multi_m2m.pm [new file with mode: 0644]

diff --git a/Changes b/Changes
index 704f03e..a37e44d 100644 (file)
--- a/Changes
+++ b/Changes
@@ -8,6 +8,7 @@ Revision history for Perl extension DBIx::Class::Schema::Loader
           information_schema as information_schema does not work for readonly
           users
         - add rel_type param for relationship_attrs coderef
+        - pass link table details to rel_name_map for many_to_many bridges (RT#81091)
 
 0.07033  2012-09-09 16:11:47
         - more thoroughly document the new behavior for relationship attributes
index 54ebe6b..2d3e3bb 100644 (file)
@@ -618,6 +618,10 @@ If it is a coderef, the argument passed will be a hashref of this form:
         remote_class   => name of the DBIC class we are related to,
         remote_moniker => moniker of the DBIC class we are related to,
         remote_columns => columns in the other table in the relationship,
+        # for type => "many_to_many" only:
+        link_class     => name of the DBIC class for the link table
+        link_moniker   => moniker of the DBIC class for the link table
+        link_rel_name  => name of the relationship to the link table
     }
 
 DBICSL will try to use the value returned as the relationship name.
index fac03d0..4134309 100644 (file)
@@ -544,11 +544,13 @@ sub _generate_m2ms {
 
         my @class2_to_cols = apply { s/^foreign\.//i } keys %{ $rels->[0]{args}[2] };
 
+        my $link_moniker = $rels->[0]{extra}{local_moniker};
+
         my @link_table_cols =
-            @{[ $self->schema->source($rels->[0]{extra}{local_moniker})->columns ]};
+            @{[ $self->schema->source($link_moniker)->columns ]};
 
         my @link_table_primary_cols =
-            @{[ $self->schema->source($rels->[0]{extra}{local_moniker})->primary_columns ]};
+            @{[ $self->schema->source($link_moniker)->primary_columns ]};
 
         next unless @class1_link_cols + @class2_link_cols == @link_table_cols
             && @link_table_cols == @link_table_primary_cols;
@@ -562,6 +564,11 @@ sub _generate_m2ms {
             $class2,
             $class1_remote_moniker,
             \@class1_to_cols,
+            {
+                link_class => $class,
+                link_moniker => $link_moniker,
+                link_rel_name => $class1_to_link_table_rel_name,
+            },
         );
 
         $class1_to_class2_relname = $self->_resolve_relname_collision(
@@ -579,6 +586,11 @@ sub _generate_m2ms {
             $class2,
             $class2_remote_moniker,
             \@class2_to_cols,
+            {
+                link_class => $class,
+                link_moniker => $link_moniker,
+                link_rel_name => $class2_to_link_table_rel_name,
+            },
         );
 
         $class2_to_class1_relname = $self->_resolve_relname_collision(
@@ -909,9 +921,10 @@ sub _relnames_and_method {
 
 sub _rel_name_map {
     my ($self, $relname, $method, $local_class, $local_moniker, $local_cols,
-        $remote_class, $remote_moniker, $remote_cols) = @_;
+        $remote_class, $remote_moniker, $remote_cols, $extra) = @_;
 
     my $info = {
+        %{$extra || {}},
         name           => $relname,
         type           => $method,
         local_class    => $local_class,
diff --git a/t/46relationships_multi_m2m.t b/t/46relationships_multi_m2m.t
new file mode 100644 (file)
index 0000000..3f1773c
--- /dev/null
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+use Test::More;
+use lib qw(t/lib);
+use make_dbictest_db_multi_m2m;
+use Devel::Dwarn;
+
+use DBIx::Class::Schema::Loader;
+
+my $schema_counter = 0;
+
+{
+    my $hashmap = schema_with(
+        rel_name_map => {
+            foos_2s => "other_foos",
+            bars_2s => "other_bars",
+        },
+    );
+
+    foreach ([qw(Foo bars)], [qw(Bar foos)]) {
+        my ($source, $rel) = @{$_};
+        my $row = $hashmap->resultset($source)->find(1);
+        foreach my $link ("", "other_") {
+            can_ok $row, "${link}${rel}";
+        }
+    }
+}
+{
+    my $submap = schema_with(
+        rel_name_map => sub {
+            my ($args) = @_;
+            if ($args->{type} eq "many_to_many") {
+                like $args->{link_class},
+                    qr/\ADBICTest::Schema::${schema_counter}::Result::FooBar(?:One|Two)\z/,
+                    "link_class";
+                like $args->{link_moniker}, qr/\AFooBar(?:One|Two)\z/,
+                    "link_moniker";
+                like $args->{link_rel_name}, qr/\Afoo_bar_(?:ones|twoes)\z/,
+                    "link_rel_name";
+
+                return $args->{name}."_".(split /_/, $args->{link_rel_name})[-1];
+            }
+        },
+    );
+    foreach ([qw(Foo bars)], [qw(Bar foos)]) {
+        my ($source, $rel) = @{$_};
+        my $row = $submap->resultset($source)->find(1);
+        foreach ([ones => 1], [twoes => 2]) {
+            my ($link, $count) = @{$_};
+            my $m2m = "${rel}_${link}";
+            can_ok $row, $m2m;
+            is $row->$m2m->count, $count, "$m2m count";
+        }
+    }
+}
+
+done_testing;
+
+#### generates a new schema with the given opts every time it's called
+sub schema_with {
+    $schema_counter++;
+    DBIx::Class::Schema::Loader::make_schema_at(
+            'DBICTest::Schema::'.$schema_counter,
+            { naming => 'current', @_ },
+            [ $make_dbictest_db_multi_m2m::dsn ],
+    );
+    "DBICTest::Schema::$schema_counter"->clone;
+}
diff --git a/t/lib/make_dbictest_db_multi_m2m.pm b/t/lib/make_dbictest_db_multi_m2m.pm
new file mode 100644 (file)
index 0000000..6951876
--- /dev/null
@@ -0,0 +1,43 @@
+package make_dbictest_db_multi_m2m;
+
+use strict;
+use warnings;
+use DBI;
+use dbixcsl_test_dir qw/$tdir/;
+
+eval { require DBD::SQLite };
+my $class = $@ ? 'SQLite2' : 'SQLite';
+
+my $fn = "$tdir/dbictest_multi_m2m.db";
+
+unlink($fn);
+our $dsn = "dbi:$class:dbname=$fn";
+my $dbh = DBI->connect($dsn);
+$dbh->do('PRAGMA SYNCHRONOUS = OFF');
+
+$dbh->do($_) for (
+    q|CREATE TABLE foo (
+        foo_id INTEGER PRIMARY KEY
+      )|,
+    q|CREATE TABLE bar (
+        bar_id INTEGER PRIMARY KEY
+      )|,
+    q|CREATE TABLE foo_bar_one (
+        foo_id INTEGER NOT NULL REFERENCES foo(foo_id),
+        bar_id INTEGER NOT NULL REFERENCES bar(bar_id),
+        PRIMARY KEY (foo_id, bar_id)
+      )|,
+    q|CREATE TABLE foo_bar_two (
+        foo_id INTEGER NOT NULL REFERENCES foo(foo_id),
+        bar_id INTEGER NOT NULL REFERENCES bar(bar_id),
+        PRIMARY KEY (foo_id, bar_id)
+      )|,
+    q|INSERT INTO FOO (foo_id) VALUES (1), (2)|,
+    q|INSERT INTO BAR (bar_id) VALUES (1), (2)|,
+    q|INSERT INTO foo_bar_one (foo_id, bar_id) VALUES (1,1),(2,2)|,
+    q|INSERT INTO foo_bar_two (foo_id, bar_id) VALUES (1,1),(1,2),(2,1),(2,2)|,
+);
+
+END { unlink($fn) unless $ENV{SCHEMA_LOADER_TESTS_NOCLEANUP}; }
+
+1;