Add foreign_resultobj to the customrel signature
Peter Rabbitson [Wed, 11 Jun 2014 14:32:59 +0000 (16:32 +0200)]
Tests in next commit

Changes
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSource.pm
t/lib/DBICTest/Util.pm

diff --git a/Changes b/Changes
index 055c6f4..b1b040f 100644 (file)
--- a/Changes
+++ b/Changes
@@ -7,6 +7,9 @@ Revision history for DBIx::Class
           returned from storage
         - Custom condition relationships are now invoked with a slightly
           different signature (existing coderefs will continue to work)
+        - Add extra custom condition coderef attribute 'foreign_resultobj'
+          to allow for proper reverse-relationship emulation
+          (i.e. $result->set_from_related($custom_cond, $foreign_resultobj)
 
     * Fixes
         - Fix Resultset delete/update affecting *THE ENTIRE TABLE* in cases
index a2e78ff..e11263f 100644 (file)
@@ -238,7 +238,10 @@ metadata. Currently the supplied coderef is executed as:
     self_alias        => The alias of the invoking resultset
     foreign_alias     => The alias of the to-be-joined resultset (does *NOT* always match rel_name)
 
+    # only one of these (or none at all) will ever be supplied to aid in the
+    # construction of a join-free condition
     self_resultobj    => The invocant object itself in case of a $resultobj->$rel_name() call
+    foreign_resultobj => The related object in case of $resultobj->set_from_related($rel_name, $foreign_resultobj)
 
     # deprecated inconsistent names, will be forever available for legacy code
     self_rowobj       => Old deprecated slot for self_resultobj
index 4f45c58..9f6ac9c 100644 (file)
@@ -1776,34 +1776,49 @@ sub _resolve_relationship_condition {
       self_resultsource => $self,
       self_alias => $args->{self_alias},
       foreign_alias => $args->{foreign_alias},
-      self_resultobj => defined $args->{self_resultobj} ? $args->{self_resultobj} : undef,
+      self_resultobj => (defined $args->{self_resultobj} ? $args->{self_resultobj} : undef),
+      foreign_resultobj => (defined $args->{foreign_resultobj} ? $args->{foreign_resultobj} : undef),
     };
 
     # legacy - never remove these!!!
     $cref_args->{foreign_relname} = $cref_args->{rel_name};
     $cref_args->{self_rowobj} = $cref_args->{self_resultobj};
 
-    my ($crosstable_cond, $joinfree_cond) = $args->{condition}->($cref_args);
+    my ($crosstable_cond, $joinfree_cond, @extra) = $args->{condition}->($cref_args);
+
+    # FIXME sanity check
+    carp_unique('A custom condition coderef can return at most 2 conditions: extra return values discarded')
+      if @extra;
 
     my @nonvalue_cols;
     if ($joinfree_cond) {
 
+      my ($joinfree_alias, $joinfree_source);
+      if (defined $args->{self_resultobj}) {
+        $joinfree_alias = $args->{foreign_alias};
+        $joinfree_source = $self->related_source($args->{rel_name});
+      }
+      elsif (defined $args->{foreign_resultobj}) {
+        $joinfree_alias = $args->{self_alias};
+        $joinfree_source = $self;
+      }
+
       # FIXME sanity check until things stabilize, remove at some point
       $self->throw_exception (
         "A join-free condition returned for relationship '$args->{rel_name}' without a result object to chain from"
-      ) unless defined $args->{self_resultobj};
+      ) unless $joinfree_alias;
 
-      my $foreign_src_fq_col_list = { map { ( "$args->{foreign_alias}.$_" => 1 ) } $self->related_source($args->{rel_name})->columns };
+      my $fq_col_list = { map { ( "$joinfree_alias.$_" => 1 ) } $joinfree_source->columns };
 
       # FIXME another sanity check
       if (
         ref $joinfree_cond ne 'HASH'
           or
-        grep { ! $foreign_src_fq_col_list->{$_} } keys %$joinfree_cond
+        grep { ! $fq_col_list->{$_} } keys %$joinfree_cond
       ) {
         $self->throw_exception (
           "The join-free condition returned for relationship '$args->{rel_name}' must be a hash "
-         .'reference with all keys being fully qualified column names of the foreign source'
+         .'reference with all keys being fully qualified column names of the corresponding source'
         );
       }
 
@@ -1813,7 +1828,7 @@ sub _resolve_relationship_condition {
         @{ $self->schema->storage->_extract_fixed_condition_columns($joinfree_cond) }
       };
       @nonvalue_cols = map
-        { $_ =~ /^\Q$args->{foreign_alias}.\E(.+)/ }
+        { $_ =~ /^\Q$joinfree_alias.\E(.+)/ }
         grep
           { ! $joinfree_cond_equality_columns->{$_} }
           keys %$joinfree_cond;
index b821243..847fba8 100644 (file)
@@ -66,7 +66,10 @@ sub check_customcond_args ($) {
   confess "Passed resultsource has no record of the supplied rel_name - likely wrong \$rsrc"
     unless ref $args->{self_resultsource}->relationship_info($args->{rel_name});
 
+  my $rowobj_cnt = 0;
+
   if (defined $args->{self_resultobj} or defined $args->{self_rowobj} ) {
+    $rowobj_cnt++;
     for (qw(self_resultobj self_rowobj)) {
       confess "Custom condition argument '$_' must be a result instance"
         unless defined blessed $args->{$_} and $args->{$_}->isa('DBIx::Class::Row');
@@ -76,6 +79,16 @@ sub check_customcond_args ($) {
       if refaddr($args->{self_resultobj}) != refaddr($args->{self_rowobj});
   }
 
+  if (defined $args->{foreign_resultobj}) {
+    $rowobj_cnt++;
+
+    confess "Custom condition argument 'foreign_resultobj' must be a result instance"
+      unless defined blessed $args->{foreign_resultobj} and $args->{foreign_resultobj}->isa('DBIx::Class::Row');
+  }
+
+  confess "Result objects supplied on both ends of a relationship"
+    if $rowobj_cnt == 2;
+
   $args;
 }