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]