Addition of a bunch of helper relationship methods
Matt S Trout [Sun, 18 Sep 2005 01:53:34 +0000 (01:53 +0000)]
lib/DBIx/Class/CDBICompat.pm
lib/DBIx/Class/CDBICompat/HasA.pm
lib/DBIx/Class/CDBICompat/HasMany.pm
lib/DBIx/Class/CDBICompat/MightHave.pm [deleted file]
lib/DBIx/Class/Exception.pm
lib/DBIx/Class/Relationship.pm
lib/DBIx/Class/Relationship/BelongsTo.pm [new file with mode: 0644]
lib/DBIx/Class/Relationship/HasMany.pm [new file with mode: 0644]
lib/DBIx/Class/Relationship/HasOne.pm
t/lib/DBICTest/Schema/HelperRels.pm
t/run/06relationship.tl

index c221a61..daf5822 100644 (file)
@@ -16,7 +16,6 @@ __PACKAGE__->load_own_components(qw/
   Constructor
   AccessorMapping
   ColumnCase
-  MightHave
   HasMany
   HasA
   LazyLoading
index b635429..3010954 100644 (file)
@@ -20,10 +20,10 @@ sub has_a {
     return 1;
   }
   my ($pri, $too_many) = keys %{ $f_class->_primaries };
-  $self->throw( "has_a only works with a single primary key; ${f_class} has more. try using a has_one relationship instead of Class::DBI compat rels" )
+  $self->throw( "has_a only works with a single primary key; ${f_class} has more. try using a belongs_to relationship instead of Class::DBI compat rels" )
     if $too_many;
 
-  $self->has_one($col, $f_class);
+  $self->belongs_to($col, $f_class);
   return 1;
 }
 
index acc7041..3f745f6 100644 (file)
@@ -12,40 +12,15 @@ sub has_many {
     ($f_class, @f_method) = @$f_class;
   }
 
-  my ($pri, $too_many) = keys %{ $class->_primaries };
-  $class->throw( "has_many only works with a single primary key; ${class} has more" )
-      if $too_many;
-  my $self_key = $pri;
-    
-  eval "require $f_class";
+  if (ref $f_key eq 'HASH' && !$args) { $args = $f_key; undef $f_key; };
 
-  if (ref $f_key eq 'HASH') { $args = $f_key; undef $f_key; };
-
-  #unless ($f_key) { Not selective enough. Removed pending fix.
-  #  ($f_rel) = grep { $_->{class} && $_->{class} eq $class }
-  #               $f_class->_relationships;
-  #}
-
-  unless ($f_key) {
-    #warn join(', ', %{ $f_class->_columns });
-    $class =~ /([^\:]+)$/;
-    #warn $1;
-    $f_key = lc $1 if $f_class->_columns->{lc $1};
+  $args ||= {};
+  if (delete $args->{no_cascade_delete}) {
+    $args->{cascade_delete} = 0;
   }
 
-  $class->throw( "Unable to resolve foreign key for has_many from ${class} to ${f_class}" )
-    unless $f_key;
-  $class->throw( "No such column ${f_key} on foreign class ${f_class}" )
-    unless $f_class->_columns->{$f_key};
-  $args ||= {};
-  my $cascade = not (ref $args eq 'HASH' && delete $args->{no_cascade_delete});
+  $class->NEXT::has_many($rel, $f_class, $f_key, $args);
 
- $class->add_relationship($rel, $f_class,
-                            { "foreign.${f_key}" => "self.${self_key}" },
-                            { accessor => 'multi',
-                              join_type => 'LEFT',
-                              ($cascade ? ('cascade_delete' => 1) : ()),
-                              %$args } );
   if (@f_method) {
     no strict 'refs';
     no warnings 'redefine';
diff --git a/lib/DBIx/Class/CDBICompat/MightHave.pm b/lib/DBIx/Class/CDBICompat/MightHave.pm
deleted file mode 100644 (file)
index 5cf073e..0000000
+++ /dev/null
@@ -1,22 +0,0 @@
-package DBIx::Class::CDBICompat::MightHave;
-
-use strict;
-use warnings;
-
-sub might_have {
-  my ($class, $rel, $f_class, @columns) = @_;
-  my ($pri, $too_many) = keys %{ $class->_primaries };
-  $class->throw( "might_have only works with a single primary key; ${class} has more" )
-    if $too_many;
-  my $f_pri;
-  ($f_pri, $too_many) = keys %{ $f_class->_primaries };
-  $class->throw( "might_have only works with a single primary key; ${f_class} has more" )
-    if $too_many;
-  $class->add_relationship($rel, $f_class,
-   { "foreign.${f_pri}" => "self.${pri}" },
-   { accessor => 'single', proxy => \@columns,
-     cascade_update => 1, cascade_delete => 1 });
-  1;
-}
-
-1;
index cfa7887..40426ad 100644 (file)
@@ -49,7 +49,7 @@ sub throw {
 
     my $message = $params{message} || $params{error} || $! || '';
 
-    local $Carp::CarpLevel = 1;
+    local $Carp::CarpLevel = (caller(1) eq 'NEXT' ? 2 : 1);
 
     Carp::croak($message);
 }
index 4b9d64f..d7d1675 100644 (file)
@@ -5,7 +5,15 @@ use warnings;
 
 use base qw/DBIx::Class Class::Data::Inheritable/;
 
-__PACKAGE__->load_own_components(qw/Accessor CascadeActions ProxyMethods Base HasOne/);
+__PACKAGE__->load_own_components(qw/
+  HasMany
+  HasOne
+  BelongsTo
+  Accessor
+  CascadeActions
+  ProxyMethods
+  Base
+/);
 
 __PACKAGE__->mk_classdata('_relationships', { } );
 
diff --git a/lib/DBIx/Class/Relationship/BelongsTo.pm b/lib/DBIx/Class/Relationship/BelongsTo.pm
new file mode 100644 (file)
index 0000000..4acb8fe
--- /dev/null
@@ -0,0 +1,42 @@
+package DBIx::Class::Relationship::BelongsTo;
+
+use strict;
+use warnings;
+
+sub belongs_to {
+  my ($class, $rel, $f_class, $cond, $attrs) = @_;
+  eval "require $f_class";
+  # single key relationship
+  if (not defined $cond) {
+    my ($pri, $too_many) = keys %{ $f_class->_primaries };
+    my $acc_type = ($class->_columns->{$rel}) ? 'filter' : 'single';
+    $class->add_relationship($rel, $f_class,
+      { "foreign.${pri}" => "self.${rel}" },
+      { accessor => $acc_type }
+    );
+  }
+  # multiple key relationship
+  else {
+    my %f_primaries = %{ $f_class->_primaries };
+    my $cond_rel;
+    for (keys %$cond) {
+      $cond_rel->{"foreign.$_"} = "self.".$cond->{$_};
+      # primary key usage checks
+      if (exists $f_primaries{$_}) {
+        delete $f_primaries{$_};
+      }
+      else
+      {
+        $class->throw("non primary key used in join condition: $_");
+      }
+    }
+    $class->throw("not all primary keys used in multi key relationship!") if keys %f_primaries;
+    $class->add_relationship($rel, $f_class,
+      $cond_rel,
+      { accessor => 'single', %{$attrs ||{}} }
+    );
+  }
+  return 1;
+}
+
+1;
diff --git a/lib/DBIx/Class/Relationship/HasMany.pm b/lib/DBIx/Class/Relationship/HasMany.pm
new file mode 100644 (file)
index 0000000..2efebb7
--- /dev/null
@@ -0,0 +1,36 @@
+package DBIx::Class::Relationship::HasMany;
+
+use strict;
+use warnings;
+
+sub has_many {
+  my ($class, $rel, $f_class, $cond, $attrs) = @_;
+    
+  eval "require $f_class";
+
+  if (!ref $cond) {
+    my $f_key;
+    if (defined $cond && length $cond) {
+      $f_key = $cond;
+      $class->throw( "No such column ${f_key} on foreign class ${f_class}" )
+        unless ($@ || $f_class->_columns->{$f_key});
+    } else {
+      $class =~ /([^\:]+)$/;
+      $f_key = lc $1 if $f_class->_columns->{lc $1};
+      $class->throw( "Unable to resolve foreign key for has_many from ${class} to ${f_class}" )
+        unless $f_key;
+    }
+    my ($pri, $too_many) = keys %{ $class->_primaries };
+    $class->throw( "has_many can only infer join for a single primary key; ${class} has more" )
+      if $too_many;
+    $cond = { "foreign.${f_key}" => "self.${pri}" },
+  }
+
+  $class->add_relationship($rel, $f_class, $cond,
+                            { accessor => 'multi',
+                              join_type => 'LEFT',
+                              cascade_delete => 1,
+                              %{$attrs||{}} } );
+}
+
+1;
index 6128f7a..ea97e7d 100644 (file)
@@ -3,40 +3,46 @@ package DBIx::Class::Relationship::HasOne;
 use strict;
 use warnings;
 
+sub might_have {
+  shift->_has_one('LEFT' => @_);
+}
+
 sub has_one {
-  my ($class, $acc_name, $f_class, $cond) = @_;
-  eval "require $f_class";
-  # single key relationship
-  if (not defined $cond) {
-    my ($pri, $too_many) = keys %{ $f_class->_primaries };
-    my $acc_type = ($class->_columns->{$acc_name}) ? 'filter' : 'single';
-    $class->add_relationship($acc_name, $f_class,
-      { "foreign.${pri}" => "self.${acc_name}" },
-      { accessor => $acc_type }
-    );
-  }
-  # multiple key relationship
-  else {
-    my %f_primaries = %{ $f_class->_primaries };
-    my $cond_rel;
-    for (keys %$cond) {
-      $cond_rel->{"foreign.$_"} = "self.".$cond->{$_};
-      # primary key usage checks
-      if (exists $f_primaries{$_}) {
-        delete $f_primaries{$_};
-      }
-      else
-      {
-        $class->throw("non primary key used in join condition: $_");
-      }
+  shift->_has_one(undef => @_);
+}
+
+sub _has_one {
+  my ($class, $join_type, $rel, $f_class, @columns) = @_;
+  my $cond;
+  if (ref $columns[0]) {
+    $cond = shift @columns;
+  } else {
+    my ($pri, $too_many) = keys %{ $class->_primaries };
+    $class->throw( "might_have/has_one can only infer join for a single primary key; ${class} has more" )
+      if $too_many;
+    my $f_key;
+    if ($f_class->_columns->{$rel}) {
+      $f_key = $rel;
+    } else {
+      ($f_key, $too_many) = keys %{ $f_class->_primaries };
+      $class->throw( "might_have/has_one can only infer join for a single primary key; ${f_class} has more" )
+        if $too_many;
     }
-    $class->throw("not all primary keys used in multi key relationship!") if keys %f_primaries;
-    $class->add_relationship($acc_name, $f_class,
-      $cond_rel,
-      { accessor => 'single' }
-    );
+    $cond = { "foreign.${f_key}" => "self.${pri}" },
+  }
+  shift(@columns) unless defined $columns[0]; # Explicit empty condition
+  my %attrs;
+  if (ref $columns[0] eq 'HASH') {
+    %attrs = %{shift @columns};
   }
-  return 1;
+  shift(@columns) unless defined $columns[0]; # Explicit empty attrs
+  $class->add_relationship($rel, $f_class,
+   $cond,
+   { accessor => 'single', (@columns ? (proxy => \@columns) : ()),
+     cascade_update => 1, cascade_delete => 1,
+     ($join_type ? ('join_type' => $join_type) : ()),
+     %attrs });
+  1;
 }
 
 1;
index 4c74bc4..590c99f 100644 (file)
@@ -2,79 +2,33 @@ package DBICTest::Schema::BasicRels;
 
 use base 'DBIx::Class::Core';
 
-DBICTest::Schema::Artist->add_relationship(
-    cds => 'DBICTest::Schema::CD',
-    { 'foreign.artist' => 'self.artistid' },
-    { order_by => 'year' }
-);
-DBICTest::Schema::Artist->add_relationship(
-    twokeys => 'DBICTest::Schema::TwoKeys',
-    { 'foreign.artist' => 'self.artistid' }
-);
-DBICTest::Schema::Artist->add_relationship(
-    onekeys => 'DBICTest::Schema::OneKey',
-    { 'foreign.artist' => 'self.artistid' }
-);
+DBICTest::Schema::Artist->has_many(cds => 'DBICTest::Schema::CD', undef,
+                                     { order_by => 'year' });
+DBICTest::Schema::Artist->has_many(twokeys => 'DBICTest::Schema::TwoKeys');
+DBICTest::Schema::Artist->has_many(onekeys => 'DBICTest::Schema::OneKey');
 
-DBICTest::Schema::CD->has_one('artist', 'DBICTest::Schema::Artist');
-#DBICTest::Schema::CD->add_relationship(
-#    artist => 'DBICTest::Schema::Artist',
-#    { 'foreign.artistid' => 'self.artist' },
-#);
-DBICTest::Schema::CD->add_relationship(
-    tracks => 'DBICTest::Schema::Track',
-    { 'foreign.cd' => 'self.cdid' }
-);
-DBICTest::Schema::CD->add_relationship(
-    tags => 'DBICTest::Schema::Tag',
-    { 'foreign.cd' => 'self.cdid' }
-);
-#DBICTest::Schema::CD->might_have(liner_notes => 'DBICTest::Schema::LinerNotes' => qw/notes/);
-DBICTest::Schema::CD->add_relationship(
-    liner_notes => 'DBICTest::Schema::LinerNotes',
-    { 'foreign.liner_id' => 'self.cdid' },
-    { join_type => 'LEFT' }
-);
+DBICTest::Schema::CD->belongs_to('artist', 'DBICTest::Schema::Artist');
 
-DBICTest::Schema::SelfRefAlias->add_relationship(
-    self_ref => 'DBICTest::Schema::SelfRef',
-    { 'foreign.id' => 'self.self_ref' },
-    { accessor     => 'single' }
+DBICTest::Schema::CD->has_many(tracks => 'DBICTest::Schema::Track');
+DBICTest::Schema::CD->has_many(tags => 'DBICTest::Schema::Tag');
 
-);
-DBICTest::Schema::SelfRefAlias->add_relationship(
-    alias => 'DBICTest::Schema::SelfRef',
-    { 'foreign.id' => 'self.alias' },
-    { accessor     => 'single' }
-);
+DBICTest::Schema::CD->might_have(liner_notes => 'DBICTest::Schema::LinerNotes' => qw/notes/);
 
-DBICTest::Schema::SelfRef->add_relationship(
-    aliases => 'DBICTest::Schema::SelfRefAlias',
-    { 'foreign.self_ref' => 'self.id' },
-    { accessor => 'multi' }
-);
+DBICTest::Schema::SelfRefAlias->belongs_to(
+  self_ref => 'DBICTest::Schema::SelfRef');
 
-DBICTest::Schema::Tag->has_one('cd', 'DBICTest::Schema::CD');
-#DBICTest::Schema::Tag->add_relationship(
-#    cd => 'DBICTest::Schema::CD',
-#    { 'foreign.cdid' => 'self.cd' }
-#);
+DBICTest::Schema::SelfRefAlias->belongs_to(
+  alias => 'DBICTest::Schema::SelfRef');
 
-DBICTest::Schema::Track->has_one('cd', 'DBICTest::Schema::CD');
-#DBICTest::Schema::Track->add_relationship(
-#    cd => 'DBICTest::Schema::CD',
-#    { 'foreign.cdid' => 'self.cd' }
-#);
+DBICTest::Schema::SelfRef->has_many(
+  aliases => 'DBICTest::Schema::SelfRefAlias' => 'self_ref');
 
-DBICTest::Schema::TwoKeys->has_one('artist', 'DBICTest::Schema::Artist');
-# DBICTest::Schema::TwoKeys->add_relationship(
-#    artist => 'DBICTest::Schema::Artist',
-#    { 'foreign.artistid' => 'self.artist' }
-# );
-DBICTest::Schema::TwoKeys->has_one('cd', 'DBICTest::Schema::CD');
-#DBICTest::Schema::TwoKeys->add_relationship(
-#    cd => 'DBICTest::Schema::CD',
-#    { 'foreign.cdid' => 'self.cd' }
-#);
+DBICTest::Schema::Tag->belongs_to('cd', 'DBICTest::Schema::CD');
+
+DBICTest::Schema::Track->belongs_to('cd', 'DBICTest::Schema::CD');
+
+DBICTest::Schema::TwoKeys->belongs_to('artist', 'DBICTest::Schema::Artist');
+
+DBICTest::Schema::TwoKeys->belongs_to('cd', 'DBICTest::Schema::CD');
 
 1;
index 93f47c4..04d2249 100644 (file)
@@ -4,22 +4,35 @@ plan tests => 13;
 
 # has_a test
 my $cd = DBICTest::CD->find(4);
-my ($artist) = $cd->search_related('artist');
+my ($artist) = ($INC{'DBICTest/HelperRels'}
+                  ? $cd->artist
+                  : $cd->search_related('artist'));
 is($artist->name, 'Random Boy Band', 'has_a search_related ok');
 
 # has_many test with an order_by clause defined
 $artist = DBICTest::Artist->find(1);
-is( ($artist->search_related('cds'))[1]->title, 'Spoonful of bees', 'has_many search_related with order_by ok' );
+my @cds = ($INC{'DBICTest/HelperRels'}
+             ? $artist->cds
+             : $artist->search_related('cds'));
+is( $cds[1]->title, 'Spoonful of bees', 'has_many search_related with order_by ok' );
 
 # search_related with additional abstract query
-my @cds = $artist->search_related('cds', { title => { like => '%of%' } } );
+@cds = ($INC{'DBICTest/HelperRels'}
+          ? $artist->cds({ title => { like => '%of%' } })
+          : $artist->search_related('cds', { title => { like => '%of%' } } )
+       );
 is( $cds[1]->title, 'Forkful of bees', 'search_related with abstract query ok' );
 
 # creating a related object
-$artist->create_related( 'cds', {
-    title => 'Big Flop',
-    year => 2005,
-} );
+if ($INC{'DBICTest/HelperRels.pm'}) {
+  $artist->add_to_cds({ title => 'Big Flop', year => 2005 });
+} else {
+  $artist->create_related( 'cds', {
+      title => 'Big Flop',
+      year => 2005,
+  } );
+}
+
 is( ($artist->search_related('cds'))[3]->title, 'Big Flop', 'create_related ok' );
 
 # count_related