use Scalar::Util qw/weaken blessed/;
use Try::Tiny;
+use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
use namespace::clean;
=head1 NAME
clause of the C<JOIN> statement associated with this relationship.
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
-following:
+elect to additionally return a simplified B<optional> join-free condition
+consisting of a hashref with B<all keys being fully qualified names of columns
+declared on the corresponding result source>. This boils down to two scenarios:
+
+=over
+
+=item *
+
+When relationship resolution is invoked after C<< $result->$rel_name >>, as
+opposed to C<< $rs->related_resultset($rel_name) >>, the C<$result> object
+is passed to the coderef as C<< $args->{self_result_object} >>.
+
+=item *
+
+Alternatively when the user-space invokes resolution via
+C<< $result->set_from_related( $rel_name => $foreign_values_or_object ) >>, the
+corresponding data is passed to the coderef as C<< $args->{foreign_values} >>,
+B<always> in the form of a hashref. If a foreign result object is supplied
+(which is valid usage of L</set_from_related>), its values will be extracted
+into hashref form by calling L<get_columns|DBIx::Class::Row/get_columns>.
+
+=back
+
+Note that the above scenarios are mutually exclusive, that is you will be supplied
+none or only one of C<self_result_object> and C<foreign_values>. In other words if
+you define your condition coderef as:
sub {
my $args = shift;
"$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_result_object} ? () : {
+ "$args->{foreign_alias}.artist" => $args->{self_result_object}->artistid,
"$args->{foreign_alias}.year" => { '>', "1979", '<', "1990" },
},
+ ! $args->{foreign_values} ? () : {
+ "$args->{self_alias}.artistid" => $args->{foreign_values}{artist},
+ }
);
}
-Now this code:
+Then this code:
my $artist = $schema->resultset("Artist")->find({ id => 4 });
$artist->cds_80s->all;
'4', '1990', '1979'
-Note that in order to be able to use
-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 create related objects (an exception will be thrown).
+While this code:
+
+ my $cd = $schema->resultset("CD")->search({ artist => 1 }, { rows => 1 })->single;
+ my $artist = $schema->resultset("Artist")->new({});
+ $artist->set_from_related('cds_80s');
+
+Will properly set the C<< $artist->artistid >> field of this new object to C<1>
+
+Note that in order to be able to use L</set_from_related> (and by extension
+L<< $result->create_related|DBIx::Class::Relationship::Base/create_related >>),
+the returned join free condition B<must> contain only plain values/deflatable
+objects. For instance the C<year> constraint in the above example prevents
+the relationship from being used to create related objects using
+C<< $artst->create_related( cds_80s => { title => 'blah' } ) >> (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_resultsource => The resultsource instance on which rel_name is registered
- rel_name => The relationship name (does *NOT* always match foreign_alias)
+ 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)
+ 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)
+
+ self_result_object => The invocant *object* itself in case of a call like
+ $result_object->$rel_name( ... )
+
+ foreign_values => A *hashref* of related data: may be passed in directly or
+ derived via ->get_columns() from a related object in case of
+ $result_object->set_from_related( $rel_name, $foreign_result_object )
# 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
+ self_rowobj => Old deprecated slot for self_result_object
+ foreign_relname => Old deprecated slot for rel_name
});
=head3 attributes
return $self->{related_resultsets}{$rel} = do {
- my $rel_info = $self->relationship_info($rel)
+ my $rsrc = $self->result_source;
+
+ my $rel_info = $rsrc->relationship_info($rel)
or $self->throw_exception( "No such relationship '$rel'" );
my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
if (@_ > 1 && (@_ % 2 == 1));
my $query = ((@_ > 1) ? {@_} : shift);
- my $rsrc = $self->result_source;
-
# 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 {
}
catch {
$self->throw_exception ($_) if $self->in_storage;
- $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION; # RV, no return()
+ UNRESOLVABLE_CONDITION; # RV, no return()
};
# keep in mind that the following if() block is part of a do{} - no return()s!!!
# 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') {
=cut
sub new_related {
- my ($self, $rel, $values) = @_;
-
- # FIXME - this is a bad position for this (also an identical copy in
- # set_from_related), but I have no saner way to hook, and I absolutely
- # want this to throw at least for coderefs, instead of the "insert a NULL
- # when it gets hard" insanity --ribasushi
- #
- # 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 $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("Relationship '$rel' does not resolve to a join-free condition fragment")
- if $crosstable;
-
- 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);
+ my ($self, $rel, $data) = @_;
+
+ return $self->search_related($rel)->new_result( $self->result_source->_resolve_relationship_condition (
+ infer_values_based_on => $data,
+ rel_name => $rel,
+ self_result_object => $self,
+ foreign_alias => $rel,
+ self_alias => 'me',
+ )->{inferred_values} );
}
=head2 create_related
sub set_from_related {
my ($self, $rel, $f_obj) = @_;
- my $rsrc = $self->result_source;
- my $rel_info = $rsrc->relationship_info($rel)
- or $self->throw_exception( "No such relationship '$rel'" );
-
- if (defined $f_obj) {
- my $f_class = $rel_info->{class};
- $self->throw_exception( "Object '$f_obj' isn't a ".$f_class )
- unless blessed $f_obj and $f_obj->isa($f_class);
- }
-
-
- # FIXME - this is a bad position for this (also an identical copy in
- # new_related), but I have no saner way to hook, and I absolutely
- # want this to throw at least for coderefs, instead of the "insert a NULL
- # when it gets hard" insanity --ribasushi
- #
- # sanity check - currently throw when a complex coderef rel is encountered
- # FIXME - should THROW MOAR!
- my ($cond, $crosstable, $nonequality_foreign_columns) = $rsrc->_resolve_condition (
- $rel_info->{cond}, $f_obj, $rel, $rel
- );
- $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 { "'$_'" } @$nonequality_foreign_columns
- )) if $nonequality_foreign_columns;
-
- $self->set_columns($cond);
+ $self->set_columns( $self->result_source->_resolve_relationship_condition (
+ infer_values_based_on => {},
+ rel_name => $rel,
+ foreign_values => $f_obj,
+ foreign_alias => $rel,
+ self_alias => 'me',
+ )->{inferred_values} );
return 1;
}