X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits%2FDBIx-Class.git;a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FRelationship%2FBase.pm;h=feabdc7c5a39179998fd9b26c2db8b569e5b2ad3;hp=41a8fec9e5cf3aa65b2c9342104f471d3556cd74;hb=69bc5f2b82b4a4f027cd9d57c38c25dc4e0b72c0;hpb=6fbef4a4db1f453046b0450c485c495d743030d5 diff --git a/lib/DBIx/Class/Relationship/Base.pm b/lib/DBIx/Class/Relationship/Base.pm index 41a8fec..feabdc7 100644 --- a/lib/DBIx/Class/Relationship/Base.pm +++ b/lib/DBIx/Class/Relationship/Base.pm @@ -167,9 +167,9 @@ L and the resulting SQL will be used verbatim as the C clause of the C statement associated with this relationship. While every coderef-based condition must return a valid C clause, it may -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 +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: @@ -219,11 +219,11 @@ 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 row object), + 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 $row_obj->relationship + self_rowobj => The invocant itself in case of a $result_object->$relationship call }); =head3 attributes @@ -249,6 +249,12 @@ command immediately before C. =item proxy =E $column | \@columns | \%column +The 'proxy' attribute can be used to retrieve values, and to perform +updates if the relationship has 'cascade_update' set. The 'might_have' +and 'has_one' relationships have this set by default; if you want a proxy +to update across a 'belongs_to' relationship, you must set the attribute +yourself. + =over 4 =item \@columns @@ -256,33 +262,41 @@ command immediately before C. 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 +For a 'belongs_to relationship, note the 'cascade_update': + + MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd, + { proxy => ['title'], cascade_update => 1 } + ); + $track->title('New Title'); + $track->update; # updates title in CD + =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. - MyDB::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', { + MyApp::Schema::Track->belongs_to( cd => 'DBICTest::Schema::CD', 'cd', { proxy => { cd_title => 'title' }, }); -This will create an accessor named C on the C<$track> row object. +This will create an accessor named C on the C<$track> result object. =back 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' } ], }); @@ -331,6 +345,10 @@ C relationships. You can disable this behaviour on a per-relationship basis by supplying C<< cascade_update => 0 >> in the relationship attributes. +The C 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 >>. + 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 have update called. It will not change foreign keys automatically - @@ -368,7 +386,7 @@ L. Default is on, set to 0 to disable. =over 4 -=item Arguments: $relname, $rel_info +=item Arguments: $rel_name, $rel_info =back @@ -383,16 +401,41 @@ sub register_relationship { } =over 4 -=item Arguments: $relationship_name +=item Arguments: $rel_name -=item Return Value: $related_resultset +=item Return Value: L<$related_resultset|DBIx::Class::ResultSet> =back $rs = $cd->related_resultset('artist'); Returns a L for the relationship named -$relationship_name. +$rel_name. + +=head2 $relationship_accessor + +=over 4 + +=item Arguments: none + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | L<$related_resultset|DBIx::Class::ResultSet> | undef + +=back + + # These pairs do the same thing + $row = $cd->related_resultset('artist')->single; # has_one relationship + $row = $cd->artist; + $rs = $cd->related_resultset('tracks'); # has_many relationship + $rs = $cd->tracks; + +This is the recommended way to transverse through relationships, based +on the L name given in the relationship definition. + +This will return either a L or a +L, depending on if the relationship is +C (returns only one row) or C (returns many rows). The +method may also return C if the relationship doesn't exist for +this instance (like in the case of C relationships). =cut @@ -417,14 +460,8 @@ sub related_resultset { # 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) { @@ -434,49 +471,46 @@ sub related_resultset { $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) } - - if (ref $rel_info->{cond} eq 'CODE' && !$extended_cond) { - # since we don't have the extended condition, we need to step - # back, get a resultset for the current row and do a - # search_related there. - - my $row_srcname = $source->source_name; - my $base_rs_class = $source->resultset_class; - my $base_rs_attr = $source->resultset_attributes; - my $base_rs = $base_rs_class->new - ($source, - { - %$base_rs_attr, - alias => $source->storage->relname_to_table_alias(lc($row_srcname).'__row',1) - }); - my $alias = $base_rs->current_source_alias; - my %identity = map { ( "${alias}.${_}" => $self->get_column($_) ) } $source->primary_columns; - my $row_rs = $base_rs->search(\%identity); - my $related = $row_rs->related_resultset($rel, { %$attrs, alias => 'me' }); - $related->search($query); - - } else { - # when we have the extended condition or we have a simple - # relationship declaration, it can optimize the JOIN away by - # simply adding the identity in WHERE. - - if (ref $rel_info->{cond} eq 'CODE' && $extended_cond) { - $cond = $extended_cond; + 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') { + elsif (ref $cond eq 'ARRAY') { $cond = [ map { if (ref $_ eq 'HASH') { my $hash; @@ -489,27 +523,36 @@ sub related_resultset { $_; } } @$cond ]; - } elsif (ref $cond eq 'HASH') { - foreach my $key (grep { ! /\./ } keys %$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, $attrs + ); } }; } =head2 search_related - @objects = $rs->search_related('relname', $cond, $attrs); - $objects_rs = $rs->search_related('relname', $cond, $attrs); +=over 4 + +=item Arguments: $rel_name, $cond?, L<\%attrs?|DBIx::Class::ResultSet/ATTRIBUTES> + +=item Return Value: L<$resultset|DBIx::Class::ResultSet> (scalar context) | L<@result_objs|DBIx::Class::Manual::ResultClass> (list context) + +=back Run a search on a related resultset. The search will be restricted to the -item or items represented by the L it was called -upon. This method can be called on a ResultSet, a Row or a ResultSource class. +results represented by the L it was called +upon. + +See L for more information. =cut @@ -519,8 +562,6 @@ sub search_related { =head2 search_related_rs - ( $objects_rs ) = $rs->search_related_rs('relname', $cond, $attrs); - This method works exactly the same as search_related, except that it guarantees a resultset, even in list context. @@ -532,12 +573,16 @@ sub search_related_rs { =head2 count_related - $obj->count_related('relname', $cond, $attrs); +=over 4 -Returns the count of all the items in the related resultset, restricted by the -current item or where conditions. Can be called on a -L or a -L object. +=item Arguments: $rel_name, $cond?, L<\%attrs?|DBIx::Class::ResultSet/ATTRIBUTES> + +=item Return Value: $count + +=back + +Returns the count of all the rows in the related resultset, restricted by the +current result or where conditions. =cut @@ -548,77 +593,91 @@ sub count_related { =head2 new_related - my $new_obj = $obj->new_related('relname', \%col_data); +=over 4 + +=item Arguments: $rel_name, \%col_data + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> + +=back -Create a new item of the related foreign class. If called on a -L object, it will magically -set any foreign key columns of the new object to the related primary -key columns of the source object for you. The newly created item will -not be saved into your storage until you call L -on it. +Create a new result object of the related foreign class. It will magically set +any foreign key columns of the new object to the related primary key columns +of the source object for you. The newly created result will not be saved into +your storage until you call L on it. =cut sub new_related { - my ($self, $rel, $values, $attrs) = @_; - return $self->search_related($rel)->new($values, $attrs); + 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); } =head2 create_related - my $new_obj = $obj->create_related('relname', \%col_data); +=over 4 + +=item Arguments: $rel_name, \%col_data + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> + +=back + + my $result = $obj->create_related($rel_name, \%col_data); -Creates a new item, similarly to new_related, and also inserts the item's data -into your storage medium. See the distinction between C and C -in L for details. +Creates a new result object, similarly to new_related, and also inserts the +result's data into your storage medium. See the distinction between C +and C in L for details. =cut sub create_related { my $self = shift; my $rel = shift; - - $self->throw_exception("Can't call *_related as class methods") - unless ref $self; - - # we need to stop and check if this is at all possible. If this is - # an extended relationship with an incomplete definition, we should - # just forbid it right now. - my $rel_info = $self->result_source->relationship_info($rel); - if (ref $rel_info->{cond} eq 'CODE') { - my ($cond, $ext) = $rel_info->{cond}->({ - self_alias => 'me', - foreign_alias => $rel, - self_rowobj => $self, - self_resultsource => $self->result_source, - foreign_relname => $rel, - }); - $self->throw_exception("unable to set_from_related - no simplified condition available for '${rel}'") - unless $ext; - - # now we need to make sure all non-identity relationship - # definitions are overriden. - my ($argref) = @_; - while ( my($col, $value) = each %$ext ) { - $col =~ s/^$rel\.//; - my $vref = ref $value; - if ($vref eq 'HASH') { - if (keys(%$value) && (keys %$value)[0] ne '=' && - !exists $argref->{$col}) { - $self->throw_exception("unable to set_from_related via complex '${rel}' condition on column(s): '${col}'") - } - } - } - } - - my $obj = $self->search_related($rel)->create(@_); + my $obj = $self->new_related($rel, @_)->insert; delete $self->{related_resultsets}->{$rel}; return $obj; } =head2 find_related - my $found_item = $obj->find_related('relname', @pri_vals | \%pri_vals); +=over 4 + +=item Arguments: $rel_name, \%col_data | @pk_values, { key => $unique_constraint, L<%attrs|DBIx::Class::ResultSet/ATTRIBUTES> }? + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> | undef + +=back + + my $result = $obj->find_related($rel_name, \%col_data); Attempt to find a related object using its primary key or unique constraints. See L for details. @@ -633,11 +692,16 @@ sub find_related { =head2 find_or_new_related - my $new_obj = $obj->find_or_new_related('relname', \%col_data); +=over 4 + +=item Arguments: $rel_name, \%col_data, { key => $unique_constraint, L<%attrs|DBIx::Class::ResultSet/ATTRIBUTES> }? + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> -Find an item of a related class. If none exists, instantiate a new item of the -related class. The object will not be saved into your storage until you call -L on it. +=back + +Find a result object of a related class. See L +for details. =cut @@ -649,9 +713,15 @@ sub find_or_new_related { =head2 find_or_create_related - my $new_obj = $obj->find_or_create_related('relname', \%col_data); +=over 4 + +=item Arguments: $rel_name, \%col_data, { key => $unique_constraint, L<%attrs|DBIx::Class::ResultSet/ATTRIBUTES> }? -Find or create an item of a related class. See +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> + +=back + +Find or create a result object of a related class. See L for details. =cut @@ -664,9 +734,15 @@ sub find_or_create_related { =head2 update_or_create_related - my $updated_item = $obj->update_or_create_related('relname', \%col_data, \%attrs?); +=over 4 + +=item Arguments: $rel_name, \%col_data, { key => $unique_constraint, L<%attrs|DBIx::Class::ResultSet/ATTRIBUTES> }? + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> + +=back -Update or create an item of a related class. See +Update or create a result object of a related class. See L for details. =cut @@ -679,6 +755,14 @@ sub update_or_create_related { =head2 set_from_related +=over 4 + +=item Arguments: $rel_name, L<$result|DBIx::Class::Manual::ResultClass> + +=item Return Value: not defined + +=back + $book->set_from_related('author', $author_obj); $book->author($author_obj); ## same thing @@ -697,34 +781,51 @@ set them in the storage. 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; } =head2 update_from_related +=over 4 + +=item Arguments: $rel_name, L<$result|DBIx::Class::Manual::ResultClass> + +=item Return Value: not defined + +=back + $book->update_from_related('author', $author_obj); The same as L, but the changes are immediately updated @@ -740,9 +841,20 @@ sub update_from_related { =head2 delete_related - $obj->delete_related('relname', $cond, $attrs); +=over 4 + +=item Arguments: $rel_name, $cond?, L<\%attrs?|DBIx::Class::ResultSet/ATTRIBUTES> + +=item Return Value: $underlying_storage_rv + +=back + +Delete any related row, subject to the given conditions. Internally, this +calls: + + $self->search_related(@_)->delete -Delete any related item subject to the given conditions. +And returns the result of that. =cut @@ -755,36 +867,60 @@ sub delete_related { =head2 add_to_$rel -B, C and 'multi' type +B, C and 'multi' type relationships.> +=head3 has_many / multi + =over 4 -=item Arguments: ($foreign_vals | $obj), $link_vals? +=item Arguments: \%col_data + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> + +=back + +Creates/inserts a new result object. Internally, this calls: + + $self->create_related($rel, @_) + +And returns the result of that. + +=head3 many_to_many + +=over 4 + +=item Arguments: (\%col_data | L<$result|DBIx::Class::Manual::ResultClass>), \%link_col_data? + +=item Return Value: L<$result|DBIx::Class::Manual::ResultClass> =back my $role = $schema->resultset('Role')->find(1); $actor->add_to_roles($role); - # creates a My::DBIC::Schema::ActorRoles linking table row object + # creates a My::DBIC::Schema::ActorRoles linking table result object $actor->add_to_roles({ name => 'lead' }, { salary => 15_000_000 }); - # creates a new My::DBIC::Schema::Role row object and the linking table + # creates a new My::DBIC::Schema::Role result object and the linking table # object with an extra column in the link -Adds a linking table object for C<$obj> or C<$foreign_vals>. If the first -argument is a hash reference, the related object is created first with the -column values in the hash. If an object reference is given, just the linking -table object is created. In either case, any additional column values for the -linking table object can be specified in C<$link_vals>. +Adds a linking table object. If the first argument is a hash reference, the +related object is created first with the column values in the hash. If an object +reference is given, just the linking table object is created. In either case, +any additional column values for the linking table object can be specified in +C<\%link_col_data>. + +See L for additional details. =head2 set_$rel -B relationships.> +B relationships.> =over 4 -=item Arguments: (\@hashrefs | \@objs), $link_vals? +=item Arguments: (\@hashrefs_of_col_data | L<\@result_objs|DBIx::Class::Manual::ResultClass>), $link_vals? + +=item Return Value: not defined =back @@ -814,25 +950,27 @@ removed in a future version. =head2 remove_from_$rel -B relationships.> +B relationships.> =over 4 -=item Arguments: $obj +=item Arguments: L<$result|DBIx::Class::Manual::ResultClass> + +=item Return Value: not defined =back my $role = $schema->resultset('Role')->find(1); $actor->remove_from_roles($role); - # removes $role's My::DBIC::Schema::ActorRoles linking table row object + # removes $role's My::DBIC::Schema::ActorRoles linking table result object Removes the link between the current object and the related object. Note that 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 AUTHORS +=head1 AUTHOR AND CONTRIBUTORS -Matt S. Trout +See L and L in DBIx::Class =head1 LICENSE