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
+elect to additionally return a simplified join-free condition hashref when
invoked as C<< $row_object->relationship >>, as opposed to
C<< $rs->related_resultset('relationship') >>. In this case C<$row_object> is
passed to the coderef as C<< $args->{self_rowobj} >>, so a user can do the
An arrayref containing a list of accessors in the foreign class to create in
the main class. If, for example, you do the following:
- MyDB::Schema::CD->might_have(liner_notes => 'MyDB::Schema::LinerNotes',
+ MyApp::Schema::CD->might_have(liner_notes => 'MyApp::Schema::LinerNotes',
undef, {
proxy => [ qw/notes/ ],
});
-Then, assuming MyDB::Schema::LinerNotes has an accessor named notes, you can do:
+Then, assuming MyApp::Schema::LinerNotes has an accessor named notes, you can do:
- my $cd = MyDB::Schema::CD->find(1);
+ my $cd = MyApp::Schema::CD->find(1);
$cd->notes('Notes go here'); # set notes -- LinerNotes object is
# created if it doesn't exist
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.
- MyDB::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
+ MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
proxy => { cd_title => 'title' },
});
NOTE: you can pass a nested struct too, for example:
- MyDB::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
+ MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', {
proxy => [ 'year', { cd_title => 'title' } ],
});
# condition resolution may fail if an incomplete master-object prefetch
# is encountered - that is ok during prefetch construction (not yet in_storage)
-
- # if $rel_info->{cond} is a CODE, we might need to join from the
- # current resultsource instead of just querying the target
- # resultsource, in that case, the condition might provide an
- # additional condition in order to avoid an unecessary join if
- # that is at all possible.
- my ($cond, $extended_cond) = try {
- $source->_resolve_condition( $rel_info->{cond}, $rel, $self )
+ my ($cond, $is_crosstable) = try {
+ $source->_resolve_condition( $rel_info->{cond}, $rel, $self, $rel )
}
catch {
if ($self->in_storage) {
$DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION; # RV
};
- if ($cond eq $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION) {
- my $reverse = $source->reverse_relationship_info($rel);
- foreach my $rev_rel (keys %$reverse) {
- if ($reverse->{$rev_rel}{attrs}{accessor} && $reverse->{$rev_rel}{attrs}{accessor} eq 'multi') {
- $attrs->{related_objects}{$rev_rel} = [ $self ];
- weaken $attrs->{related_object}{$rev_rel}[0];
- } else {
- $attrs->{related_objects}{$rev_rel} = $self;
- weaken $attrs->{related_object}{$rev_rel};
- }
- }
+ # 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';
+
+ # A WHOREIFFIC hack to reinvoke the entire condition resolution
+ # with the correct alias. Another way of doing this involves a
+ # lot of state passing around, and the @_ positions are already
+ # mapped out, making this crap a less icky option.
+ #
+ # The point of this exercise is to retain the spirit of the original
+ # $obj->search_related($rel) where the resulting rset will have the
+ # 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';
+ $obj_table_alias =~ s/\W+/_/g;
+
+ $source->resultset->search(
+ $self->ident_condition($obj_table_alias),
+ { alias => $obj_table_alias },
+ )->search_related('me', $query, $attrs)
}
-
- # this is where we're going to check if we have an extended
- # rel. In that case, we need to: 1) If there's a second
- # condition, we use that instead. 2) If there is only one
- # condition, we need to join the current resultsource and have
- # additional conditions.
- if (ref $rel_info->{cond} eq 'CODE') {
- # this is an extended relationship.
- if ($extended_cond) {
- $cond = $extended_cond;
-
- } else {
-
- # it's a bit hard to find out what to do with other joins
- $self->throw_exception('Extended relationship '.$rel.' with additional join requires optimized declaration')
- if exists $attrs->{join} && $attrs->{join};
-
- # aliases get a bit more complicated, so we won't accept additional queries
- $self->throw_exception('Extended relationship '.$rel.' with additional query requires optimized declaration')
- if $query;
-
- $attrs->{from} =
- [ { $rel => $self->result_source->from },
- [ { 'me' => $self->result_source->related_source($rel)->from }, { 1 => 1 } ] ];
-
- $cond->{"${rel}.${_}"} = $self->get_column($_) for $self->result_source->primary_columns;
+ 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 $DBIx::Class::ResultSource::UNRESOLVABLE_CONDITION) {
+ my $reverse = $source->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);
+ } else {
+ weaken($attrs->{related_objects}{$rev_rel} = $self);
+ }
+ }
}
- }
-
- if (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};
+ 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 {
+ $_;
}
- $hash;
- } else {
- $_;
+ } @$cond ];
+ }
+ elsif (ref $cond eq 'HASH') {
+ foreach my $key (grep { ! /\./ } keys %$cond) {
+ $cond->{"me.$key"} = delete $cond->{$key};
}
- } @$cond ];
- } elsif (ref $cond eq 'HASH') {
- foreach my $key (grep { ! /\./ } keys %$cond) {
- $cond->{"me.$key"} = delete $cond->{$key};
}
- }
- $query = ($query ? { '-and' => [ $cond, $query ] } : $cond);
- $self->result_source->related_source($rel)->resultset->search(
- $query, $attrs
- );
+ $query = ($query ? { '-and' => [ $cond, $query ] } : $cond);
+ $self->result_source->related_source($rel)->resultset->search(
+ $query, $attrs
+ );
+ }
};
}
sub new_related {
my ($self, $rel, $values, $attrs) = @_;
- return $self->search_related($rel)->new($values, $attrs);
+
+ # 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
+ ));
+ }
+ }
+
+ my $row = $self->search_related($rel)->new($values, $attrs);
+ return $row;
}
=head2 create_related
sub create_related {
my $self = shift;
my $rel = shift;
- my $obj = $self->search_related($rel)->create(@_);
+ my $obj = $self->new_related($rel, @_)->insert;
delete $self->{related_resultsets}->{$rel};
return $obj;
}
sub set_from_related {
my ($self, $rel, $f_obj) = @_;
- my $rel_info = $self->relationship_info($rel);
- $self->throw_exception( "No such relationship ${rel}" ) unless $rel_info;
- my $cond = $rel_info->{cond};
- $self->throw_exception(
- "set_from_related can only handle a hash condition; the ".
- "condition for $rel is of type ".
- (ref $cond ? ref $cond : 'plain scalar')
- ) unless ref $cond eq 'HASH';
+
+ 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);
}
- # _resolve_condition might return two hashrefs, specially in the
- # current case, since we know $f_object is an object.
- my ($condref1, $condref2) = $self->result_source->_resolve_condition
- ($rel_info->{cond}, $f_obj, $rel);
- # if we get two condrefs, we need to use the second, otherwise we
- # use the first.
- $self->set_columns($condref2 ? $condref2 : $condref1);
+ # 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);
return 1;
}