[Dbix-class] Re: Useful patch to DBIx::Class::Schema::Loader

Ben Tilly btilly at gmail.com
Sat May 28 00:26:50 GMT 2011


For some reason the sent patch did not include the unit test.  Also,
being named ".patch", it got scrubbed.

I'm resending it with a filename to let it know that it is in fact
plain text.  And this version of the patch includes the unit test
changes.

Sorry.

On Thu, May 26, 2011 at 8:35 PM, Ben Tilly <btilly at gmail.com> wrote:
> I'm looking for feedback on a patch to DBIx::Class::Schema::Loader.
> If people like it, I would like to see this added to the next release.
>
> The patch adds a relationship_name_map argument to
> DBIx::Class::Schema::Loader::Base to give people control over what
> names will be given to relationships. =A0A comment in the source
> suggests that this was supposed to be implemented, but that never
> seems to have happened.
>
> As implemented, this argument can be a coderef or a hashref. =A0If it is
> a hashref, you can do things like:
>
> =A0{
> =A0 =A0 =A0bar =3D> "baz",
> =A0 =A0 =A0Foo =3D> {
> =A0 =A0 =A0 =A0 =A0bar =3D> "blat",
> =A0 =A0 =A0},
> =A0}
>
> which will cause relationships that would have been called bar to be
> called baz, except in class Foo where it will be called blat instead.
>
> If a coderef you are given the moniker for the class being built, the
> name of the relationship accessor, and then a hashref with more
> detailed information.
>
> This patch includes documentation and unit tests.
>
-------------- next part --------------
diff -ru DBIx-Class-Schema-Loader.old/blib/lib/DBIx/Class/Schema/Loader/Bas=
e.pm DBIx-Class-Schema-Loader/blib/lib/DBIx/Class/Schema/Loader/Base.pm
--- DBIx-Class-Schema-Loader.old/blib/lib/DBIx/Class/Schema/Loader/Base.pm	=
2011-05-25 06:14:57.000000000 -0700
+++ DBIx-Class-Schema-Loader/blib/lib/DBIx/Class/Schema/Loader/Base.pm	2011=
-05-25 10:32:09.000000000 -0700
@@ -87,6 +87,7 @@
                                 preserve_case
                                 col_collision_map
                                 rel_collision_map
+                                relationship_name_map
                                 real_dump_directory
                                 result_component_map
                                 datetime_undef_if_invalid
@@ -327,6 +328,43 @@
       column_info     =3D> hashref of column info (data_type, is_nullable,=
 etc),
     }
 =

+=3Dhead2 relationship_name_map
+
+Similar in idea to moniker_map, but different in the details.  It can be
+a hashref or a code ref.
+
+If it is a hashref, keys can be either the default relationship name, or t=
he
+moniker.  The keys that are the default relationship name should map to the
+name of the key.  Keys that are monikers should map to hashes mapping
+relationship names to their translation.  You can do both at once, and the
+more specific moniker version will be picked up first.  So, for instance,
+you could have
+
+    {
+        bar =3D> "baz",
+        Foo =3D> {
+            bar =3D> "blat",
+        },
+    }
+
+and relationships that would have been named C<bar> will now be named C<ba=
z>
+except that in the table whose moniker is C<Foo> it will be named C<blat>.
+
+If it is a coderef, the arguments passed will be
+
+    name of the DBIC class we are building,
+    default relationship name that DBICSL would ordinarily give this colum=
n,
+    {
+        name         =3D> default relationship name,
+        type         =3D> the relationship type eg: C<has_many>,
+        this_moniker =3D> name of the DBIC class we are building,
+        this_columns =3D> columns in this table in the relationship,
+        that_moniker =3D> name of the DBIC class we are related to,
+        that_columns =3D> columns in the other table in the relationship,
+    }
+
+DBICSL will try to use the value returned as the relationship name.
+
 =3Dhead2 inflect_plural
 =

 Just like L</moniker_map> above (can be hash/code-ref, falls back to defau=
lt
@@ -1703,8 +1741,7 @@
     }
 }
 =

-# use the same logic to run moniker_map, col_accessor_map, and
-# relationship_name_map
+# use the same logic to run moniker_map and col_accessor_map
 sub _run_user_map {
     my ( $self, $map, $default_code, $ident, @extra ) =3D @_;
 =

diff -ru DBIx-Class-Schema-Loader.old/blib/lib/DBIx/Class/Schema/Loader/Rel=
Builder.pm DBIx-Class-Schema-Loader/blib/lib/DBIx/Class/Schema/Loader/RelBu=
ilder.pm
--- DBIx-Class-Schema-Loader.old/blib/lib/DBIx/Class/Schema/Loader/RelBuild=
er.pm	2011-05-25 06:14:57.000000000 -0700
+++ DBIx-Class-Schema-Loader/blib/lib/DBIx/Class/Schema/Loader/RelBuilder.p=
m	2011-05-25 10:33:56.000000000 -0700
@@ -89,6 +89,7 @@
     inflect_singular
     relationship_attrs
     rel_collision_map
+    relationship_name_map
     _temp_classes
 /);
 =

@@ -105,13 +106,14 @@
     # are better documented in L<DBIx::Class::Schema::Loader::Base>.
 =

     my $self =3D {
-        base               =3D> $base,
-        schema             =3D> $base->schema,
-        inflect_plural     =3D> $base->inflect_plural,
-        inflect_singular   =3D> $base->inflect_singular,
-        relationship_attrs =3D> $base->relationship_attrs,
-        rel_collision_map  =3D> $base->rel_collision_map,
-        _temp_classes      =3D> [],
+        base                   =3D> $base,
+        schema                 =3D> $base->schema,
+        inflect_plural         =3D> $base->inflect_plural,
+        inflect_singular       =3D> $base->inflect_singular,
+        relationship_attrs     =3D> $base->relationship_attrs,
+        rel_collision_map      =3D> $base->rel_collision_map,
+        relationship_name_map  =3D> $base->relationship_name_map,
+        _temp_classes          =3D> [],
     };
 =

     weaken $self->{base}; #< don't leak
@@ -460,7 +462,57 @@
         }
     }
 =

-    return ( $local_relname, $remote_relname, $remote_method );
+    # And let the user remap these names
+    my $local_info =3D {
+        name         =3D> $local_relname,
+        type         =3D> $remote_method,
+        this_moniker =3D> $remote_moniker,
+        that_moniker =3D> $local_moniker,
+        this_columns =3D> $rel->{remote_columns},
+        that_columns =3D> $rel->{local_columns},
+    };
+
+    my $remote_info =3D {
+        name         =3D> $remote_relname,
+        type         =3D> 'belongs_to',
+        this_moniker =3D> $local_moniker,
+        that_moniker =3D> $remote_moniker,
+        this_columns =3D> $rel->{local_columns},
+        that_columns =3D> $rel->{remote_columns},
+    };
+
+    my $map =3D $self->relationship_name_map;
+    if (not defined($map)) {
+        # We don't try to map the map.
+    }
+    elsif ('HASH' eq ref($map)) {
+        for my $info ($local_info, $remote_info) {
+            my $name =3D $info->{name};
+            my $type =3D $info->{type};
+            if ($map->{$type} and 'HASH' eq ref($map->{$type})
+                and $map->{$type}{$name}
+            ) {
+                $info->{name} =3D $map->{$type}{$name};
+            }
+            elsif ($map->{$name} and not 'HASH' eq ref($map->{$name})) {
+                $info->{name} =3D $map->{$name};
+            }
+        }
+    }
+    elsif ('CODE' eq ref($map)) {
+        for my $info ($local_info, $remote_info) {
+            $info->{name} =3D $map->(@$info{'this_moniker', 'name'}, $info=
);
+        }
+    }
+    else {
+        warn <<"EOF";
+Skipping relationship_name_map '$map' because it is not a hashref or coder=
ef.
+See "relationship_name_map" in perldoc DBIx::Class::Schema::Loader::Base
+for the correct usage.
+EOF
+    }
+
+    return ( $local_info->{name}, $remote_info->{name}, $remote_method );
 }
 =

 sub cleanup {
diff -ru DBIx-Class-Schema-Loader.old/lib/DBIx/Class/Schema/Loader/Base.pm =
DBIx-Class-Schema-Loader/lib/DBIx/Class/Schema/Loader/Base.pm
--- DBIx-Class-Schema-Loader.old/lib/DBIx/Class/Schema/Loader/Base.pm	2011-=
05-25 06:14:57.000000000 -0700
+++ DBIx-Class-Schema-Loader/lib/DBIx/Class/Schema/Loader/Base.pm	2011-05-2=
5 11:16:49.000000000 -0700
@@ -87,6 +87,7 @@
                                 preserve_case
                                 col_collision_map
                                 rel_collision_map
+                                relationship_name_map
                                 real_dump_directory
                                 result_component_map
                                 datetime_undef_if_invalid
@@ -327,6 +328,43 @@
       column_info     =3D> hashref of column info (data_type, is_nullable,=
 etc),
     }
 =

+=3Dhead2 relationship_name_map
+
+Similar in idea to moniker_map, but different in the details.  It can be
+a hashref or a code ref.
+
+If it is a hashref, keys can be either the default relationship name, or t=
he
+moniker.  The keys that are the default relationship name should map to the
+name of the key.  Keys that are monikers should map to hashes mapping
+relationship names to their translation.  You can do both at once, and the
+more specific moniker version will be picked up first.  So, for instance,
+you could have
+
+    {
+        bar =3D> "baz",
+        Foo =3D> {
+            bar =3D> "blat",
+        },
+    }
+
+and relationships that would have been named C<bar> will now be named C<ba=
z>
+except that in the table whose moniker is C<Foo> it will be named C<blat>.
+
+If it is a coderef, the arguments passed will be
+
+    name of the DBIC class we are building,
+    default relationship name that DBICSL would ordinarily give this colum=
n,
+    {
+        name         =3D> default relationship name,
+        type         =3D> the relationship type eg: C<has_many>,
+        this_moniker =3D> name of the DBIC class we are building,
+        this_columns =3D> columns in this table in the relationship,
+        that_moniker =3D> name of the DBIC class we are related to,
+        that_columns =3D> columns in the other table in the relationship,
+    }
+
+DBICSL will try to use the value returned as the relationship name.
+
 =3Dhead2 inflect_plural
 =

 Just like L</moniker_map> above (can be hash/code-ref, falls back to defau=
lt
@@ -1703,8 +1741,7 @@
     }
 }
 =

-# use the same logic to run moniker_map, col_accessor_map, and
-# relationship_name_map
+# use the same logic to run moniker_map and col_accessor_map
 sub _run_user_map {
     my ( $self, $map, $default_code, $ident, @extra ) =3D @_;
 =

diff -ru DBIx-Class-Schema-Loader.old/lib/DBIx/Class/Schema/Loader/RelBuild=
er.pm DBIx-Class-Schema-Loader/lib/DBIx/Class/Schema/Loader/RelBuilder.pm
--- DBIx-Class-Schema-Loader.old/lib/DBIx/Class/Schema/Loader/RelBuilder.pm=
	2011-05-25 06:14:57.000000000 -0700
+++ DBIx-Class-Schema-Loader/lib/DBIx/Class/Schema/Loader/RelBuilder.pm	201=
1-05-25 11:16:59.000000000 -0700
@@ -89,6 +89,7 @@
     inflect_singular
     relationship_attrs
     rel_collision_map
+    relationship_name_map
     _temp_classes
 /);
 =

@@ -105,13 +106,14 @@
     # are better documented in L<DBIx::Class::Schema::Loader::Base>.
 =

     my $self =3D {
-        base               =3D> $base,
-        schema             =3D> $base->schema,
-        inflect_plural     =3D> $base->inflect_plural,
-        inflect_singular   =3D> $base->inflect_singular,
-        relationship_attrs =3D> $base->relationship_attrs,
-        rel_collision_map  =3D> $base->rel_collision_map,
-        _temp_classes      =3D> [],
+        base                   =3D> $base,
+        schema                 =3D> $base->schema,
+        inflect_plural         =3D> $base->inflect_plural,
+        inflect_singular       =3D> $base->inflect_singular,
+        relationship_attrs     =3D> $base->relationship_attrs,
+        rel_collision_map      =3D> $base->rel_collision_map,
+        relationship_name_map  =3D> $base->relationship_name_map,
+        _temp_classes          =3D> [],
     };
 =

     weaken $self->{base}; #< don't leak
@@ -460,7 +462,57 @@
         }
     }
 =

-    return ( $local_relname, $remote_relname, $remote_method );
+    # And let the user remap these names
+    my $local_info =3D {
+        name         =3D> $local_relname,
+        type         =3D> $remote_method,
+        this_moniker =3D> $remote_moniker,
+        that_moniker =3D> $local_moniker,
+        this_columns =3D> $rel->{remote_columns},
+        that_columns =3D> $rel->{local_columns},
+    };
+
+    my $remote_info =3D {
+        name         =3D> $remote_relname,
+        type         =3D> 'belongs_to',
+        this_moniker =3D> $local_moniker,
+        that_moniker =3D> $remote_moniker,
+        this_columns =3D> $rel->{local_columns},
+        that_columns =3D> $rel->{remote_columns},
+    };
+
+    my $map =3D $self->relationship_name_map;
+    if (not defined($map)) {
+        # With no map we have nothing to do.
+    }
+    elsif ('HASH' eq ref($map)) {
+        for my $info ($local_info, $remote_info) {
+            my $name =3D $info->{name};
+            my $moniker =3D $info->{this_moniker};
+            if ($map->{$moniker} and 'HASH' eq ref($map->{$moniker})
+                and $map->{$moniker}{$name}
+            ) {
+                $info->{name} =3D $map->{$moniker}{$name};
+            }
+            elsif ($map->{$name} and not 'HASH' eq ref($map->{$name})) {
+                $info->{name} =3D $map->{$name};
+            }
+        }
+    }
+    elsif ('CODE' eq ref($map)) {
+        for my $info ($local_info, $remote_info) {
+            $info->{name} =3D $map->(@$info{'this_moniker', 'name'}, $info=
);
+        }
+    }
+    else {
+        warn <<"EOF";
+Skipping relationship_name_map '$map' because it is not a hashref or coder=
ef.
+See "relationship_name_map" in perldoc DBIx::Class::Schema::Loader::Base
+for the correct usage.
+EOF
+    }
+
+    return ( $local_info->{name}, $remote_info->{name}, $remote_method );
 }
 =

 sub cleanup {
diff -ru DBIx-Class-Schema-Loader.old/t/45relationships.t DBIx-Class-Schema=
-Loader/t/45relationships.t
--- DBIx-Class-Schema-Loader.old/t/45relationships.t	2011-05-25 06:14:57.00=
0000000 -0700
+++ DBIx-Class-Schema-Loader/t/45relationships.t	2011-05-25 11:16:24.000000=
000 -0700
@@ -1,5 +1,5 @@
 use strict;
-use Test::More tests =3D> 6;
+use Test::More tests =3D> 14;
 use Test::Exception;
 use lib qw(t/lib);
 use make_dbictest_db;
@@ -16,6 +16,79 @@
     'skip_relationships blocks generation of fooref rel',
   );
 =

+# test hashref as relationship_name_map
+my $hash_relationship =3D schema_with(
+    relationship_name_map =3D> {
+        fooref =3D> "got_fooref",
+        bars   =3D> "ignored",
+        Foo    =3D> {
+            bars =3D> "got_bars",
+            fooref =3D> "ignored",
+        },
+    }
+);
+is( ref($hash_relationship->source('Foo')->relationship_info('got_bars')),
+    'HASH',
+    'single level hash in relationship_name_map picked up correctly'
+  );
+is( ref($hash_relationship->source('Bar')->relationship_info('got_fooref')=
),
+    'HASH',
+    'double level hash in relationship_name_map picked up correctly'
+  );
+
+# test coderef as relationship_name_map
+my $code_relationship =3D schema_with(
+    relationship_name_map =3D> sub {
+        my ($moniker, $name, $args) =3D @_;
+
+        if ($moniker eq 'Foo') {
+            is ($name, 'bars', 'correct second argument for Foo passed');
+            is_deeply(
+                $args,
+                {
+		    name         =3D> 'bars',
+		    type         =3D> 'has_many',
+		    this_moniker =3D> 'Foo',
+		    this_columns =3D> ['fooid'],
+		    that_moniker =3D> 'Bar',
+		    that_columns =3D> ['fooref'],
+		},
+		'correct args for Foo passed'
+              );
+	    return 'bars_caught';
+        }
+	elsif ($moniker =3D 'Bar') {
+            is ($name, 'fooref', 'correct second argument for Bar passed');
+            is_deeply(
+                $args,
+                {
+		    name         =3D> 'fooref',
+		    type         =3D> 'belongs_to',
+		    this_moniker =3D> 'Bar',
+		    this_columns =3D> ['fooref'],
+		    that_moniker =3D> 'Foo',
+		    that_columns =3D> ['fooid'],
+		},
+		'correct args for Foo passed'
+              );
+	=

+            return 'fooref_caught';
+	}
+	else {
+	    die "Bad moniker '$moniker'";
+	}
+    }
+  );
+is( ref($code_relationship->source('Foo')->relationship_info('bars_caught'=
)),
+    'HASH',
+    'relationship_name_map overrode local_info correctly'
+  );
+is( ref($code_relationship->source('Bar')->relationship_info('fooref_caugh=
t')),
+    'HASH',
+    'relationship_name_map overrode remote_info correctly'
+  );
+
+
 =

 # test relationship_attrs
 throws_ok {


More information about the DBIx-Class mailing list