UR::Observer(3pm)

πŸ“¦
version 0.470 Β· 2 versions
, in debian bookworm, ubuntu jammy, ubuntu noble

πŸš€ UR::Observer β€” bind callbacks to object changes πŸ’–

okay so bestie, welcome to UR::Observer, part of the User Contributed Perl Documentation collection, and honestly? it's giving observer pattern realness. no cap.


✨ SYNOPSIS ✨

$rocket = Acme::Rocket->create(
    fuel_level => 100
);

$observer = $rocket->add_observer(
    aspect => 'fuel_level',
    callback =>
        sub {
            print "fuel level is: " . shift->fuel_level . "\n"
        },
    priority => 2,
);

$observer2 = UR::Observer->create(
    subject_class_name => 'Acme::Rocket',
    subject_id    => $rocket->id,
    aspect => 'fuel_level',
    callback =>
        sub {
            my($self,$changed_aspect,$old_value,$new_value) = @_;
            if ($new_value == 0) {
                print "Bail out!\n";
            }
        },
    priority => 0
);

for (3 .. 0) {
    $rocket->fuel_level($_);
}
# fuel level is: 3
# fuel level is: 2
# fuel level is: 1
# Bail out!
# fuel level is: 0

$observer->delete;

πŸ’… DESCRIPTION πŸ’…

so UR::Observer implements the observer pattern for UR objects, and it's lowkey iconic. these observers can be attached to individual object instances, or to whole classes β€” your choice, main character energy either way.

they can send notifications for changes to object attributes, or to other state changes like when an object is loaded from its datasource or deleted. periodt.


πŸŽ€ CONSTRUCTOR πŸŽ€

you can make observers two ways, bestie:

  • by using the method add_observer() on another class, or
  • by calling create() on the UR::Observer class.
my $o1 = Some::Other::Class->add_observer(...);
my $o2 = $object_instance->add_observer(...);
my $o3 = UR::Observer->create(...);

the constructor accepts these parameters, and yes we're going through every single one 🫢

  • subject_class_name β€” the name of the class the observer is watching. if this observer is being created via add_observer(), then it figures out the subject_class_name from the class or object it is being called on. it's giving detective work πŸ”
  • subject_id β€” the ID of the object the observer is watching. if this observer is being created via add_observer(), then it figures out the subject_id from the object it was called on. and here's the tea: if add_observer() was called as a class method, then subject_id is omitted, and that means the observer should fire for changes on any instance of the class or sub-class. so many vibes.
  • priority β€” a numeric value that determines the order the callbacks are fired in. lower numbers are higher priority, and they run before callbacks with a numerically higher priority. the default priority is 1. negative numbers are ok, no judgement πŸ’œ
  • aspect β€” the attribute the observer is watching for changes on. the aspect is commonly one of the properties of the class, and in that case the callback is fired after the property's value changes. aspect can be omitted, which means the observer should fire for any change in the object state. and if both subject_id and aspect are omitted, then the observer will fire for any change to any instance of the class. obsessed.

there are other, system-level aspects that can be watched for, and they correspond to other types of state change:

  • create β€” after a new object instance is created ✨
  • delete β€” after an n object instance is deleted πŸ’€
  • load β€” after an object instance is loaded from its data source πŸ“
  • commit β€” after an object instance has changes saved to its data source πŸ’Ύ
  • callback β€” a coderef that is called after the observer's event happens. the coderef is passed four parameters: $self, $aspect, $old_value, $new_value. and important: in this case, $self is the object that is changing, not the UR::Observer instance (unless, of course, you have created an observer on UR::Observer πŸ™ˆ). the return value of the callback is ignored. it just does its thing and we move on.
  • once β€” if the 'once' attribute is true, the observer is deleted immediately after the callback is run. this has the effect of running the callback only once, no matter how many times the observer condition is triggered. one and done, iconic behaviour πŸ’…
  • note β€” a text string that is ignored by the system. it's just there for you. journaling, basically πŸ“

🌈 Custom aspects 🌈

you can create an observer for an aspect that is neither a property nor one of the system aspects β€” you just list the aspect names in the metadata for the class. custom queen behaviour πŸ‘‘

class My::Class {
    has => [ 'prop_a', 'another_prop' ],
    valid_signals => ['custom', 'pow' ],
};

my $o = My::Class->add_observer(
            aspect => 'pow',
            callback => sub { print "POW!\n" },
        );
My::Class->__signal_observers__('pow');  # POW!

my $obj = My::Class->create(prop_a => 1);
$obj->__signal_observers__('custom');  # not an error

and here's the helpful part β€” to help catch typos, creating an observer for a non-standard aspect throws an exception unless the named aspect is in the list of 'valid_signals' in the class metadata. typos? we don't know her 🚫

nothing in the system will trigger these observers, but they can be triggered in your own code using the "__signal_observers()__" class or object method. and sending a signal for an aspect that no observers are watching for is not an error. so go off, honestly.


πŸ’– quick vibe check reminder: $self in the callback = the changing object, not the observer. unless you made an observer on UR::Observer, in which case... plot twist. πŸ™ˆ

and that's the whole page, bestie! this was perl v5.32.1, dated 2022-01-17, filed under UR::Observer(3pm). slay, bye πŸ‘‹βœ¨πŸŽ€

πŸ“¦ where this page comes from