Move a CDBI escape to the CDBI class hierarchy
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Relationship / Base.pm
index 483eefd..2868250 100644 (file)
@@ -7,6 +7,7 @@ use base qw/DBIx::Class/;
 
 use Scalar::Util qw/weaken blessed/;
 use Try::Tiny;
+use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
 use namespace::clean;
 
 =head1 NAME
@@ -38,11 +39,11 @@ methods, for predefined ones, look in L<DBIx::Class::Relationship>.
 
 =over 4
 
-=item Arguments: 'relname', 'Foreign::Class', $condition, $attrs
+=item Arguments: $rel_name, $foreign_class, $condition, $attrs
 
 =back
 
-  __PACKAGE__->add_relationship('relname',
+  __PACKAGE__->add_relationship('rel_name',
                                 'Foreign::Class',
                                 $condition, $attrs);
 
@@ -54,9 +55,16 @@ source, indicated by its class name.
 The condition argument describes the C<ON> clause of the C<JOIN>
 expression used to connect the two sources when creating SQL queries.
 
-To create simple equality joins, supply a hashref containing the
-remote table column name as the key(s), and the local table column
-name as the value(s), for example given:
+=head4 Simple equality
+
+To create simple equality joins, supply a hashref containing the remote
+table column name as the key(s) prefixed by C<'foreign.'>, and the
+corresponding local table column name as the value(s) prefixed by C<'self.'>.
+Both C<foreign> and C<self> are pseudo aliases and must be entered
+literally. They will be replaced with the actual correct table alias
+when the SQL is produced.
+
+For example given:
 
   My::Schema::Author->has_many(
     books => 'My::Schema::Book',
@@ -75,10 +83,6 @@ This describes a relationship between the C<Author> table and the
 C<Book> table where the C<Book> table has a column C<author_id>
 containing the ID value of the C<Author>.
 
-C<foreign> and C<self> are pseudo aliases and must be entered
-literally. They will be replaced with the actual correct table alias
-when the SQL is produced.
-
 Similarly:
 
   My::Schema::Book->has_many(
@@ -103,9 +107,11 @@ will result in the C<JOIN> clause:
 This describes the relationship from C<Book> to C<Edition>, where the
 C<Edition> table refers to a publisher and a type (e.g. "paperback"):
 
+=head4 Multiple groups of simple equality conditions
+
 As is the default in L<SQL::Abstract>, the key-value pairs will be
-C<AND>ed in the result. C<OR> can be achieved with an arrayref, for
-example a condition like:
+C<AND>ed in the resulting C<JOIN> clause. An C<OR> can be achieved with
+an arrayref. For example a condition like:
 
   My::Schema::Item->has_many(
     related_item_links => My::Schema::Item::Links,
@@ -125,6 +131,14 @@ This describes the relationship from C<Item> to C<Item::Links>, where
 C<Item::Links> is a many-to-many linking table, linking items back to
 themselves in a peer fashion (without a "parent-child" designation)
 
+=head4 Custom join conditions
+
+  NOTE: The custom join condition specification mechanism is capable of
+  generating JOIN clauses of virtually unlimited complexity. This may limit
+  your ability to traverse some of the more involved relationship chains the
+  way you expect, *and* may bring your RDBMS to its knees. Exercise care
+  when declaring relationships as described here.
+
 To specify joins which describe more than a simple equality of column
 values, the custom join condition coderef syntax can be used. For
 example:
@@ -170,7 +184,7 @@ While every coderef-based condition must return a valid C<ON> clause, it may
 elect to additionally return a simplified join-free condition hashref when
 invoked as C<< $result->relationship >>, as opposed to
 C<< $rs->related_resultset('relationship') >>. In this case C<$result> is
-passed to the coderef as C<< $args->{self_rowobj} >>, so a user can do the
+passed to the coderef as C<< $args->{self_resultobj} >>, so a user can do the
 following:
 
   sub {
@@ -181,8 +195,8 @@ following:
         "$args->{foreign_alias}.artist" => { -ident => "$args->{self_alias}.artistid" },
         "$args->{foreign_alias}.year"   => { '>', "1979", '<', "1990" },
       },
-      $args->{self_rowobj} && {
-        "$args->{foreign_alias}.artist" => $args->{self_rowobj}->artistid,
+      $args->{self_resultobj} && {
+        "$args->{foreign_alias}.artist" => $args->{self_resultobj}->artistid,
         "$args->{foreign_alias}.year"   => { '>', "1979", '<', "1990" },
       },
     );
@@ -206,24 +220,33 @@ With the bind values:
     '4', '1990', '1979'
 
 Note that in order to be able to use
-L<< $row->create_related|DBIx::Class::Relationship::Base/create_related >>,
+L<< $result->create_related|DBIx::Class::Relationship::Base/create_related >>,
 the coderef must not only return as its second such a "simple" condition
 hashref which does not depend on joins being available, but the hashref must
 contain only plain values/deflatable objects, such that the result can be
 passed directly to L<DBIx::Class::Relationship::Base/set_from_related>. For
 instance the C<year> constraint in the above example prevents the relationship
-from being used to to create related objects (an exception will be thrown).
+from being used to create related objects (an exception will be thrown).
 
 In order to allow the user to go truly crazy when generating a custom C<ON>
 clause, the C<$args> hashref passed to the subroutine contains some extra
 metadata. Currently the supplied coderef is executed as:
 
   $relationship_info->{cond}->({
-    self_alias        => The alias of the invoking resultset ('me' in case of a result object),
-    foreign_alias     => The alias of the to-be-joined resultset (often matches relname),
-    self_resultsource => The invocant's resultsource,
-    foreign_relname   => The relationship name (does *not* always match foreign_alias),
-    self_rowobj       => The invocant itself in case of a $result_object->$relationship call
+    self_resultsource => The resultsource instance on which rel_name is registered
+    rel_name          => The relationship name (does *NOT* always match foreign_alias)
+
+    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
+    foreign_relname   => Old deprecated slot for rel_name
   });
 
 =head3 attributes
@@ -275,7 +298,7 @@ Then, assuming MyApp::Schema::LinerNotes has an accessor named notes, you can do
 
 For a 'belongs_to relationship, note the 'cascade_update':
 
-  MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd,
+  MyApp::Schema::Track->belongs_to( cd => 'MyApp::Schema::CD', 'cd,
       { proxy => ['title'], cascade_update => 1 }
   );
   $track->title('New Title');
@@ -284,9 +307,9 @@ For a 'belongs_to relationship, note the 'cascade_update':
 =item \%column
 
 A hashref where each key is the accessor you want installed in the main class,
-and its value is the name of the original in the fireign class.
+and its value is the name of the original in the foreign class.
 
-  MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
+  MyApp::Schema::Track->belongs_to( cd => 'MyApp::Schema::CD', 'cd', {
       proxy => { cd_title => 'title' },
   });
 
@@ -296,7 +319,7 @@ This will create an accessor named C<cd_title> on the C<$track> result object.
 
 NOTE: you can pass a nested struct too, for example:
 
-  MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
+  MyApp::Schema::Track->belongs_to( cd => 'MyApp::Schema::CD', 'cd', {
     proxy => [ 'year', { cd_title => 'title' } ],
   });
 
@@ -423,8 +446,8 @@ $rel_name.
 =back
 
   # These pairs do the same thing
-  $row = $cd->related_resultset('artist')->single;  # has_one relationship
-  $row = $cd->artist;
+  $result = $cd->related_resultset('artist')->single;  # has_one relationship
+  $result = $cd->artist;
   $rs = $cd->related_resultset('tracks');           # has_many relationship
   $rs = $cd->tracks;
 
@@ -446,11 +469,15 @@ sub related_resultset {
     unless ref $self;
 
   my $rel = shift;
-  my $rel_info = $self->relationship_info($rel);
-  $self->throw_exception( "No such relationship '$rel'" )
-    unless $rel_info;
 
-  return $self->{related_resultsets}{$rel} ||= do {
+  return $self->{related_resultsets}{$rel}
+    if defined $self->{related_resultsets}{$rel};
+
+  return $self->{related_resultsets}{$rel} = do {
+
+    my $rel_info = $self->relationship_info($rel)
+      or $self->throw_exception( "No such relationship '$rel'" );
+
     my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
     $attrs = { %{$rel_info->{attrs} || {}}, %$attrs };
 
@@ -466,18 +493,12 @@ sub related_resultset {
       $rsrc->_resolve_condition( $rel_info->{cond}, $rel, $self, $rel )
     }
     catch {
-      if ($self->in_storage) {
-        $self->throw_exception ($_);
-      }
-
-      $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION;  # RV
+      $self->throw_exception ($_) if $self->in_storage;
+      UNRESOLVABLE_CONDITION;  # RV, no return()
     };
 
     # keep in mind that the following if() block is part of a do{} - no return()s!!!
-    if ($is_crosstable) {
-      $self->throw_exception (
-        "A cross-table relationship condition returned for statically declared '$rel'"
-      ) unless ref $rel_info->{cond} eq 'CODE';
+    if ($is_crosstable 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
@@ -502,7 +523,7 @@ sub related_resultset {
       # 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 $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION) {
+      if ($cond eq UNRESOLVABLE_CONDITION) {
         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') {
@@ -620,24 +641,27 @@ sub new_related {
   # sanity check - currently throw when a complex coderef rel is encountered
   # FIXME - should THROW MOAR!
 
-  if (ref $self) {  # cdbi calls this as a class method, /me vomits
-
     my $rsrc = $self->result_source;
-    my (undef, $crosstable, $cond_targets) = $rsrc->_resolve_condition (
-      $rsrc->relationship_info($rel)->{cond}, $rel, $self, $rel
+    my $rel_info = $rsrc->relationship_info($rel)
+      or $self->throw_exception( "No such relationship '$rel'" );
+    my (undef, $crosstable, $nonequality_foreign_columns) = $rsrc->_resolve_condition (
+      $rel_info->{cond}, $rel, $self, $rel
     );
 
-    $self->throw_exception("Custom relationship '$rel' does not resolve to a join-free condition fragment")
+    $self->throw_exception("Relationship '$rel' does not resolve to a join-free condition fragment")
       if $crosstable;
 
-    if (my @unspecified_rel_condition_chunks = grep { ! exists $values->{$_} } @{$cond_targets||[]} ) {
+    if (
+      $nonequality_foreign_columns
+        and
+      my @unspecified_rel_condition_chunks = grep { ! exists $values->{$_} } @$nonequality_foreign_columns
+    ) {
       $self->throw_exception(sprintf (
         "Custom relationship '%s' not definitive - returns conditions instead of values for column(s): %s",
         $rel,
         map { "'$_'" } @unspecified_rel_condition_chunks
       ));
     }
-  }
 
   return $self->search_related($rel)->new_result($values);
 }
@@ -799,16 +823,17 @@ sub set_from_related {
   #
   # sanity check - currently throw when a complex coderef rel is encountered
   # FIXME - should THROW MOAR!
-  my ($cond, $crosstable, $cond_targets) = $rsrc->_resolve_condition (
+  my ($cond, $crosstable, $nonequality_foreign_columns) = $rsrc->_resolve_condition (
     $rel_info->{cond}, $f_obj, $rel, $rel
   );
-  $self->throw_exception("Custom relationship '$rel' does not resolve to a join-free condition fragment")
+  $self->throw_exception("Relationship '$rel' does not resolve to a join-free condition fragment")
     if $crosstable;
+
   $self->throw_exception(sprintf (
     "Custom relationship '%s' not definitive - returns conditions instead of values for column(s): %s",
     $rel,
-    map { "'$_'" } @$cond_targets
-  )) if $cond_targets;
+    map { "'$_'" } @$nonequality_foreign_columns
+  )) if $nonequality_foreign_columns;
 
   $self->set_columns($cond);