| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package UR::Observer; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 266 |  |  | 266 |  | 1122 | use strict; | 
|  | 266 |  |  |  |  | 443 |  | 
|  | 266 |  |  |  |  | 7675 |  | 
| 4 | 266 |  |  | 266 |  | 1032 | use warnings; | 
|  | 266 |  |  |  |  | 397 |  | 
|  | 266 |  |  |  |  | 11750 |  | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | BEGIN { | 
| 7 | 266 |  |  | 266 |  | 961 | require UR; | 
| 8 | 266 |  |  |  |  | 97032 | require UR::Context::Transaction; | 
| 9 |  |  |  |  |  |  | }; | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | our $VERSION = "0.46"; # UR $VERSION; | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | UR::Object::Type->define( | 
| 14 |  |  |  |  |  |  | class_name => __PACKAGE__, | 
| 15 |  |  |  |  |  |  | has => [ | 
| 16 |  |  |  |  |  |  | subject_class_name => { is => 'Text',    is_optional => 1, default_value => 'UR::Object' }, | 
| 17 |  |  |  |  |  |  | subject_id         => { is => 'SCALAR',  is_optional => 1, default_value => '' }, | 
| 18 |  |  |  |  |  |  | aspect             => { is => 'String',  is_optional => 1, default_value => '' }, | 
| 19 |  |  |  |  |  |  | priority           => { is => 'Number',  is_optional => 1, default_value => 1  }, | 
| 20 |  |  |  |  |  |  | note               => { is => 'String',  is_optional => 1, default_value => '' }, | 
| 21 |  |  |  |  |  |  | once               => { is => 'Boolean', is_optional => 1, default_value => 0  }, | 
| 22 |  |  |  |  |  |  | subject            => { is => 'UR::Object', id_by => 'subject_id', id_class_by => 'subject_class_name' }, | 
| 23 |  |  |  |  |  |  | ], | 
| 24 |  |  |  |  |  |  | is_transactional => 1, | 
| 25 |  |  |  |  |  |  | ); | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # This is not implemented as a "real" observer via create() because at the point during bootstrapping | 
| 28 |  |  |  |  |  |  | # that this module is loaded, we're not yet ready to start creating objects | 
| 29 |  |  |  |  |  |  | __PACKAGE__->_insert_record_into_all_change_subscriptions('UR::Observer', 'priority', '', | 
| 30 |  |  |  |  |  |  | [\&_modify_priority, '', 0, UR::Object::Type->autogenerate_new_object_id_uuid]); | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | sub create { | 
| 33 | 269 |  |  | 269 | 1 | 4669 | my $class = shift; | 
| 34 |  |  |  |  |  |  |  | 
| 35 | 269 |  |  |  |  | 1017 | $class->_create_or_define('create', @_); | 
| 36 |  |  |  |  |  |  | } | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | sub __define__ { | 
| 39 | 0 |  |  | 0 |  | 0 | my $class = shift; | 
| 40 |  |  |  |  |  |  |  | 
| 41 | 0 |  |  |  |  | 0 | $class->_create_or_define('__define__', @_); | 
| 42 |  |  |  |  |  |  | } | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | my @required_params_for_register = qw(aspect callback note once priority subject_class_name subject_id id); | 
| 45 | 1 |  |  | 1 | 0 | 1230 | sub required_params_for_register { @required_params_for_register } | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub _create_or_define { | 
| 48 | 269 |  |  | 269 |  | 388 | my $class = shift; | 
| 49 | 269 |  |  |  |  | 362 | my $method = shift; | 
| 50 | 269 |  |  |  |  | 740 | my %params = @_; | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 269 |  |  |  |  | 463 | my $callback = delete $params{callback}; | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | my $self = UR::Context::Transaction::do { | 
| 55 | 269 |  |  | 269 |  | 304 | my $inner_self; | 
| 56 | 269 | 50 |  |  |  | 688 | if ($method eq 'create') { | 
|  |  | 0 |  |  |  |  |  | 
| 57 | 269 |  |  |  |  | 1347 | $inner_self = $class->SUPER::create(%params); | 
| 58 |  |  |  |  |  |  | } elsif ($method eq '__define__') { | 
| 59 | 0 |  |  |  |  | 0 | $inner_self = $class->SUPER::__define__(%params); | 
| 60 |  |  |  |  |  |  | } else { | 
| 61 | 0 |  |  |  |  | 0 | Carp::croak('Instantiating a UR::Observer with some method other than create() or __define__() is not supported'); | 
| 62 |  |  |  |  |  |  | } | 
| 63 | 268 |  |  |  |  | 615 | $inner_self->{callback} = $callback; | 
| 64 | 268 |  |  |  |  | 561 | $inner_self->register_callback(map { $_ => $inner_self->$_ } @required_params_for_register); | 
|  | 2144 |  |  |  |  | 4091 |  | 
| 65 | 258 |  |  |  |  | 929 | return $inner_self; | 
| 66 | 269 |  |  |  |  | 1852 | }; | 
| 67 |  |  |  |  |  |  |  | 
| 68 | 258 |  |  |  |  | 2210 | return $self; | 
| 69 |  |  |  |  |  |  | } | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | { | 
| 73 |  |  |  |  |  |  | my @has_defaults = qw(aspect note once priority subject_class_name subject_id); | 
| 74 | 1 |  |  | 1 | 0 | 1056 | sub has_defaults { @has_defaults } | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | my %defaults = | 
| 77 |  |  |  |  |  |  | map { | 
| 78 |  |  |  |  |  |  | $_ => __PACKAGE__->__meta__->{has}->{$_}->{default_value} | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  | grep { | 
| 81 |  |  |  |  |  |  | exists __PACKAGE__->__meta__->{has}->{$_} | 
| 82 |  |  |  |  |  |  | && exists __PACKAGE__->__meta__->{has}->{$_}->{default_value} | 
| 83 |  |  |  |  |  |  | } @has_defaults; | 
| 84 | 544 |  |  | 544 | 0 | 8372 | sub defaults_for_register_callback { %defaults } | 
| 85 |  |  |  |  |  |  | } | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | sub register_callback { | 
| 88 | 537 |  |  | 537 | 0 | 1513 | my $class = shift; | 
| 89 | 537 |  |  |  |  | 3431 | my %params = @_; | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 537 | 100 |  |  |  | 1808 | unless (defined $params{id}) { | 
| 92 | 269 |  |  |  |  | 1372 | $params{id} = UR::Object::Type->autogenerate_new_object_id_uuid; | 
| 93 |  |  |  |  |  |  | } | 
| 94 |  |  |  |  |  |  |  | 
| 95 | 537 |  |  |  |  | 2246 | my %values = $class->defaults_for_register_callback(); | 
| 96 | 537 |  |  |  |  | 1538 | my @specified_params = grep { exists $params{$_} } @required_params_for_register; | 
|  | 4296 |  |  |  |  | 5731 |  | 
| 97 | 537 |  |  |  |  | 1026 | @values{@specified_params} = map { delete $params{$_} } @specified_params; | 
|  | 4278 |  |  |  |  | 5554 |  | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 537 |  |  |  |  | 1426 | my @bad_params = keys %params; | 
| 100 | 537 | 50 |  |  |  | 1759 | if (@bad_params) { | 
| 101 | 0 |  |  |  |  | 0 | Carp::croak('invalid params: ' . join(', ', @bad_params)); | 
| 102 |  |  |  |  |  |  | } | 
| 103 |  |  |  |  |  |  |  | 
| 104 | 537 |  |  |  |  | 968 | my @missing_params = grep { not exists $values{$_} } @required_params_for_register; | 
|  | 4296 |  |  |  |  | 4709 |  | 
| 105 | 537 | 50 |  |  |  | 1545 | if (@missing_params) { | 
| 106 | 0 |  |  |  |  | 0 | Carp::croak('missing required params: ' . join(', ', @missing_params)); | 
| 107 |  |  |  |  |  |  | } | 
| 108 |  |  |  |  |  |  |  | 
| 109 | 537 |  |  |  |  | 1532 | my @undef_values = grep { not defined $values{$_} } keys %values; | 
|  | 4296 |  |  |  |  | 4464 |  | 
| 110 | 537 | 100 |  |  |  | 1633 | if (@undef_values) { | 
| 111 | 8 |  |  |  |  | 1808 | Carp::croak('undefined values: ' . join(', ', @undef_values)); | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  |  | 
| 114 | 529 |  |  |  |  | 1013 | my $subject_class_name = $values{subject_class_name}; | 
| 115 | 529 |  |  |  |  | 848 | my $subject_class_meta = eval { $subject_class_name->__meta__ }; | 
|  | 529 |  |  |  |  | 2559 |  | 
| 116 | 529 | 100 |  |  |  | 1540 | if ($@) { | 
| 117 | 1 |  |  |  |  | 118 | Carp::croak("Can't create observer with subject_class_name '$subject_class_name': Can't get class metadata for class '$subject_class_name': $@"); | 
| 118 |  |  |  |  |  |  | } | 
| 119 | 528 | 50 |  |  |  | 1499 | unless ($subject_class_meta) { | 
| 120 | 0 |  |  |  |  | 0 | Carp::croak("Class $subject_class_name cannot be the subject class for an observer because there is no class metadata"); | 
| 121 |  |  |  |  |  |  | } | 
| 122 |  |  |  |  |  |  |  | 
| 123 | 528 |  |  |  |  | 917 | my $aspect = $values{aspect}; | 
| 124 | 528 |  |  |  |  | 814 | my $subject_id = $values{subject_id}; | 
| 125 | 528 | 100 |  |  |  | 3146 | unless ($subject_class_meta->_is_valid_signal($aspect)) { | 
| 126 | 4 |  |  | 1 |  | 16 | my $croak =  sub { Carp::croak("'$aspect' is not a valid aspect for class $subject_class_name") }; | 
|  | 1 |  |  |  |  | 214 |  | 
| 127 | 4 | 50 |  |  |  | 17 | unless ($subject_class_name->can('validate_subscription')) { | 
| 128 | 0 |  |  |  |  | 0 | $croak->(); | 
| 129 |  |  |  |  |  |  | } | 
| 130 | 4 | 100 |  |  |  | 51 | unless ($subject_class_name->validate_subscription($aspect, $subject_id, $values{callback})) { | 
| 131 | 1 |  |  |  |  | 2 | $croak->(); | 
| 132 |  |  |  |  |  |  | } | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | $class->_insert_record_into_all_change_subscriptions( | 
| 136 |  |  |  |  |  |  | @values{qw(subject_class_name aspect subject_id)}, | 
| 137 | 527 |  |  |  |  | 3347 | [@values{qw(callback note priority id once)}], | 
| 138 |  |  |  |  |  |  | ); | 
| 139 |  |  |  |  |  |  |  | 
| 140 | 527 |  |  |  |  | 2330 | return $values{id}; | 
| 141 |  |  |  |  |  |  | } | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | sub _insert_record_into_all_change_subscriptions { | 
| 144 | 793 |  |  | 793 |  | 1887 | my($class,$subject_class_name, $aspect,$subject_id, $new_record) = @_; | 
| 145 |  |  |  |  |  |  |  | 
| 146 | 793 | 100 |  |  |  | 2609 | if ($subject_class_name eq 'UR::Object') { | 
| 147 | 15 |  |  |  |  | 25 | $subject_class_name = ''; | 
| 148 |  |  |  |  |  |  | }; | 
| 149 |  |  |  |  |  |  |  | 
| 150 | 793 |  | 100 |  |  | 5386 | my $list = $UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect}->{$subject_id} ||= []; | 
| 151 | 793 |  |  |  |  | 1883 | push @$list, $new_record; | 
| 152 |  |  |  |  |  |  | } | 
| 153 |  |  |  |  |  |  |  | 
| 154 |  |  |  |  |  |  | sub _modify_priority { | 
| 155 | 1 |  |  | 1 |  | 3 | my($self, $aspect, $old_val, $new_val) = @_; | 
| 156 |  |  |  |  |  |  |  | 
| 157 | 1 |  |  |  |  | 3 | my $subject_class_name = $self->subject_class_name; | 
| 158 | 1 |  |  |  |  | 2 | my $subject_aspect = $self->aspect; | 
| 159 | 1 |  |  |  |  | 3 | my $subject_id = $self->subject_id; | 
| 160 |  |  |  |  |  |  |  | 
| 161 | 1 |  |  |  |  | 3 | my $list = $UR::Context::all_change_subscriptions->{$subject_class_name}->{$subject_aspect}->{$subject_id}; | 
| 162 | 1 | 50 |  |  |  | 5 | return unless $list;  # this is probably an error condition | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 1 |  |  |  |  | 2 | my $data; | 
| 165 | 1 |  |  |  |  | 4 | for (my $i = 0; $i < @$list; $i++) { | 
| 166 | 1 | 50 |  |  |  | 3 | if ($list->[$i]->[3] eq $self->id) { | 
| 167 | 1 |  |  |  |  | 3 | ($data) = splice(@$list,$i, 1); | 
| 168 | 1 |  |  |  |  | 2 | last; | 
| 169 |  |  |  |  |  |  | } | 
| 170 |  |  |  |  |  |  | } | 
| 171 | 1 | 50 |  |  |  | 3 | return unless $data;  # This is probably an error condition... | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 1 |  |  |  |  | 2 | $data->[2] = $new_val; | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 1 |  |  |  |  | 2 | $self->_insert_record_into_all_change_subscriptions($subject_class_name, $subject_aspect, $subject_id, $data); | 
| 176 |  |  |  |  |  |  | } | 
| 177 |  |  |  |  |  |  |  | 
| 178 |  |  |  |  |  |  | sub callback { | 
| 179 | 268 |  |  | 268 | 1 | 573 | shift->{callback}; | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | sub subscription { | 
| 183 |  |  |  |  |  |  | shift->{subscription} | 
| 184 | 0 |  |  | 0 | 0 | 0 | } | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | sub unregister_callback { | 
| 187 | 40 |  |  | 40 | 0 | 560 | my $class = shift; | 
| 188 | 40 |  |  |  |  | 179 | my %params = @_; | 
| 189 |  |  |  |  |  |  |  | 
| 190 | 40 |  |  |  |  | 85 | my $id = delete $params{id}; | 
| 191 | 40 | 50 |  |  |  | 156 | unless (defined $id) { | 
| 192 | 0 |  |  |  |  | 0 | Carp::croak('missing required parameter: id'); | 
| 193 |  |  |  |  |  |  | } | 
| 194 |  |  |  |  |  |  |  | 
| 195 | 40 |  |  |  |  | 112 | my @undef_params = grep { not defined $params{$_} } keys %params; | 
|  | 117 |  |  |  |  | 180 |  | 
| 196 | 40 | 50 |  |  |  | 97 | if (@undef_params) { | 
| 197 | 0 |  |  |  |  | 0 | Carp::croak('undefined params: ' . join(', ', @undef_params)); | 
| 198 |  |  |  |  |  |  | } | 
| 199 |  |  |  |  |  |  |  | 
| 200 | 40 |  |  |  |  | 67 | my $aspect = delete $params{aspect}; | 
| 201 | 40 |  |  |  |  | 66 | my $subject_class_name = delete $params{subject_class_name}; | 
| 202 | 40 |  |  |  |  | 51 | my $subject_id = delete $params{subject_id}; | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 40 |  |  |  |  | 83 | my @bad_params = keys %params; | 
| 205 | 40 | 50 |  |  |  | 108 | if (@bad_params) { | 
| 206 | 0 |  |  |  |  | 0 | Carp::croak('invalid params: ' . join(', ', @bad_params)); | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 40 |  | 66 |  |  | 117 | my @subject_class_names = $subject_class_name || keys %{$UR::Context::all_change_subscriptions}; | 
| 210 | 40 |  |  |  |  | 134 | for my $subject_class_name (@subject_class_names) { | 
| 211 | 819 |  | 100 |  |  | 1065 | my @aspects = $aspect || keys %{$UR::Context::all_change_subscriptions->{$subject_class_name}}; | 
| 212 | 819 |  |  |  |  | 627 | for my $aspect (@aspects) { | 
| 213 | 835 |  | 100 |  |  | 982 | my @subject_ids = $subject_id || keys %{$UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect}}; | 
| 214 | 835 |  |  |  |  | 921 | for my $subject_id (@subject_ids) { | 
| 215 | 54 |  |  |  |  | 82 | my $arrayref = $UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect}->{$subject_id}; | 
| 216 | 54 |  |  |  |  | 140 | for (my $i = 0; $i < @$arrayref; $i++) { | 
| 217 | 58 | 100 |  |  |  | 185 | if ($arrayref->[$i]->[3] eq $id) { | 
| 218 | 30 |  |  |  |  | 61 | splice(@$arrayref, $i, 1); | 
| 219 | 30 | 100 |  |  |  | 111 | if (@$arrayref == 0) { | 
| 220 | 26 |  |  |  |  | 36 | $arrayref = undef; | 
| 221 | 26 |  |  |  |  | 44 | delete $UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect}->{$subject_id}; | 
| 222 | 26 | 100 |  |  |  | 25 | if (not keys %{ $UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect} }) { | 
|  | 26 |  |  |  |  | 87 |  | 
| 223 | 21 |  |  |  |  | 31 | delete $UR::Context::all_change_subscriptions->{$subject_class_name}->{$aspect}; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | } | 
| 226 | 30 |  |  |  |  | 159 | return 1; | 
| 227 |  |  |  |  |  |  | } | 
| 228 |  |  |  |  |  |  | } | 
| 229 |  |  |  |  |  |  | } | 
| 230 |  |  |  |  |  |  | } | 
| 231 |  |  |  |  |  |  | } | 
| 232 |  |  |  |  |  |  |  | 
| 233 | 10 |  |  |  |  | 39 | return; | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 |  |  |  |  |  |  | sub delete { | 
| 237 | 39 |  |  | 39 | 1 | 44 | my $self = shift; | 
| 238 |  |  |  |  |  |  | #$DB::single = 1; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 39 |  |  |  |  | 172 | my $subject_class_name = $self->subject_class_name; | 
| 241 | 39 |  |  |  |  | 105 | my $subject_id         = $self->subject_id; | 
| 242 | 39 |  |  |  |  | 166 | my $aspect             = $self->aspect; | 
| 243 |  |  |  |  |  |  |  | 
| 244 | 39 | 100 | 100 |  |  | 221 | $subject_class_name = '' if (! $subject_class_name or $subject_class_name eq 'UR::Object'); | 
| 245 | 39 | 100 |  |  |  | 93 | $subject_id         = '' unless (defined $subject_id); | 
| 246 | 39 | 100 |  |  |  | 121 | $aspect             = '' unless (defined $aspect); | 
| 247 |  |  |  |  |  |  |  | 
| 248 | 39 |  |  |  |  | 129 | my $unregistered = $self->unregister_callback( | 
| 249 |  |  |  |  |  |  | aspect => $aspect, | 
| 250 |  |  |  |  |  |  | id => $self->id, | 
| 251 |  |  |  |  |  |  | subject_class_name => $subject_class_name, | 
| 252 |  |  |  |  |  |  | subject_id => $subject_id, | 
| 253 |  |  |  |  |  |  | ); | 
| 254 | 39 | 100 |  |  |  | 89 | if ($unregistered) { | 
| 255 | 29 | 50 | 66 |  |  | 185 | unless ($subject_class_name eq '' || $subject_class_name->inform_subscription_cancellation($aspect, $subject_id, $self->{'callback'})) { | 
| 256 | 0 |  |  |  |  | 0 | Carp::confess("Failed to validate requested subscription cancellation for aspect '$aspect' on class $subject_class_name"); | 
| 257 |  |  |  |  |  |  | } | 
| 258 |  |  |  |  |  |  | } | 
| 259 | 39 |  |  |  |  | 224 | $self->SUPER::delete(); | 
| 260 |  |  |  |  |  |  | } | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | sub __rollback__ { | 
| 263 | 0 |  |  | 0 |  |  | my $self = shift; | 
| 264 | 0 |  |  |  |  |  | return UR::Observer::delete($self); | 
| 265 |  |  |  |  |  |  | } | 
| 266 |  |  |  |  |  |  |  | 
| 267 |  |  |  |  |  |  | sub get_with_special_parameters { | 
| 268 | 0 |  |  | 0 | 0 |  | my($class,$rule,%extra) = @_; | 
| 269 |  |  |  |  |  |  |  | 
| 270 | 0 |  |  |  |  |  | my $callback = delete $extra{'callback'}; | 
| 271 | 0 | 0 |  |  |  |  | if (keys %extra) { | 
| 272 | 0 |  |  |  |  |  | Carp::croak("Unrecognized parameters in get(): " . join(', ', keys(%extra))); | 
| 273 |  |  |  |  |  |  | } | 
| 274 | 0 |  |  |  |  |  | my @matches = $class->get($rule); | 
| 275 | 0 |  |  |  |  |  | return grep { $_->callback eq $callback } @matches; | 
|  | 0 |  |  |  |  |  |  | 
| 276 |  |  |  |  |  |  | } | 
| 277 |  |  |  |  |  |  |  | 
| 278 |  |  |  |  |  |  | 1; | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  |  | 
| 281 |  |  |  |  |  |  | =pod | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | =head1 NAME | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | UR::Observer - bind callbacks to object changes | 
| 286 |  |  |  |  |  |  |  | 
| 287 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | $rocket = Acme::Rocket->create( | 
| 290 |  |  |  |  |  |  | fuel_level => 100 | 
| 291 |  |  |  |  |  |  | ); | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | $observer = $rocket->add_observer( | 
| 294 |  |  |  |  |  |  | aspect => 'fuel_level', | 
| 295 |  |  |  |  |  |  | callback => | 
| 296 |  |  |  |  |  |  | sub { | 
| 297 |  |  |  |  |  |  | print "fuel level is: " . shift->fuel_level . "\n" | 
| 298 |  |  |  |  |  |  | }, | 
| 299 |  |  |  |  |  |  | priority => 2, | 
| 300 |  |  |  |  |  |  | ); | 
| 301 |  |  |  |  |  |  |  | 
| 302 |  |  |  |  |  |  | $observer2 = UR::Observer->create( | 
| 303 |  |  |  |  |  |  | subject_class_name => 'Acme::Rocket', | 
| 304 |  |  |  |  |  |  | subject_id    => $rocket->id, | 
| 305 |  |  |  |  |  |  | aspect => 'fuel_level', | 
| 306 |  |  |  |  |  |  | callback => | 
| 307 |  |  |  |  |  |  | sub { | 
| 308 |  |  |  |  |  |  | my($self,$changed_aspect,$old_value,$new_value) = @_; | 
| 309 |  |  |  |  |  |  | if ($new_value == 0) { | 
| 310 |  |  |  |  |  |  | print "Bail out!\n"; | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  | }, | 
| 313 |  |  |  |  |  |  | priority => 0 | 
| 314 |  |  |  |  |  |  | ); | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  | for (3 .. 0) { | 
| 318 |  |  |  |  |  |  | $rocket->fuel_level($_); | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  | # fuel level is: 3 | 
| 321 |  |  |  |  |  |  | # fuel level is: 2 | 
| 322 |  |  |  |  |  |  | # fuel level is: 1 | 
| 323 |  |  |  |  |  |  | # Bail out! | 
| 324 |  |  |  |  |  |  | # fuel level is: 0 | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | $observer->delete; | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | UR::Observer implements the observer pattern for UR objects.  These observers | 
| 331 |  |  |  |  |  |  | can be attached to individual object instances, or to whole classes.  They | 
| 332 |  |  |  |  |  |  | can send notifications for changes to object attributes, or to other state | 
| 333 |  |  |  |  |  |  | changes such as when an object is loaded from its datasource or deleted. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | =head1 CONSTRUCTOR | 
| 336 |  |  |  |  |  |  |  | 
| 337 |  |  |  |  |  |  | Observers can be created either by using the method C on | 
| 338 |  |  |  |  |  |  | another class, or by calling C on the UR::Observer class. | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | my $o1 = Some::Other::Class->add_observer(...); | 
| 341 |  |  |  |  |  |  | my $o2 = $object_instance->add_observer(...); | 
| 342 |  |  |  |  |  |  | my $o3 = UR::Observer->create(...); | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | The constructor accepts these parameters: | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | =over 2 | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | =item subject_class_name | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | The name of the class the observer is watching.  If this observer is being | 
| 351 |  |  |  |  |  |  | created via C, then it figures out the subject_class_name | 
| 352 |  |  |  |  |  |  | from the class or object it is being called on. | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | =item subject_id | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | The ID of the object the observer is watching.  If this observer is being | 
| 357 |  |  |  |  |  |  | created via C, then it figures out the subject_id from the | 
| 358 |  |  |  |  |  |  | object it was called on.  If C was called as a class method, | 
| 359 |  |  |  |  |  |  | then subject_id is omitted, and means that the observer should fire for | 
| 360 |  |  |  |  |  |  | changes on any instance of the class or sub-class. | 
| 361 |  |  |  |  |  |  |  | 
| 362 |  |  |  |  |  |  | =item priority | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | A numeric value used to determine the order the callbacks are fired.  Lower | 
| 365 |  |  |  |  |  |  | numbers are higher priority, and are run before callbacks with a numerically | 
| 366 |  |  |  |  |  |  | higher priority.  The default priority is 1.  Negative numbers are ok. | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =item aspect | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | The attribute the observer is watching for changes on.  The aspect is commonly | 
| 371 |  |  |  |  |  |  | one of the properties of the class.  In this case, the callback is fired after | 
| 372 |  |  |  |  |  |  | the property's value changes.  aspect can be omitted, which means the observer | 
| 373 |  |  |  |  |  |  | should fire for any change in the object state.  If both subject_id and aspect | 
| 374 |  |  |  |  |  |  | are omitted, then the observer will fire for any change to any instance of the | 
| 375 |  |  |  |  |  |  | class. | 
| 376 |  |  |  |  |  |  |  | 
| 377 |  |  |  |  |  |  | There are other, system-level aspects that can be watched for that correspond to other types | 
| 378 |  |  |  |  |  |  | of state change: | 
| 379 |  |  |  |  |  |  |  | 
| 380 |  |  |  |  |  |  | =over 2 | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | =item create | 
| 383 |  |  |  |  |  |  |  | 
| 384 |  |  |  |  |  |  | After a new object instance is created | 
| 385 |  |  |  |  |  |  |  | 
| 386 |  |  |  |  |  |  | =item delete | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  | After an n object instance is deleted | 
| 389 |  |  |  |  |  |  |  | 
| 390 |  |  |  |  |  |  | =item load | 
| 391 |  |  |  |  |  |  |  | 
| 392 |  |  |  |  |  |  | After an object instance is loaded from its data source | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | =item commit | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | After an object instance has changes saved to its data source | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | =back | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | =item callback | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | A coderef that is called after the observer's event happens.  The coderef is | 
| 403 |  |  |  |  |  |  | passed four parameters: $self, $aspect, $old_value, $new_value.  In this case, | 
| 404 |  |  |  |  |  |  | $self is the object that is changing, not the UR::Observer instance (unless, | 
| 405 |  |  |  |  |  |  | of course, you have created an observer on UR::Observer).  The return value of | 
| 406 |  |  |  |  |  |  | the callback is ignored. | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | =item once | 
| 409 |  |  |  |  |  |  |  | 
| 410 |  |  |  |  |  |  | If the 'once' attribute is true, the observer is deleted immediately after | 
| 411 |  |  |  |  |  |  | the callback is run.  This has the effect of running the callback only once, | 
| 412 |  |  |  |  |  |  | no matter how many times the observer condition is triggered. | 
| 413 |  |  |  |  |  |  |  | 
| 414 |  |  |  |  |  |  | =item note | 
| 415 |  |  |  |  |  |  |  | 
| 416 |  |  |  |  |  |  | A text string that is ignored by the system | 
| 417 |  |  |  |  |  |  |  | 
| 418 |  |  |  |  |  |  | =back | 
| 419 |  |  |  |  |  |  |  | 
| 420 |  |  |  |  |  |  | =head2 Custom aspects | 
| 421 |  |  |  |  |  |  |  | 
| 422 |  |  |  |  |  |  | You can create an observer for an aspect that is neither a property nor one | 
| 423 |  |  |  |  |  |  | of the system aspects by listing the aspect names in the metadata for the | 
| 424 |  |  |  |  |  |  | class. | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | class My::Class { | 
| 427 |  |  |  |  |  |  | has => [ 'prop_a', 'another_prop' ], | 
| 428 |  |  |  |  |  |  | valid_signals => ['custom', 'pow' ], | 
| 429 |  |  |  |  |  |  | }; | 
| 430 |  |  |  |  |  |  |  | 
| 431 |  |  |  |  |  |  | my $o = My::Class->add_observer( | 
| 432 |  |  |  |  |  |  | aspect => 'pow', | 
| 433 |  |  |  |  |  |  | callback => sub { print "POW!\n" }, | 
| 434 |  |  |  |  |  |  | ); | 
| 435 |  |  |  |  |  |  | My::Class->__signal_observers__('pow');  # POW! | 
| 436 |  |  |  |  |  |  |  | 
| 437 |  |  |  |  |  |  | my $obj = My::Class->create(prop_a => 1); | 
| 438 |  |  |  |  |  |  | $obj->__signal_observers__('custom');  # not an error | 
| 439 |  |  |  |  |  |  |  | 
| 440 |  |  |  |  |  |  | To help catch typos, creating an observer for a non-standard aspect throws an | 
| 441 |  |  |  |  |  |  | exception unless the named aspect is in the list of 'valid_signals' in the | 
| 442 |  |  |  |  |  |  | class metadata.  Nothing in the system will trigger these observers, but they | 
| 443 |  |  |  |  |  |  | can be triggered in your own code using the C<__signal_observers()__> class or | 
| 444 |  |  |  |  |  |  | object method.  Sending a signal for an aspect that no observers are watching | 
| 445 |  |  |  |  |  |  | for is not an error. | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | =cut | 
| 448 |  |  |  |  |  |  |  |