Stop accepting foreign_values => undef/rowobj in the resolver
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Relationship / Base.pm
index 882d47b..f82d2ec 100644 (file)
@@ -6,8 +6,8 @@ use warnings;
 use base qw/DBIx::Class/;
 
 use Scalar::Util qw/weaken blessed/;
-use Try::Tiny;
-use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
+use DBIx::Class::_Util qw( UNRESOLVABLE_CONDITION fail_on_internal_call );
+use DBIx::Class::Carp;
 use namespace::clean;
 
 =head1 NAME
@@ -406,7 +406,7 @@ the relationship attributes.
 
 The C<belongs_to> relationship does not update across relationships
 by default, so if you have a 'proxy' attribute on a belongs_to and want to
-use 'update' on it, you muse set C<< cascade_update => 1 >>.
+use 'update' on it, you must set C<< cascade_update => 1 >>.
 
 This is not a RDMS style cascade update - it purely means that when
 an object has update called on it, all the related objects also
@@ -499,15 +499,20 @@ this instance (like in the case of C<might_have> relationships).
 =cut
 
 sub related_resultset {
-  my $self = shift;
+  $_[0]->throw_exception(
+    '$result->related_resultset() no longer accepts extra search arguments, '
+  . 'you need to switch to ...->related_resultset($relname)->search_rs(...) '
+  . 'instead (it was never documented and more importantly could never work '
+  . 'reliably due to the heavy caching involved)'
+  ) if @_ > 2;
 
-  $self->throw_exception("Can't call *_related as class methods")
-    unless ref $self;
+  $_[0]->throw_exception("Can't call *_related as class methods")
+    unless ref $_[0];
 
-  my $rel = shift;
+  return $_[0]->{related_resultsets}{$_[1]}
+    if defined $_[0]->{related_resultsets}{$_[1]};
 
-  return $self->{related_resultsets}{$rel}
-    if defined $self->{related_resultsets}{$rel};
+  my ($self, $rel) = @_;
 
   return $self->{related_resultsets}{$rel} = do {
 
@@ -516,25 +521,27 @@ sub related_resultset {
     my $rel_info = $rsrc->relationship_info($rel)
       or $self->throw_exception( "No such relationship '$rel'" );
 
-    my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
-    $attrs = { %{$rel_info->{attrs} || {}}, %$attrs };
+    my $cond_res = $rsrc->_resolve_relationship_condition(
+      rel_name => $rel,
+      self_result_object => $self,
 
-    $self->throw_exception( "Invalid query: @_" )
-      if (@_ > 1 && (@_ % 2 == 1));
-    my $query = ((@_ > 1) ? {@_} : shift);
+      # this may look weird, but remember that we are making a resultset
+      # out of an existing object, with the new source being at the head
+      # of the FROM chain. Having a 'me' alias is nothing but expected there
+      foreign_alias => 'me',
 
-    # condition resolution may fail if an incomplete master-object prefetch
-    # is encountered - that is ok during prefetch construction (not yet in_storage)
-    my ($cond, $is_crosstable) = try {
-      $rsrc->_resolve_condition( $rel_info->{cond}, $rel, $self, $rel )
-    }
-    catch {
-      $self->throw_exception ($_) if $self->in_storage;
-      UNRESOLVABLE_CONDITION;  # RV, no return()
-    };
+      self_alias => "!!!\xFF()!!!_SHOULD_NEVER_BE_SEEN_IN_USE_!!!()\xFF!!!",
+
+      # not strictly necessary, but shouldn't hurt either
+      require_join_free_condition => !!(ref $rel_info->{cond} ne 'CODE'),
+    );
 
     # keep in mind that the following if() block is part of a do{} - no return()s!!!
-    if ($is_crosstable and ref $rel_info->{cond} eq 'CODE') {
+    if (
+      ! $cond_res->{join_free_condition}
+        and
+      ref $rel_info->{cond} eq 'CODE'
+    ) {
 
       # A WHOREIFFIC hack to reinvoke the entire condition resolution
       # with the correct alias. Another way of doing this involves a
@@ -546,20 +553,28 @@ sub related_resultset {
       # root alias as 'me', instead of $rel (as opposed to invoking
       # $rs->search_related)
 
-      local $rsrc->{_relationships}{me} = $rsrc->{_relationships}{$rel};  # make the fake 'me' rel
+      # make the fake 'me' rel
+      local $rsrc->{_relationships}{me} = {
+        %{ $rsrc->{_relationships}{$rel} },
+        _original_name => $rel,
+      };
+
       my $obj_table_alias = lc($rsrc->source_name) . '__row';
       $obj_table_alias =~ s/\W+/_/g;
 
       $rsrc->resultset->search(
         $self->ident_condition($obj_table_alias),
         { alias => $obj_table_alias },
-      )->search_related('me', $query, $attrs)
+      )->related_resultset('me')->search(undef, $rel_info->{attrs})
     }
     else {
+
       # FIXME - this conditional doesn't seem correct - got to figure out
       # at some point what it does. Also the entire UNRESOLVABLE_CONDITION
       # business seems shady - we could simply not query *at all*
-      if ($cond eq UNRESOLVABLE_CONDITION) {
+      my $attrs;
+      if ( $cond_res->{join_free_condition} eq UNRESOLVABLE_CONDITION ) {
+        $attrs = { %{$rel_info->{attrs}} };
         my $reverse = $rsrc->reverse_relationship_info($rel);
         foreach my $rev_rel (keys %$reverse) {
           if ($reverse->{$rev_rel}{attrs}{accessor} && $reverse->{$rev_rel}{attrs}{accessor} eq 'multi') {
@@ -569,29 +584,10 @@ sub related_resultset {
           }
         }
       }
-      elsif (ref $cond eq 'ARRAY') {
-        $cond = [ map {
-          if (ref $_ eq 'HASH') {
-            my $hash;
-            foreach my $key (keys %$_) {
-              my $newkey = $key !~ /\./ ? "me.$key" : $key;
-              $hash->{$newkey} = $_->{$key};
-            }
-            $hash;
-          } else {
-            $_;
-          }
-        } @$cond ];
-      }
-      elsif (ref $cond eq 'HASH') {
-       foreach my $key (grep { ! /\./ } keys %$cond) {
-          $cond->{"me.$key"} = delete $cond->{$key};
-        }
-      }
 
-      $query = ($query ? { '-and' => [ $cond, $query ] } : $cond);
       $rsrc->related_source($rel)->resultset->search(
-        $query, $attrs
+        $cond_res->{join_free_condition},
+        $attrs || $rel_info->{attrs},
       );
     }
   };
@@ -615,8 +611,9 @@ See L<DBIx::Class::ResultSet/search_related> for more information.
 
 =cut
 
-sub search_related {
-  return shift->related_resultset(shift)->search(@_);
+sub search_related :DBIC_method_is_indirect_sugar {
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+  shift->related_resultset(shift)->search(@_);
 }
 
 =head2 search_related_rs
@@ -626,8 +623,9 @@ it guarantees a resultset, even in list context.
 
 =cut
 
-sub search_related_rs {
-  return shift->related_resultset(shift)->search_rs(@_);
+sub search_related_rs :DBIC_method_is_indirect_sugar {
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+  shift->related_resultset(shift)->search_rs(@_)
 }
 
 =head2 count_related
@@ -645,8 +643,9 @@ current result or where conditions.
 
 =cut
 
-sub count_related {
-  shift->search_related(@_)->count;
+sub count_related :DBIC_method_is_indirect_sugar {
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+  shift->related_resultset(shift)->search_rs(@_)->count;
 }
 
 =head2 new_related
@@ -669,7 +668,7 @@ your storage until you call L<DBIx::Class::Row/insert> on it.
 sub new_related {
   my ($self, $rel, $data) = @_;
 
-  return $self->search_related($rel)->new_result( $self->result_source->_resolve_relationship_condition (
+  $self->related_resultset($rel)->new_result( $self->result_source->_resolve_relationship_condition (
     infer_values_based_on => $data,
     rel_name => $rel,
     self_result_object => $self,
@@ -721,9 +720,10 @@ See L<DBIx::Class::ResultSet/find> for details.
 
 =cut
 
-sub find_related {
+sub find_related :DBIC_method_is_indirect_sugar {
   #my ($self, $rel, @args) = @_;
-  return shift->search_related(shift)->find(@_);
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+  return shift->related_resultset(shift)->find(@_);
 }
 
 =head2 find_or_new_related
@@ -743,8 +743,9 @@ for details.
 
 sub find_or_new_related {
   my $self = shift;
-  my $obj = $self->find_related(@_);
-  return defined $obj ? $obj : $self->new_related(@_);
+  my $rel = shift;
+  my $obj = $self->related_resultset($rel)->find(@_);
+  return defined $obj ? $obj : $self->related_resultset($rel)->new_result(@_);
 }
 
 =head2 find_or_create_related
@@ -764,8 +765,9 @@ L<DBIx::Class::ResultSet/find_or_create> for details.
 
 sub find_or_create_related {
   my $self = shift;
-  my $obj = $self->find_related(@_);
-  return (defined($obj) ? $obj : $self->create_related(@_));
+  my $rel = shift;
+  my $obj = $self->related_resultset($rel)->find(@_);
+  return (defined($obj) ? $obj : $self->create_related( $rel => @_ ));
 }
 
 =head2 update_or_create_related
@@ -783,8 +785,9 @@ L<DBIx::Class::ResultSet/update_or_create> for details.
 
 =cut
 
-sub update_or_create_related {
+sub update_or_create_related :DBIC_method_is_indirect_sugar {
   #my ($self, $rel, @args) = @_;
+  DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
   shift->related_resultset(shift)->update_or_create(@_);
 }
 
@@ -809,8 +812,8 @@ call set_from_related on the book.
 This is called internally when you pass existing objects as values to
 L<DBIx::Class::ResultSet/create>, or pass an object to a belongs_to accessor.
 
-The columns are only set in the local copy of the object, call L</update> to
-set them in the storage.
+The columns are only set in the local copy of the object, call
+L<update|DBIx::Class::Row/update> to update them in the storage.
 
 =cut
 
@@ -820,7 +823,34 @@ sub set_from_related {
   $self->set_columns( $self->result_source->_resolve_relationship_condition (
     infer_values_based_on => {},
     rel_name => $rel,
-    foreign_values => $f_obj,
+    foreign_values => (
+      # maintain crazy set_from_related interface
+      #
+      ( ! defined $f_obj )          ? +{}
+    : ( ! defined blessed $f_obj )  ? $f_obj
+                                    : do {
+
+        my $f_result_class = $self->result_source->related_source($rel)->result_class;
+
+        unless( $f_obj->isa($f_result_class) ) {
+
+          $self->throw_exception(
+            'Object supplied to set_from_related() must inherit from '
+          . "'$DBIx::Class::ResultSource::__expected_result_class_isa'"
+          ) unless $f_obj->isa(
+            $DBIx::Class::ResultSource::__expected_result_class_isa
+          );
+
+          carp_unique(
+            'Object supplied to set_from_related() usually should inherit from '
+          . "the related ResultClass ('$f_result_class'), perhaps you've made "
+          . 'a mistake?'
+          );
+        }
+
+        +{ $f_obj->get_columns };
+      }
+    ),
     foreign_alias => $rel,
     self_alias => 'me',
   )->{inferred_values} );
@@ -872,8 +902,9 @@ And returns the result of that.
 
 sub delete_related {
   my $self = shift;
-  my $obj = $self->search_related(@_)->delete;
-  delete $self->{related_resultsets}->{$_[0]};
+  my $rel = shift;
+  my $obj = $self->related_resultset($rel)->search_rs(@_)->delete;
+  delete $self->{related_resultsets}->{$rel};
   return $obj;
 }
 
@@ -980,13 +1011,16 @@ Removes the link between the current object and the related object. Note that
 the related object itself won't be deleted unless you call ->delete() on
 it. This method just removes the link between the two objects.
 
-=head1 AUTHOR AND CONTRIBUTORS
+=head1 FURTHER QUESTIONS?
 
-See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
+Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
 
-=head1 LICENSE
+=head1 COPYRIGHT AND LICENSE
 
-You may distribute this code under the same terms as Perl itself.
+This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
+by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
+redistribute it and/or modify it under the same terms as the
+L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
 
 =cut