use Scalar::Util qw/weaken blessed/;
use Try::Tiny;
+use DBIx::Class::_Util 'UNRESOLVABLE_CONDITION';
use namespace::clean;
=head1 NAME
=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);
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',
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(
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,
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:
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<< $row->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).
+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_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_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
});
=head3 attributes
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');
=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' },
});
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' } ],
});
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
=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;
sub related_resultset {
my $self = shift;
+
$self->throw_exception("Can't call *_related as class methods")
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 $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(@_) : {});
$attrs = { %{$rel_info->{attrs} || {}}, %$attrs };
if (@_ > 1 && (@_ % 2 == 1));
my $query = ((@_ > 1) ? {@_} : shift);
- my $source = $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 {
- $source->_resolve_condition( $rel_info->{cond}, $rel, $self, $rel )
+ $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
# root alias as 'me', instead of $rel (as opposed to invoking
# $rs->search_related)
- local $source->{_relationships}{me} = $source->{_relationships}{$rel}; # make the fake 'me' rel
- my $obj_table_alias = lc($source->source_name) . '__row';
+ # 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;
- $source->resultset->search(
+ $rsrc->resultset->search(
$self->ident_condition($obj_table_alias),
{ alias => $obj_table_alias },
)->search_related('me', $query, $attrs)
# 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) {
- my $reverse = $source->reverse_relationship_info($rel);
+ 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') {
weaken($attrs->{related_objects}{$rev_rel}[0] = $self);
}
$query = ($query ? { '-and' => [ $cond, $query ] } : $cond);
- $self->result_source->related_source($rel)->resultset->search(
+ $rsrc->related_source($rel)->resultset->search(
$query, $attrs
);
}
=cut
sub count_related {
- my $self = shift;
- return $self->search_related(@_)->count;
+ shift->search_related(@_)->count;
}
=head2 new_related
=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 (undef, $crosstable, $relcols) = $rsrc->_resolve_condition (
- $rsrc->relationship_info($rel)->{cond}, $rel, $self, $rel
- );
-
- $self->throw_exception("Custom relationship '$rel' does not resolve to a join-free condition fragment")
- if $crosstable;
-
- if (@{$relcols || []} and @$relcols = grep { ! exists $values->{$_} } @$relcols) {
- $self->throw_exception(sprintf (
- "Custom relationship '%s' not definitive - returns conditions instead of values for column(s): %s",
- $rel,
- map { "'$_'" } @$relcols
- ));
- }
- }
-
- 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
=cut
sub find_related {
- my $self = shift;
- my $rel = shift;
- return $self->search_related($rel)->find(@_);
+ #my ($self, $rel, @args) = @_;
+ return shift->search_related(shift)->find(@_);
}
=head2 find_or_new_related
=cut
sub update_or_create_related {
- my $self = shift;
- my $rel = shift;
- return $self->related_resultset($rel)->update_or_create(@_);
+ #my ($self, $rel, @args) = @_;
+ shift->related_resultset(shift)->update_or_create(@_);
}
=head2 set_from_related
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
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, $relcols) = $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")
- if $crosstable;
- $self->throw_exception(sprintf (
- "Custom relationship '%s' not definitive - returns conditions instead of values for column(s): %s",
- $rel,
- map { "'$_'" } @$relcols
- )) if @{$relcols || []};
-
- $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;
}
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