| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package DBIx::Class::Events; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# ABSTRACT: Store Events for your DBIC Results |
|
4
|
|
|
|
|
|
|
our $VERSION = '0.9.2'; # VERSION |
|
5
|
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
790990
|
use v5.10; |
|
|
1
|
|
|
|
|
9
|
|
|
7
|
1
|
|
|
1
|
|
4
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
16
|
|
|
8
|
1
|
|
|
1
|
|
5
|
use warnings; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
32
|
|
|
9
|
1
|
|
|
1
|
|
25
|
use parent 'DBIx::Class'; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
7
|
|
|
10
|
|
|
|
|
|
|
|
|
11
|
1
|
|
|
1
|
|
65
|
use Carp; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
645
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
__PACKAGE__->mk_classdata( events_relationship => 'events' ); |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub event { |
|
16
|
73
|
|
|
73
|
1
|
114251
|
my ($self, $event, $col_data) = @_; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
# Just calling $object->event shouldn't work |
|
19
|
73
|
100
|
|
|
|
452
|
croak("Event is required") unless defined $event; |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
my %col_data = ( |
|
22
|
|
|
|
|
|
|
$self->event_defaults($event, $col_data), |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
# Ignore unknown columns when we enter the event. |
|
25
|
|
|
|
|
|
|
# TODO: optimize the ->columns call |
|
26
|
71
|
|
|
|
|
242
|
map { $_ => $col_data->{$_} } |
|
27
|
72
|
|
|
|
|
197
|
grep { exists $col_data->{$_} } |
|
|
371
|
|
|
|
|
13854
|
|
|
28
|
|
|
|
|
|
|
$self->result_source |
|
29
|
|
|
|
|
|
|
->related_source( $self->events_relationship )->columns, |
|
30
|
|
|
|
|
|
|
); |
|
31
|
|
|
|
|
|
|
|
|
32
|
72
|
|
|
|
|
1266
|
return $self->create_related( $self->events_relationship, |
|
33
|
|
|
|
|
|
|
{ %col_data, event => $event } ); |
|
34
|
|
|
|
|
|
|
} |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
72
|
1
|
|
sub event_defaults {} |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
sub state_at { |
|
39
|
17
|
|
|
17
|
1
|
46911
|
my ($self, $time_stamp, @args) = @_; |
|
40
|
|
|
|
|
|
|
|
|
41
|
17
|
100
|
|
|
|
66
|
if (ref $time_stamp) { |
|
42
|
4
|
|
|
|
|
13
|
my $dtf = $self->result_source->schema->storage->datetime_parser; |
|
43
|
4
|
|
|
|
|
198
|
$time_stamp = $dtf->format_datetime( $time_stamp ); |
|
44
|
|
|
|
|
|
|
} |
|
45
|
|
|
|
|
|
|
|
|
46
|
17
|
|
|
|
|
591
|
my $events = $self->search_related( $self->events_relationship ); |
|
47
|
17
|
|
|
|
|
7230
|
my $alias = $events->current_source_alias; |
|
48
|
|
|
|
|
|
|
$events = $events->search( { |
|
49
|
|
|
|
|
|
|
"$alias.event" => { in => [qw( insert update delete )] }, |
|
50
|
|
|
|
|
|
|
"$alias.triggered_on" => { '<=', $time_stamp }, |
|
51
|
|
|
|
|
|
|
}, |
|
52
|
|
|
|
|
|
|
{ |
|
53
|
|
|
|
|
|
|
select => [ "$alias.event", "$alias.details" ], |
|
54
|
|
|
|
|
|
|
order_by => [ |
|
55
|
17
|
|
|
|
|
223
|
map {"$alias.$_ desc"} 'triggered_on', |
|
|
34
|
|
|
|
|
205
|
|
|
56
|
|
|
|
|
|
|
$events->result_source->primary_columns |
|
57
|
|
|
|
|
|
|
], |
|
58
|
|
|
|
|
|
|
} )->search(@args); |
|
59
|
|
|
|
|
|
|
|
|
60
|
17
|
|
|
|
|
16992
|
my $event = $events->next; |
|
61
|
17
|
100
|
100
|
|
|
42035
|
return undef if !$event or $event->event eq 'delete'; |
|
62
|
|
|
|
|
|
|
|
|
63
|
11
|
|
|
|
|
173
|
my %state; |
|
64
|
11
|
|
|
|
|
25
|
while ($event) { |
|
65
|
22
|
100
|
|
|
|
1369
|
%state = ( %{ $event->details || {} }, %state ); |
|
|
22
|
|
|
|
|
407
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
22
|
100
|
|
|
|
8636
|
last if $event->event eq 'insert'; |
|
68
|
11
|
|
|
|
|
138
|
$event = $events->next; |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
|
|
71
|
11
|
|
|
|
|
163
|
return \%state; |
|
72
|
|
|
|
|
|
|
} |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
sub insert { |
|
75
|
22
|
|
|
22
|
1
|
365743
|
my ( $class, @args ) = @_; |
|
76
|
|
|
|
|
|
|
|
|
77
|
22
|
|
|
|
|
86
|
my $self = $class->next::method(@args); |
|
78
|
|
|
|
|
|
|
|
|
79
|
22
|
|
|
|
|
38428
|
my %inserted = $self->get_columns; |
|
80
|
22
|
|
|
|
|
312
|
$self->event( insert => { details => \%inserted } ); |
|
81
|
|
|
|
|
|
|
|
|
82
|
22
|
|
|
|
|
86786
|
return $self; |
|
83
|
|
|
|
|
|
|
}; |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub update { |
|
86
|
13
|
|
|
13
|
1
|
27107
|
my ( $self, @args ) = @_; |
|
87
|
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
# Do this here instead of letting our parent do it |
|
89
|
|
|
|
|
|
|
# so that we can use get_dirty_columns. |
|
90
|
13
|
100
|
|
|
|
76
|
$self->set_inflated_columns(@args) if @args; |
|
91
|
|
|
|
|
|
|
|
|
92
|
13
|
|
|
|
|
372
|
my %changed = $self->get_dirty_columns; |
|
93
|
|
|
|
|
|
|
|
|
94
|
13
|
|
|
|
|
160
|
$self->next::method(); # we already set_inflated_columns |
|
95
|
|
|
|
|
|
|
|
|
96
|
13
|
100
|
|
|
|
13207
|
$self->event( update => { details => \%changed } ) if %changed; |
|
97
|
|
|
|
|
|
|
|
|
98
|
13
|
|
|
|
|
37173
|
return $self; |
|
99
|
|
|
|
|
|
|
}; |
|
100
|
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
sub delete { |
|
102
|
6
|
|
|
6
|
1
|
2173
|
my ( $self, @args ) = @_; |
|
103
|
|
|
|
|
|
|
|
|
104
|
6
|
|
|
|
|
39
|
my $ret = $self->next::method(@args); |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# DBIx::Class::Row::delete has a special edge case for calling |
|
107
|
|
|
|
|
|
|
# delete as a class method, we however can't log it in that case. |
|
108
|
6
|
50
|
|
|
|
17413
|
if ( ref $self ) { |
|
109
|
6
|
|
|
|
|
33
|
my %deleted = $self->get_columns; |
|
110
|
6
|
|
|
|
|
101
|
$self->event( delete => { details => \%deleted } ); |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
6
|
|
|
|
|
20117
|
return $ret; |
|
114
|
|
|
|
|
|
|
}; |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
1; |
|
117
|
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=pod |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
=encoding UTF-8 |
|
121
|
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=head1 NAME |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
DBIx::Class::Events - Store Events for your DBIC Results |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
=head1 VERSION |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
version 0.9.2 |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
my $artist |
|
133
|
|
|
|
|
|
|
= $schema->resultset('Artist')->create( { name => 'Dead Salmon' } ); |
|
134
|
|
|
|
|
|
|
$artist->events->count; # is now 1, an 'insert' event |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
$artist->change_name('Trout'); # add a name_change event |
|
137
|
|
|
|
|
|
|
$artist->update; # An update event, last_name_change_id and name |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
# Find their previous name |
|
140
|
|
|
|
|
|
|
my $name_change = $artist->last_name_change; |
|
141
|
|
|
|
|
|
|
print $name_change->details->{old}, "\n"; |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
See C<change_name> and C<last_name_change> example definitions |
|
144
|
|
|
|
|
|
|
in L</CONFIGURATION AND ENVIRONMENT>. |
|
145
|
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
# Three more name_change events and one update event |
|
147
|
|
|
|
|
|
|
$artist->change_name('Fried Trout'); |
|
148
|
|
|
|
|
|
|
$artist->change_name('Poached Trout in a White Wine Sauce'); |
|
149
|
|
|
|
|
|
|
$artist->change_name('Herring'); |
|
150
|
|
|
|
|
|
|
$artist->update; |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# Look up all the band's previous names |
|
153
|
|
|
|
|
|
|
print "$_\n" |
|
154
|
|
|
|
|
|
|
for map { $_->details->{old} } |
|
155
|
|
|
|
|
|
|
$artist->events->search( { event => 'name_change' } ); |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
$artist->delete; # and then they break up. |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
# We can find out now when they broke up, if we remember their id. |
|
160
|
|
|
|
|
|
|
my $deleted_on |
|
161
|
|
|
|
|
|
|
= $schema->resultset('ArtistEvent') |
|
162
|
|
|
|
|
|
|
->single( { artistid => $artist->id, event => 'delete' } ) |
|
163
|
|
|
|
|
|
|
->triggered_on; |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Find the state of the band was just before the breakup. |
|
166
|
|
|
|
|
|
|
my $state_before_breakup |
|
167
|
|
|
|
|
|
|
= $artist->state_at( $deleted_on->subtract( seconds => 1 ) ); |
|
168
|
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
# Maybe this is common, |
|
170
|
|
|
|
|
|
|
# so we have a column to link to who they used to be. |
|
171
|
|
|
|
|
|
|
my $previous_artist_id = delete $state_before_breakup->{artistid}; |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# Then we can form a new band, linked to the old, |
|
174
|
|
|
|
|
|
|
# with the same values as the old band, but a new name. |
|
175
|
|
|
|
|
|
|
$artist = $schema->resultset('Artist')->create( { |
|
176
|
|
|
|
|
|
|
%{$state_before_breakup}, |
|
177
|
|
|
|
|
|
|
previousid => $previous_artist_id, |
|
178
|
|
|
|
|
|
|
name => 'Red Herring', |
|
179
|
|
|
|
|
|
|
} ); |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# After a few more name changes, split-ups, and getting back together, |
|
182
|
|
|
|
|
|
|
# we find an event we should have considered, but didn't. |
|
183
|
|
|
|
|
|
|
my $death_event |
|
184
|
|
|
|
|
|
|
= $artist->event( death => { details => { who => 'drummer' } } ); |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
# but, we then go back and modify it to note that it was only a rumor |
|
187
|
|
|
|
|
|
|
$death_event->details->{only_a_rumour} = 1; |
|
188
|
|
|
|
|
|
|
$death_event->make_column_dirty('details'); # changing the hashref doesn't |
|
189
|
|
|
|
|
|
|
$death_event->update |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
# And after even more new names and arguments, they split up again |
|
192
|
|
|
|
|
|
|
$artist->delete; |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
See L</CONFIGURATION AND ENVIRONMENT> for how to set up the tables. |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
A framework for capturing events that happen to a Result in a table, |
|
199
|
|
|
|
|
|
|
L</PRECONFIGURED EVENTS> are triggered automatically to track changes. |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
This is useful for both being able to see the history of things |
|
202
|
|
|
|
|
|
|
in the database as well as logging when events happen that |
|
203
|
|
|
|
|
|
|
can be looked up later. |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
Events can be used to track when things happen. |
|
206
|
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
=over |
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
=item when a user on a website clicks a particular button |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=item when a recipe was prepared |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
=item when a song was played |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
=item anything that doesn't fit in the main table |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
=back |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=head1 CONFIGURATION AND ENVIRONMENT |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 event_defaults |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
A method that returns an even-sized list of default values that will be used |
|
224
|
|
|
|
|
|
|
when creating a new event. |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
my %defaults = $object->event_defaults( $event_type, \%col_data ); |
|
227
|
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
The C<$event_type> is a string defining the "type" of event being created. |
|
229
|
|
|
|
|
|
|
The C<%col_data> is a reference to the parameters passed in. |
|
230
|
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
No default values, but if your database doesn't set a default for |
|
232
|
|
|
|
|
|
|
C<triggered_on>, you may want to set it to a C<< DateTime->now >> object. |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
=head2 events_relationship |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
An class accessor that returns the relationship to get from your object |
|
237
|
|
|
|
|
|
|
to the relationship. |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
Default is C<events>, but you can override it: |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
__PACKAGE__->has_many( |
|
242
|
|
|
|
|
|
|
'cd_events' => |
|
243
|
|
|
|
|
|
|
( 'MyApp::Schema::Result::ArtistEvents', 'cdid' ), |
|
244
|
|
|
|
|
|
|
{ cascade_delete => 0 }, |
|
245
|
|
|
|
|
|
|
); |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
__PACKAGE__->events_relationship('cd_events'); |
|
248
|
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
=head2 Tables |
|
250
|
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=head3 Tracked Table |
|
252
|
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
The table with events to be tracked in the L</Tracking Table>. |
|
254
|
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
It requires the Component and L</events_relationship> in the Result class: |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
package MyApp::Schema::Result::Artist; |
|
258
|
|
|
|
|
|
|
use base qw( DBIx::Class::Core ); |
|
259
|
|
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
...; |
|
261
|
|
|
|
|
|
|
|
|
262
|
|
|
|
|
|
|
__PACKAGE__->load_components( qw/ Events / ); |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# A different name can be used with the "events_relationship" attribute |
|
265
|
|
|
|
|
|
|
__PACKAGE__->has_many( |
|
266
|
|
|
|
|
|
|
'events' => ( 'MyApp::Schema::Result::ArtistEvent', 'artistid' ), |
|
267
|
|
|
|
|
|
|
{ cascade_delete => 0 }, |
|
268
|
|
|
|
|
|
|
); |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
You can also add custom events to track when something happens. For example, |
|
271
|
|
|
|
|
|
|
you can create a method to add events when an artist changes their name: |
|
272
|
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
__PACKAGE__->add_column( |
|
274
|
|
|
|
|
|
|
last_name_change_id => { data_type => 'integer' } ); |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
__PACKAGE__->has_one( |
|
277
|
|
|
|
|
|
|
'last_name_change' => 'MyApp::Schema::Result::ArtistEvent', |
|
278
|
|
|
|
|
|
|
{ 'foreign.artisteventid' => 'self.last_name_change_id' }, |
|
279
|
|
|
|
|
|
|
{ cascade_delete => 0 }, |
|
280
|
|
|
|
|
|
|
); |
|
281
|
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
sub change_name { |
|
283
|
|
|
|
|
|
|
my ( $self, $new_name ) = @_; |
|
284
|
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
my $event = $self->event( name_change => |
|
286
|
|
|
|
|
|
|
{ details => { new => $new_name, old => $self->name } } ); |
|
287
|
|
|
|
|
|
|
$self->last_name_change( $event ); |
|
288
|
|
|
|
|
|
|
# $self->update; # be lazy and make our caller call ->update |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
$self->name( $new_name ); |
|
291
|
|
|
|
|
|
|
} |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
=head3 Tracking Table |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
This table holds the events for the L</Tracked Table>. |
|
296
|
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
The C<triggered_on> column must either provide a C<DEFAULT> value |
|
298
|
|
|
|
|
|
|
or you should add a default to L</event_defaults>. |
|
299
|
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
package MyApp::Schema::Result::ArtistEvent; |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
use warnings; |
|
303
|
|
|
|
|
|
|
use strict; |
|
304
|
|
|
|
|
|
|
use JSON; |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
use base qw( DBIx::Class::Core ); |
|
307
|
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
__PACKAGE__->load_components(qw/ InflateColumn::DateTime /); |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
__PACKAGE__->table('artist_event'); |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
__PACKAGE__->add_columns( |
|
313
|
|
|
|
|
|
|
artisteventid => { data_type => 'integer', is_auto_increment => 1 }, |
|
314
|
|
|
|
|
|
|
artistid => { data_type => 'integer' }, |
|
315
|
|
|
|
|
|
|
|
|
316
|
|
|
|
|
|
|
# The type of event |
|
317
|
|
|
|
|
|
|
event => { data_type => 'varchar' }, |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# Any other custom columns you want to store for each event. |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
triggered_on => { |
|
322
|
|
|
|
|
|
|
data_type => 'datetime', |
|
323
|
|
|
|
|
|
|
default_value => \'NOW()', |
|
324
|
|
|
|
|
|
|
}, |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# Where we store freeform data about what happened |
|
327
|
|
|
|
|
|
|
details => { data_type => 'longtext' }, |
|
328
|
|
|
|
|
|
|
); |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
__PACKAGE__->set_primary_key('artisteventid'); |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# You should set up automatic inflation/deflation of the details column |
|
333
|
|
|
|
|
|
|
# as it is used this way by "state_at" and the insert/update/delete |
|
334
|
|
|
|
|
|
|
# events. Does not have to be JSON, just be able to serialize a hashref. |
|
335
|
|
|
|
|
|
|
{ |
|
336
|
|
|
|
|
|
|
my $json = JSON->new->utf8; |
|
337
|
|
|
|
|
|
|
__PACKAGE__->inflate_column( 'details' => { |
|
338
|
|
|
|
|
|
|
inflate => sub { $json->decode(shift) }, |
|
339
|
|
|
|
|
|
|
deflate => sub { $json->encode(shift) }, |
|
340
|
|
|
|
|
|
|
} ); |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# A path back to the object that this event is for, |
|
344
|
|
|
|
|
|
|
# not required unlike the has_many "events" relationship above |
|
345
|
|
|
|
|
|
|
__PACKAGE__->belongs_to( |
|
346
|
|
|
|
|
|
|
'artist' => ( 'MyApp::Schema::Result::Artist', 'artistid' ) ); |
|
347
|
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
You probably also want an index for searching for events: |
|
349
|
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
sub sqlt_deploy_hook { |
|
351
|
|
|
|
|
|
|
my ( $self, $sqlt_table ) = @_; |
|
352
|
|
|
|
|
|
|
$sqlt_table->add_index( |
|
353
|
|
|
|
|
|
|
name => 'artist_event_idx', |
|
354
|
|
|
|
|
|
|
fields => [ "artistid", "triggered_on", "event" ], |
|
355
|
|
|
|
|
|
|
); |
|
356
|
|
|
|
|
|
|
} |
|
357
|
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head1 PRECONFIGURED EVENTS |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
Automatically creates Events for actions that modify a row. |
|
361
|
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
See the L</BUGS AND LIMITATIONS> of bulk modifications on events. |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=over |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
=item insert |
|
367
|
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
Logs all columns to the C<details> column, with an C<insert> event. |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
=item update |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
Logs dirty columns to the C<details> column, with an C<update> event. |
|
373
|
|
|
|
|
|
|
|
|
374
|
|
|
|
|
|
|
=item delete |
|
375
|
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
Logs all columns to the C<details> column, with a C<delete> event. |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
=back |
|
379
|
|
|
|
|
|
|
|
|
380
|
|
|
|
|
|
|
=head1 METHODS |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
=head2 event |
|
383
|
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
Inserts a new event with L</event_defaults>: |
|
385
|
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
my $new_event = $artist->event( $event => \%params ); |
|
387
|
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
First, the L</event_defaults> method is called to build a list of values |
|
389
|
|
|
|
|
|
|
to set on the new event. This method is passed the C<$event> and a reference |
|
390
|
|
|
|
|
|
|
to C<%params>. |
|
391
|
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
Then, the C<%params>, filtered for valid L</events_relationship> C<columns>, |
|
393
|
|
|
|
|
|
|
are added to the C<create_related> arguments, overriding the defaults. |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
=head2 state_at |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
Takes a timestamp and returns the state of the thing at that timestamp as a |
|
398
|
|
|
|
|
|
|
hash reference. Can be either a correctly deflated string or a DateTime |
|
399
|
|
|
|
|
|
|
object that will be deflated with C<format_datetime>. |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
Returns undef if the object was not C<in_storage> at the timestamp. |
|
402
|
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
my $state = $schema->resultset('Artist')->find( { name => 'David Bowie' } ) |
|
404
|
|
|
|
|
|
|
->state_at('2006-05-29 08:00'); |
|
405
|
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
An idea is to use it to recreate an object as it was at that timestamp. |
|
407
|
|
|
|
|
|
|
Of course, default values that the database provides will not be included, |
|
408
|
|
|
|
|
|
|
unless the L</event_defaults> method accounts for that. |
|
409
|
|
|
|
|
|
|
|
|
410
|
|
|
|
|
|
|
my $resurrected_object |
|
411
|
|
|
|
|
|
|
= $object->result_source->new( $object->state_at($timestamp) ); |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
See ".. format a DateTime object for searching?" under L<DBIx::Class::Manual::FAQ/Searching> |
|
414
|
|
|
|
|
|
|
for details on formatting the timestamp. |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
You can pass additional L<search|DBIx::Class::ResultSet/search> conditions and |
|
417
|
|
|
|
|
|
|
attributes to this method. This is done in context of searching the events |
|
418
|
|
|
|
|
|
|
table: |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
my $state = $object->state_at($timestamp, \%search_cond, \%search_attrs); |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
=head1 BUGS AND LIMITATIONS |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
There is no attempt to handle bulk updates or deletes. So, any changes to the |
|
425
|
|
|
|
|
|
|
database made by calling |
|
426
|
|
|
|
|
|
|
L<"update"|DBIx::Class::ResultSet/update> or L<"delete"|DBIx::Class::ResultSet/delete> |
|
427
|
|
|
|
|
|
|
will not create events the same as L<single row|DBIx::Class::Row> modifications. Use the |
|
428
|
|
|
|
|
|
|
L<"update_all"|DBIx::Class::ResultSet/update_all> or L<"delete_all"|DBIx::Class::ResultSet/delete_all> |
|
429
|
|
|
|
|
|
|
methods of the C<ResultSet> if you want these triggers. |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
There are three required columns on the L</events_relationship> table: |
|
432
|
|
|
|
|
|
|
C<event>, C<triggered_on>, and C<details>. We should eventually make those |
|
433
|
|
|
|
|
|
|
configurable. |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
=over |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
=item L<DBIx::Class::AuditAny> |
|
440
|
|
|
|
|
|
|
|
|
441
|
|
|
|
|
|
|
=item L<DBIx::Class::AuditLog> |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
=item L<DBIx::Class::Journal> |
|
444
|
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
=item L<DBIx::Class::PgLog> |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
=back |
|
448
|
|
|
|
|
|
|
|
|
449
|
|
|
|
|
|
|
=head1 AUTHOR |
|
450
|
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
Grant Street Group <developers@grantstreet.com> |
|
452
|
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
This software is Copyright (c) 2018 - 2019 by Grant Street Group. |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
This is free software, licensed under: |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
The Artistic License 2.0 (GPL Compatible) |
|
460
|
|
|
|
|
|
|
|
|
461
|
|
|
|
|
|
|
=head1 CONTRIBUTORS |
|
462
|
|
|
|
|
|
|
|
|
463
|
|
|
|
|
|
|
=for stopwords Andrew Fresh Brendan Byrd Justin Wheeler |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
=over 4 |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
=item * |
|
468
|
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
Andrew Fresh <andrew.fresh@grantstreet.com> |
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=item * |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
Andrew Fresh <andrew+github@afresh1.com> |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=item * |
|
476
|
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
Brendan Byrd <brendan.byrd@grantstreet.com> |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
=item * |
|
480
|
|
|
|
|
|
|
|
|
481
|
|
|
|
|
|
|
Justin Wheeler <justin.wheeler@grantstreet.com> |
|
482
|
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
=back |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=cut |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
__END__ |
|
488
|
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
1; |