Peter Rabbitson wrote:
David Ihnen wrote:
Matt S Trout wrote:
On Tue, Apr 21, 2009 at 10:59:48AM -0700, David Ihnen wrote:
Which is why:

->create_related('ivr_holiday', { holiday => date, description => $description });

is called. but does not create an error on groups when

->create_related('ivr_holiday', { holiday => date, description => $description, donor => undef });
That change is correct - as I say, DBIC can't tell what's going to be in
the database so it errs on the side of paranoia.

Better to die() than to silently return the wrong thing.

Yes, but it does not explain why the groups relationship doesn't throw
the same error that the donor relationship did throw, when it is
configured identically with the same sort of relationships.

Surely its a bug if only the first relationship is checked and not all
of them?


It might be, but there is no way to tell unless you show us _complete_
invocation code for both the group and the holiday stuff. There got to
be at least something that is different.
... assuming you don't want to sort through 788 K of the various tangled-together web of data model definition, nor sure that I wouldn't get in trouble for sharing it... I attempt to explain the complete details of the invocation...

To start with, there is no code invoking the group stuff. The group is null in this case, it has no relevancy to the insert occuring. I can't show you code invoking the relation groups for object ivr_holiday in conjuction with a client insert as there is none! The point is that the die should have thrown *because* there is none, right?

in application package Samp::CreateDemoUser, after creating a series of client objects, we invoke by calling this helper method on a client row object.

$client->add_ivr_holiday({holiday => DateTime->new({year => 2007, day => 25, month => 5}), description => 'Memorial Day'});

The implimentation of the helper reveals the defaulting change we added that made the die go away for the donor relation. Note that the invocation does not have a group_id nor does the client object wrapper add such an entry. The only relevant relation here is that of ivr_holiday - to my understanding the relation checking code will be checking the definition for the table that it is inserting into.

package DB::Schema::client;
use DB::Schema::notice;
use base qw/DB::Schema/;
use strict;
use Carp;
use Data::Dumper;

__PACKAGE__->load_components(qw/PK::Auto Core/);
__PACKAGE__->table('client');
__PACKAGE__->add_columns('id');
__PACKAGE__->set_primary_key('id');
__PACKAGE__->add_columns(qw/code name short_name address address2 city state zip lft rgt phone ext contact email collection_site/); __PACKAGE__->has_many(clients => 'DB::Schema::client' , 'id'); __PACKAGE__->has_many(settings => 'DB::Schema::client_settings' , 'client_id'); __PACKAGE__->has_many(samp_user => 'DB::Schema::samp_user' , 'client_id'); __PACKAGE__->has_many(titles => 'DB::Schema::samp_user_titles' , 'client_id'); __PACKAGE__->has_many(users => 'DB::Schema::samp_user' , 'client_id'); __PACKAGE__->has_many(cocs => 'DB::Schema::coc' , 'client_id'); __PACKAGE__->has_many(panels => 'DB::Schema::client_panel' , 'client_id'); __PACKAGE__->has_many(tests => 'DB::Schema::client_test' , 'client_id'); __PACKAGE__->has_many(ivr_holiday => 'DB::Schema::ivr_holiday' , 'client_id'); __PACKAGE__->has_many(profiles => 'DB::Schema::client_profile' , 'client_id'); __PACKAGE__->has_many(ivr_numbers => 'DB::Schema::client_ivr_numbers' , 'client_id'); __PACKAGE__->has_many(ivr_servers => 'DB::Schema::client_ivr_info' , 'client_id'); __PACKAGE__->has_many(laboratories => 'DB::Schema::client_laboratories' , 'client_id'); __PACKAGE__->has_many(collection_sites => 'DB::Schema::client_collection_sites' , 'client_id');

... several hundred lines of other helper methods ...

sub add_ivr_holiday {
 my $self = shift;
 my $record = shift;

 $record->{donor_id} = undef unless $record->{donor_id};

 $self->txn( sub {
   $self->create_related('ivr_holiday', $record);
 } );
}

txn is a custom txn_do idea transaction handler that helps enforce the application rule that transactions return a consistent interface pattern so that other application logic can do things like present some sort of consistent errors to the end user. Functionally it just executes the sub referenced passed to it.

package DB::Schema;

my $txndepth = 0;

sub txn {
 my $self = shift;
 my $subroutine = shift;


 my $schema = $self->schema if ($self->can('schema'));
 $schema = $self->result_source->schema if ($self->can('result_source'));

 confess "No storage in $self - huh? ($schema)" unless ($schema);

 $txndepth ++;
my @extra = eval { $schema->txn_do(sub { my @e = $subroutine->(); $self->commit_precommit; @e; } ); };
 $txndepth --;

 if ($@) {
   Carp::cluck "Caught error in txn:\n$@ $!";
   %PRECOMMIT = ();
   return
     { success => 0
     , errorfield => "GENERAL",
, errormsg => "A database error has ocurred - Please contact support"
     , dollarat => $@
     , @_
     , @extra
     };
 } else {
   @extra % 2  # so we know we got a hash
     ? return { success => 1 }
     : return { success => 1 , @extra};
 }
}


And here is the table definition for the table in question that is not throwing all the expected dies when relationships are not specified. As you see, client, donor, and groups are all belongs-to defined identically. Yet, when inserted with unspecified groups/group_id values, the library does not die telling us we have a possible problem.

package DB::Schema::ivr_holiday;
use base qw/DB::Schema/;
use strict;

__PACKAGE__->load_components(qw/DonorEvent PK::Auto DateTimeInflate Core/);

__PACKAGE__->table('ivr_holiday');

__PACKAGE__->add_columns('id');
__PACKAGE__->set_primary_key('id');

__PACKAGE__->add_columns(qw/client_id group_id donor_id description must_call/);
__PACKAGE__->add_columns('holiday' => { data_type => 'date' });

__PACKAGE__->belongs_to(client => 'DB::Schema::client', { 'foreign.id' => 'self.client_id' }, { join_type => 'left' } ); __PACKAGE__->belongs_to(donor => 'DB::Schema::donor', { 'foreign.id' => 'self.donor_id' }, { join_type => 'left' } ); __PACKAGE__->belongs_to(groups => 'DB::Schema::groups', { 'foreign.id' => 'self.group_id' }, { join_type => 'left' } );

__PACKAGE__->might_have(de_bridge => 'DB::Schema::de_to_ivr_holiday', 'ivr_holiday_id');

sub donor_event_name {
 "IVR Holiday Modification";
}

sub donor_event_time {
 return \"DEFAULT";
}

sub allow_delete {
 return 1;
}

1;

AND lastly, since I do have the DonorEvent custom plugin utilized in the ivr_holiday table, here is its code, so that if inclined you can verify that it doesn't actually add a group_id/groups information to the record on the way past.

package DBIx::Class::DonorEvent;
use base qw(DBIx::Class);
use strict;
use Data::Dumper;
use Carp;

sub set_column {
   my $self = shift;

   $self->audit_column_change(@_);

   $self->next::method( @_ );
}

sub store_column {
   my $self = shift;

   $self->audit_column_change(@_);

   $self->next::method( @_ );
}

sub insert {
 my $self = shift;

 my @a;
 if (wantarray) {
   @a = $self->next::method(@_);
 } elsif (defined wantarray) {
   $a[0] = $self->next::method(@_);
 } else {
   $self->next::method(@_);
 }

 my @rows = $self->donor_event_rows;
 $self->add_to_precommit
   ( 'donor_event_for_insert_'.$self->table.'_id_'.$self->id
   , sub {
     foreach my $event ( grep { $_->isa('DB::Schema') } @rows ) {
$event->create_related( 'raw_change_data', $_ ) foreach ( @{$self->{'possible_raw_changes'} || []} );
       $self->associate_donor_event($event);
     }
   } ) if (@rows);

 wantarray ? @a : $a[0];
}

sub associate_donor_event {
 my $self = shift;
 my $event = shift;
#  print STDERR "ROOT DONOR EVENT ASSOCIATE $self\n";
 $self->create_related( 'de_bridge', { donor_event => $event } );
}

sub update {
 my $self = shift;

 $self->next::method(@_);

 my @rows = $self->donor_event_rows;
 $self->add_to_precommit
   ( 'donor_event_for_update_'.$self->table.'_id_'.$self->id
   , sub {
     foreach my $event ( grep { $_->isa('DB::Schema') } @rows ) {
$event->create_related( 'raw_change_data', $_ ) foreach (@{$self->{'possible_raw_changes'} || []});
     }
   } );

 $self;
}

sub whatis {
   my $v = shift;
   if (defined($v)) {
       if ($v eq '') {
           return 'emptystring'
       }
       return $v;
   }
   return 'undefined'
}

sub audit_column_change {
   my $self = shift;

   my $column = shift;

   return if $self->{'audited'}{$column} ++;

   my $new = shift;
   return if (ref $new);
   undef $new if ($new eq '');
   # Do stuff with $self, like set default values.
   my $old = $self->get_column($column);

if ($column ne 'id' && (defined($old) ^ defined($new) || $old ne $new)) {

       if (Samp::instance()) {
           unless (Samp::instance()->AUTHED_USER) {
carp "No authenticated user available. Not auditing column change";
               return;
           }
       } else {
           carp "No Samp instance available.  Not auditing column change";
           return;
       }


Samp::debug(32, ref($self) . " Audit: COLUMN $column, changed from " . whatis($old) . " to " . whatis($new) . "\n");

       push @{$self->{'possible_raw_changes'}},
           ,   {   samp_user     => Samp::instance()->AUTHED_USER
               ,   change_table  => $self->table()
               ,   change_column => $column
               ,   data_old      => $old
               ,   data_new      => $new
               }
   }

}

sub donor_event_rows {
   my $self = shift;
return @{$self->{'current_donor_event'} || []} if ($self->{'current_donor_event'});
   unless (Samp::instance()) {
carp "No Samp instance available. There cannot be a donor event row.";
       return ();
       return Samp::Nothing::Donor->new("No samp instance");
   }

   unless (Samp::instance()->AUTHED_USER) {
       carp "No authenticated user available.  Not auditing column change";
       return ();
       return Samp::Nothing::Donor->new("No Authenticated User");
   }

   my @donors = $self->donor_event_donors;

   foreach my $donor (@donors) {
     my $related = Samp::Nothing::Donor->new("No Donor ID");

     unless ($donor->id) {
carp "No donor info available. Not auditing column change (table " . $self->table . ")";
     } else {
       $related = $donor->create_related
         ( 'donor_event'
         , { samp_user => Samp::instance()->AUTHED_USER
           , event_class => $self->donor_event_name()
           , event_time => $self->donor_event_time
           }
         );
     }
     push @{$self->{'current_donor_event'}}, $related;
   }
   @{$self->{'current_donor_event'} || []};
}

sub donor_event_donors {
 my $self = shift;
 my $donor = $self->can('donor') && $self->donor;
 $donor ||= $self->table eq 'donor' && $self;
 $donor ||= Samp::instance()->REQUESTED_DONOR;
}

sub donor_event_name {
   my $self = shift;
confess "unknown donor event name in class $self - define method 'donor_event_name'";
}

sub delete {
   my $self = shift;
confess "Attempt to delete a donor event monitored record when not allowed!\n" unless ($self->allow_delete);
   return $self->next::method( @_ );
}

sub allow_delete {
 return Samp::instance()->AUTHED_USER->is_norchem;
}

1;


Is more completeness needed?

David


_______________________________________________
List: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/dbix-class
IRC: irc.perl.org#dbix-class
SVN: http://dev.catalyst.perl.org/repos/bast/DBIx-Class/
Searchable Archive: http://www.grokbase.com/group/[email protected]

Reply via email to