Working with triggers

From ClassDBI

One thing to take care of while using triggers is not to use 'set'/'get' and standard accessors/mutators on the object that caused the triggering, and on other objects you fetch from the database while you are in a triggered routine. If you do use standard accessors/mutators you might get error messages like this:

MonitorData=HASH(0x6c3498) destroyed without saving changes to end_date at 
/usr/local/perl-5.6.0/lib/site_perl/5.6.0/Class/Trigger.pm line 51

and see the objects you tried to change haven't changed.

Instead use the 'private' methods described in the Class::DBI documentation for accessing/mutating data:

_attrs
_attribute_store
_attribute_set
_attribute_delete
_attribute_exists

-- EmileAben


Actually, be sure to not use the _attribute_set, but rather the _attribute_store method so the change isn't logged for updating the database (which will trigger the warning).

(The gist of the contents of this page should be folded into the CDBI pod section TRIGGERS, because it's when reading about triggers that this information is useful. Please don't lose the error message, that's how I found the solution to my problem (thanks Emile! :) )

--JohanLindstrom



If you want to turn off triggers in some class temporarily (e.g., oif you are migrating data), here is a nice idiom from Matt S. Trout (Class::Triggers has no support for triggers although the author, Tatsuhiko Miyagawa was pondering it).

In your base class:

 use vars qw/$NO_TRIGGERS/;
 # Override call_trigger to check flag
 sub call_trigger {
   return if $NO_TRIGGERS;
   return shift->SUPER::call_trigger(@_);
 }

Somewhere far far away ...

 local $My::Base::Class::NO_TRIGGERS = 1;
 ...



Occasionally you may need an after_update() or before_update() trigger that updates data in your object and where you also want other triggers to fire (eg constraint triggers) but don't want to go into an infinite loop :)

If you turn off all triggers as above then you don't get your per-column constraints checked. If you are running with autoupdate on and call _attribute_set() then your data is saved in the object but not in the database - this can be counterintuitive as you have to call update() on some but not all set() operations.

The solution? Selectively turn off the update triggers whilst in an update trigger and then you're free to use set() so that all other triggers fire and the changes are flushed to the database all at the same time.

In your base class:

 use vars qw/$DISABLE_TRIGGERS/;
 # override call_trigger to check flag or hash of flags
 # allows either a $DISABLE_TRIGGERS=1 to disable all or
 # a hash of trigger names to specifically disable.
 sub call_trigger {
   my $self = shift;
   return if ( ( !ref($DISABLE_TRIGGERS) && $DISABLE_TRIGGERS ) ||
               ( ref($DISABLE_TRIGGERS) eq 'HASH' && $DISABLE_TRIGGERS->{$_[0]} )
             );
   return $self->SUPER::call_trigger(@_);
 }

In your class code:

 sub _recalc_derived_fields {
   my $self = shift;
   
   # turn off cascading update triggers.
   local $My::Base::Class::DISABLE_TRIGGERS = { after_update => 1 };
   
   # do all calculations then a single $self->set()
 }
 __PACKAGE__->add_trigger( after_create => \&_recalc_derived_fields );
 __PACKAGE__->add_trigger( after_update => \&_recalc_derived_fields );

--AndrewOBrien