While every coderef-based condition must return a valid C<ON> clause, it may
elect to additionally return a simplified B<optional> 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_result_object} >>. Alternatively
-the user-space could be calling C<< $result->set_from_related( $rel =>
-$foreign_related_object ) >>, in which case C<$foreign_related_object> will
-be passed to the coderef as C<< $args->{foreign_result_object >>. In other
-words if you define your condition coderef as:
+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" => $args->{self_result_object}->artistid,
"$args->{foreign_alias}.year" => { '>', "1979", '<', "1990" },
},
- ! $args->{foreign_result_object} ? () : {
- "$args->{self_alias}.artistid" => $args->{foreign_result_object}->artist,
+ ! $args->{foreign_values} ? () : {
+ "$args->{self_alias}.artistid" => $args->{foreign_values}{artist},
}
);
}
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<< $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).
+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_result_object => The invocant object itself in case of a $result_object->$rel_name( ... ) call
- foreign_result_object => The related object in case of $result_object->set_from_related( $rel_name, $foreign_result_object )
+
+ 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_result_object
- 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
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
=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 {
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
# 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)
+ )->search_related('me', 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') {
}
}
}
- 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},
);
}
};
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
$self->set_columns( $self->result_source->_resolve_relationship_condition (
infer_values_based_on => {},
rel_name => $rel,
- foreign_result_object => $f_obj,
+ foreign_values => $f_obj,
foreign_alias => $rel,
self_alias => 'me',
)->{inferred_values} );
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