[prev] [thread] [next] [Date index for 2004/05/12]
--=-+NbIib+olWieWBrc3ud5 Content-Type: text/plain Content-Transfer-Encoding: 7bit > I'm not against including it in HasMany itself, as it should be safely > backwards-compatible - although I would like to see some tests in > that case. I'm attaching a HasMany.pm with the extra functionality. Also a modified 09-has_many.t with relevant checks added. Replacing both files in the distribution and running 'make test' should work. Thanks for considering this. Regards, cv --=-+NbIib+olWieWBrc3ud5 Content-Disposition: attachment; filename=HasMany.pm Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; name=HasMany.pm; charset=ISO-8859-1 package Class::DBI::Relationship::HasMany; use strict; use warnings; use base 'Class::DBI::Relationship'; sub remap_arguments { my ($proto, $class, $accessor, $f_class, $f_key, $args) =3D @_; =20 my %on_delete_beh =3D ( 'cascade' =3D> 1, 'set-null' =3D> 1, 'restrict' =3D> 1, ); =20 return $class->_croak("has_many needs an accessor name") unless $access= or; return $class->_croak("has_many needs a foreign class") unless $f_clas= s; $class->can($accessor) and return $class->_carp("$accessor method already exists in $class\n"); =20 my @f_method =3D (); if (ref $f_class eq "ARRAY") { ($f_class, @f_method) =3D @$f_class; } $class->_require_class($f_class); =20 if (ref $f_key eq "HASH") { # didn't supply f_key, this is really $a= rgs $args =3D $f_key; $f_key =3D ""; } =20 $f_key ||=3D do { my $meta =3D $f_class->meta_info('has_a'); my ($col) =3D grep $meta->{$_}->foreign_class eq $class, keys %$meta; $col || $class->table_alias; }; if (ref $f_key eq "ARRAY") { return $class->_croak("Multi-column foreign keys not supported") if @$f_key > 1; $f_key =3D $f_key->[0]; } =20 $args ||=3D {}; $args->{mapping} =3D \@f_method; $args->{foreign_key} =3D $f_key; $args->{order_by} ||=3D $args->{sort}; =20 warn "sort argumemt to has_many deprecated in favour of order_by" if $args->{sort}; =20 # Make 'cascade' the default on_delete behaviour $args->{on_delete} ||=3D "cascade"; =20 unless ( exists $on_delete_beh{$args->{on_delete}} ){ return $class->_croak("Unknown on_delete behavior: $args->{on_delete}"); } =20 return ($class, $accessor, $f_class, $args); } sub _set_up_class_data { my $self =3D shift; $self->class->_extend_class_data( __hasa_list =3D> $self->foreign_class =3D> $self->args->{foreign_k= ey}); $self->SUPER::_set_up_class_data; } sub triggers { my $self =3D shift; if ($self->args->{on_delete} eq "restrict"){ return ( before_delete =3D> sub { if ( scalar $self->foreign_class->search($self->args->{foreign_key} = =3D> shift->id) ){ return $self->class->_croak("Deletion restricted for referential integri= ty"); } }); =09 }elsif ($self->args->{on_delete} eq "set-null"){ return ( before_delete =3D> sub { foreach ( $self->foreign_class->search($self->args->{foreign_key} =3D= > shift->id) ){ $_->set($self->args->{foreign_key}, 0); $_->update; } }); }elsif ($self->args->{on_delete} eq "cascade"){ return ( before_delete =3D> sub { $self->foreign_class->search($self->args->{foreign_key} =3D> shift->i= d) ->delete_all; }); } } sub methods { my $self =3D shift; my $accessor =3D $self->accessor; return ( $accessor =3D> $self->_has_many_method, "add_to_$accessor" =3D> $self->_method_add_to, ); } sub _method_add_to { my $self =3D shift; my $accessor =3D $self->accessor; return sub { my ($self, $data) =3D @_; my $class =3D ref $self or return $self->_croak("add_to_$accessor called as class method"); return $self->_croak("add_to_$accessor needs data") unless ref $data eq "HASH"; =09 my $meta =3D $class->meta_info(has_many =3D> $accessor); my ($f_class, $f_key, $args) =3D ($meta->foreign_class, $meta->args->{foreign_key}, $meta->args); $data->{$f_key} =3D $self->id; $f_class->create($data); }; } sub _has_many_method { my $self =3D shift; my $run_search =3D $self->_hm_run_search; my @mapping =3D @{ $self->args->{mapping} } or return $run_search; return sub { return $run_search->(@_)->set_mapping_method(@mapping) unless wantarray; my @ret =3D $run_search->(@_); foreach my $meth (@mapping) { @ret =3D map $_->$meth(), @ret } return @ret; } } sub _hm_run_search { my $self =3D shift; my ($class, $accessor) =3D ($self->class, $self->accessor); return sub { my ($self, @search_args) =3D @_; my $meta =3D $class->meta_info(has_many =3D> $accessor); my ($f_class, $f_key, $args) =3D ($meta->foreign_class, $meta->args->{foreign_key}, $meta->args); if (ref $self) { # For $artist->cds unshift @search_args, ($f_key =3D> $self->id); push @search_args, { order_by =3D> $args->{order_by} } if defined $args->{order_by}; return $f_class->search(@search_args); } else { # For Artist->cds # Cross-table join as class method # This stuff is highly experimental and will probably change beyond # recognition. Use at your own risk... my %kv =3D @search_args; my $query =3D Class::DBI::Query->new({ owner =3D> $f_class }); $query->kings($class, $f_class); $query->add_restriction(sprintf "%s.%s =3D %s.%s", $f_class->table_alias, $f_key, $class->table_alias, $class->primary_column); $query->add_restriction("$_ =3D ?") for keys %kv; my $sth =3D $query->run(values %kv); return $f_class->sth_to_objects($sth); } }; } 1; --=-+NbIib+olWieWBrc3ud5 Content-Disposition: attachment; filename=09-has_many.t Content-Transfer-Encoding: quoted-printable Content-Type: text/x-troff; name=09-has_many.t; charset=ISO-8859-1 use strict; use Test::More; BEGIN { eval "use DBD::SQLite"; plan $@ ? (skip_all =3D> 'needs DBD::SQLite for testing') : (tests =3D> 51= ); use lib 't/testlib'; use Film; use Actor; use Director; Director->CONSTRUCT; Film->CONSTRUCT; Actor->CONSTRUCT; Director->has_many(directedfilms =3D> Film =3D> 'Director', { order_by =3D= > 'title', on_delete =3D> 'restrict' }); Director->has_many(codirectedfilms =3D> Film =3D> 'CoDirector', { order_by= =3D> 'title', on_delete =3D> 'set-null' }); Film->has_many(actors =3D> Actor =3D> 'Film', { order_by =3D> 'name', on_d= elete =3D> 'cascade' }); Actor->has_a(Film =3D> 'Film'); Film->has_a(Director =3D> 'Director'); Film->has_a(CoDirector =3D> 'Director'); is(Actor->primary_column, 'id', "Actor primary OK"); } ok(Actor->can('Salary'), "Actor table set-up OK"); ok(Film->can('actors'), " and have a suitable actors method in Film"); ok(Director->can('IsInsane'), "Director table set-up OK"); ok(Director->can('directedfilms'), " and have a suitable directedfilms meth= od in Director"); ok(Director->can('codirectedfilms'), " and have a suitable codirectedfilms = method in Director"); ok(my $btaste =3D Film->retrieve('Bad Taste'), "We have Bad Taste"); ok( my $pvj =3D Actor->create( { Name =3D> 'Peter Vere-Jones', Film =3D> undef, Salary =3D> '30_000', # For a voice! } ), 'create Actor' ); ok( my $pjd =3D Director->create( { Name =3D> 'Peter Jackson', # Yeah, he's also the director Birthday =3D> undef, IsInsane =3D> '1', # no doubt } ), 'create Director' ); ok( my $mj =3D Director->create( { Name =3D> 'Michael Jackson', # whatever Birthday =3D> undef, IsInsane =3D> '1', # no comments } ), 'create CoDirector' ); is $pvj->Name, "Peter Vere-Jones", "PVJ name ok"; is $pvj->Film, undef, "No film"; ok $pvj->set_Film($btaste), "Set film"; $pvj->update; is $pvj->Film->id, $btaste->id, "Actor has film"; is $pjd->Name, "Peter Jackson", "PJ name ok"; is $mj->Name, "Michael Jackson", "MJ name ok"; ok $btaste->set('Director', $pjd), "Set Director"; ok $btaste->set('CoDirector', $mj), "Set CoDirector"; $btaste->update; is $btaste->Director->id, $pjd->id, "Film Has Director"; is $btaste->CoDirector->id, $mj->id, "Film Has CoDirector"; { my @actors =3D $btaste->actors; is(@actors, 1, "Bad taste has one actor"); is($actors[0]->Name, $pvj->Name, " - the correct one"); } { my @dfilms =3D $pjd->directedfilms; is(@dfilms, 1, "PJ has directed one film"); is($dfilms[0]->Title, $btaste->Title, " - the correct one"); } { my @cdfilms =3D $mj->codirectedfilms; is(@cdfilms, 1, "MJ has codirected one film"); is($cdfilms[0]->Title, $btaste->Title, " - the correct one"); } my %pj_data =3D ( Name =3D> 'Peter Jackson', Salary =3D> '0', # it's a labour of love ); eval { my $pj =3D Film->add_to_actors(\%pj_data) }; like $@, qr/class/, "add_to_actors must be object method"; eval { my $pj =3D $btaste->add_to_actors(%pj_data) }; like $@, qr/needs/, "add_to_actors takes hash"; ok( my $pj =3D $btaste->add_to_actors( { Name =3D> 'Peter Jackson', Salary =3D> '0', # it's a labour of love } ), 'add_to_actors' ); is $pj->Name, "Peter Jackson", "PJ ok"; is $pvj->Name, "Peter Vere-Jones", "PVJ still ok"; { my @actors =3D $btaste->actors; is @actors, 2, " - so now we have 2"; is $actors[0]->Name, $pj->Name, "PJ first"; is $actors[1]->Name, $pvj->Name, "PVJ first"; } eval { my @actors =3D $btaste->actors(Name =3D> $pj->Name); is @actors, 1, "One actor from restricted (sorted) has_many"; is $actors[0]->Name, $pj->Name, "It's PJ"; }; is $@, '', "No errors"; my $as =3D Actor->create( { Name =3D> 'Arnold Schwarzenegger', Film =3D> 'Terminator 2', Salary =3D> '15_000_000' } ); eval { $pjd->delete }; like $@, qr/restricted/, "delete restriction ok"; is($pjd->Name, "Peter Jackson", "Director still there"); is(($pjd->directedfilms)[0]->Title, "Bad Taste", "And his Film still there"= ); ok($mj->delete, "Delete Michael"); my @directors =3D Director->search(Name =3D> "Michael Jackson"); is(@directors, 0, " - Michael is gone"); is($btaste->CoDirector, 0, "CoDirector is gone"); eval { $btaste->actors($pj, $pvj, $as) }; ok $@, $@; is($btaste->actors, 2, " - so we still only have 2 actors"); my @bta_before =3D Actor->search(Film =3D> 'Bad Taste'); is(@bta_before, 2, "We have 2 actors in bad taste"); ok($btaste->delete, "Delete bad taste"); my @bta_after =3D Actor->search(Film =3D> 'Bad Taste'); is(@bta_after, 0, " - after deleting there are no actors"); # While we're here, make sure Actors have unreadable mutators and # unwritable accessors eval { $as->Name("Paul Reubens") }; ok $@, $@; eval { my $name =3D $as->set_Name }; ok $@, $@; is($as->Name, 'Arnold Schwarzenegger', "Arnie's still Arnie"); --=-+NbIib+olWieWBrc3ud5--
Re: disabling cascading delete
|
Re: disabling cascading delete
|
Re: disabling cascading delete
|
Generated at 11:34 on 01 Dec 2004 by mariachi v0.52