[Dbix-class] Relations on non in_storage rows (my todays gotcha)

Zbigniew Lukasiak zzbbyy at gmail.com
Sun May 11 22:09:51 BST 2008


On Sun, May 11, 2008 at 9:01 PM, luke saunders <luke.saunders at gmail.com> wrote:
> On Sun, May 11, 2008 at 5:48 PM, Zbigniew Lukasiak <zzbbyy at gmail.com> wrote:
>> On Sun, May 11, 2008 at 4:34 PM, Matt S Trout <dbix-class at trout.me.uk> wrote:
>>> On Sun, May 11, 2008 at 12:06:54PM +0200, Zbigniew Lukasiak wrote:
>>>> If you have a new object, not yet in the database, like:
>>>>
>>>> $new_cd = $schema->resultset("CD")->new( {} );
>>>>
>>>> Then if you call $new_cd->artist - this will return a first artist in
>>>> the 'artist' table.
>>>
>>> That's a bug, but the test we were given for it didn't work. Better test
>>> case welcome and we can fix it.
>>
>> There is a strange test in 66relationship.t:
>>
>> my $undef_artist_cd = $schema->resultset("CD")->new_result({ 'title'
>> => 'badgers', 'year' => 2007 });
>> is($undef_artist_cd->has_column_loaded('artist'), '', 'FK not loaded');
>> is($undef_artist_cd->search_related('artist')->count, 3, 'open search
>> on undef FK');
>>
>> So it tests that a new row has 3 related artists - I cannot understand
>> what that is supposed to mean.  I my opinion that count there should
>> be 0.
>>
>
> It was me that made that change after a discussion with Matt.
> Essentially the reasoning was that if the FK has not been set then an
> open search is okay as anything could, in theory, be related.
>
>> If we need another test then for example:
>>
>> my $new_cd = $schema->resultset("CD")->new( {} );
>> is( $new_cd->search_related( 'artist' )->first, undef, 'No artist
>> found for new cd' );
>>
>> Maybe I don't understand what is oing on here - but if my thinking is
>> right and the test 'open search on undef FK' is wrong than the fix
>> could be to change line 796 in lib/DBIx/Class/ResultSource.pm:
>>
>> -        $ret{$k} = $for->get_column($v) if $for->has_column_loaded($v);
>> +        $ret{$k} = $for->get_column($v);
>
> Yes, this was how it was before. When this was the case this code
>
>  my $rel_object = $object->search_related($belongs_to_rel, { $fk =>
> $value })->first;
>
> would result in $rel_object being undef even if there was a related
> row corresponding to $value - the SQL would look something like
>
>  ... WHERE (PK IS NULL) AND (PK = $value);
>
> This was causing problems with nested inserts as this test shows
>
>  my $new_artist = $schema->resultset("Artist")->create({ artistid =>
> 18, name => 'larry' });
>
>  eval {
>    $schema->resultset("CD")->create({
>              cdid => 28,
>               title => 'Boogie Wiggle',
>              year => '2007',
>              artist => { artistid => 18, name => 'larry' }
>             });
>  };
>  is($@, '', 'new cd created without clash on related artist');
>
> With the old way the artist would be inserted twice as the
> find_or_new_related for the second insert would not find the existing
> artist with id 18, and this would of course cause a PK clash.
>
> I have just added this test to t/96multi_create.t so that this bug
> doesn't arise again if we revise the behaviour.
>
> So that's the background to this problem. If the open search seems
> unintuitive to people then probably we should revise/revert the
> behaviour but we need to think about search_related and
> find_or_new_related.


How about setting the new object's columns before calling
find_or_new_related in Row->new ?

Fortunately resolve_condition works on hash_refs instead of objects so:

Index: lib/DBIx/Class/Row.pm
===================================================================
--- lib/DBIx/Class/Row.pm       (revision 4379)
+++ lib/DBIx/Class/Row.pm       (working copy)
@@ -107,6 +107,9 @@
           ## 'filter' should disappear and get merged in with 'single' above!
           my $rel_obj = delete $attrs->{$key};
           if(!Scalar::Util::blessed($rel_obj)) {
+            $new->set_columns(
+              $new->result_source->resolve_condition(
+                $info->{cond}, $rel_obj, $key));
             $rel_obj = $new->find_or_new_related($key, $rel_obj);
             $new->{_rel_in_storage} = 0 unless ($rel_obj->in_storage);
           }


(and of course for other relationship types - 'multi' and 'single' the same)

Now find_or_new_related finds the related row as advertised and your
new test in 96multi_create.t is satisfied without allowing every row
in Artist to be relate to the new CD record.  It seems to not break
any more tests.

By the way podcoverage.t and broken_single_accessor.t don't pass.

-- 
Zbigniew Lukasiak
http://brudnopis.blogspot.com/



More information about the DBIx-Class mailing list