e8b0592db17bf7ce2ade729403c957fe84b39135
[dbsrgits/DBIx-Class-Journal.git] / lib / DBIx / Class / Journal.pm
1 package DBIx::Class::Journal;
2
3 use base qw/DBIx::Class/;
4
5 use strict;
6 use warnings;
7
8 our $VERSION = '0.900001_04';
9 $VERSION = eval $VERSION; # no errors in dev versions
10
11 ## On create/insert, add new entry to AuditLog and new content to AuditHistory
12
13 sub _journal_schema {
14     my $self = shift;
15     $self->result_source->schema->_journal_schema;
16 }
17
18 sub insert {
19     my ($self, @args) = @_;
20     return if $self->in_storage;
21
22     my $res = $self->next::method(@args);
23     $self->journal_log_insert;
24
25     return $res;
26 }
27
28 sub journal_log_insert {
29     my ($self) = @_;
30
31     if ( $self->in_storage ) {
32         my $j = $self->_journal_schema;
33         my $change_id = $j->journal_create_change()->id;
34         $j->journal_update_or_create_log_entry( $self, create_id => $change_id );
35         $j->journal_record_in_history( $self, audit_change_id => $change_id );
36     }
37 }
38
39 ## On delete, update delete_id of AuditLog
40
41 sub delete {
42     my $self = shift;
43     $self->next::method(@_);
44     $self->journal_log_delete(@_);
45 }
46
47 sub journal_log_delete {
48     my ($self) = @_;
49
50     unless ($self->in_storage) {
51         my $j = $self->_journal_schema;
52         $j->journal_update_or_create_log_entry( $self, delete_id => $j->journal_create_change->id );
53     }
54 }
55
56 ## On update, copy row's new contents to AuditHistory
57
58 sub update {
59     my $self = shift;
60     $self->next::method(@_);
61     $self->journal_log_update(@_);
62 }
63
64 sub journal_log_update {
65     my $self = shift;
66
67     if ($self->in_storage) {
68         my $j = $self->_journal_schema;
69         my $change_id = $j->journal_create_change->id;
70         $j->journal_record_in_history( $self, audit_change_id => $change_id );
71     }
72 }
73
74 =head1 NAME
75
76 DBIx::Class::Journal - Auditing for tables managed by DBIx::Class
77
78 =head1 SYNOPSIS
79
80 Load the module into your L<DBIx::Class> Schema Class:
81
82  package My::Schema;
83  use base 'DBIx::Class::Schema';
84
85  __PACKAGE__->load_components(qw/Schema::Journal/);
86
87 And then call C<< $schema->bootstrap_journal >> (I<once only>) to create all
88 the tables necessary for the journal, in your database.
89
90 Optionally set where the journal is stored:
91
92  __PACKAGE__->journal_connection(['dbi:SQLite:t/var/Audit.db']);
93
94 Later on, in your application, wrap operations in transactions, and optionally
95 associate a user with the changeset:
96
97  $schema->changeset_user($user->id);
98  my $new_artist = $schema->txn_do( sub {
99     return $schema->resultset('Artist')->create({ name => 'Fred' });
100  });
101
102 =head1 DESCRIPTION
103
104 The purpose of this L<DBIx::Class> component module is to create an
105 audit-trail for all changes made to the data in your database (via a
106 DBIx::Class schema). It creates I<changesets> and assigns each
107 create/update/delete operation an I<id>. The creation and deletion date of
108 each row is stored, as well as the historical contents of any row that gets
109 changed.
110
111 All queries which need auditing B<must> be called using
112 L<DBIx::Class::Schema/txn_do>, which is used to create changesets for each
113 transaction.
114
115 To track who did which changes, the C<user_id> (an integer) of the current
116 user can be set, and a C<session_id> can also be set; both are optional. To
117 access the auditing schema to look at the auditdata or revert a change, use
118 C<< $schema->_journal_schema >>.
119
120 =head1 DEPLOYMENT
121
122 Currently the module expects to be deployed alongside a new database schema,
123 and track all changes from first entry. To do that you need to create some
124 tables in which to store the journal, and you can opitonally configure which
125 data sources (tables) have their operations journalled by the module.
126
127 Connect to your schema and deploy the journal tables as below. The module
128 automatically scans your schema and sets up storage for journal entries.
129
130  # optional - defaults to all sources
131  My::Schema->journal_sources([qw/ table1 table2 /]);
132  
133  $schema = My::Schema->connect(...);
134  $schema->journal_schema_deploy;
135
136 Note that if you are retrofitting journalling to an existing database, then as
137 well as creating the journal you will need to populate it with a history so
138 that when rows are deleted they can be mapped back to a (fake) creation.
139
140 If you ever update your original schema, remember that you must then also
141 update the journal's schema to match, so that the AuditHistory has the
142 corresponding new columns in which to save data.
143
144 =head1 TABLES
145
146 The journal schema contains a number of tables. These track row creation,
147 update and deletion, and also are aware of multiple operations taking place
148 within one transaction.
149
150 =over 4
151
152 =item ChangeSet
153
154 Each changeset row has an auto-incremented C<ID>, optional C<user_id> and
155 C<session_id>, and a C<set_date> which defaults to the current datetime. This
156 is the authoritative log of one discrete change to your database, which may
157 possible consist of a number of ChangeLog operations within a single
158 transaction.
159
160 =item ChangeLog
161
162 Each operation done within the transaction is recorded as a row in the
163 ChangeLog table. It contains an auto-incrementing C<ID>, the C<changeset_id>
164 and an C<order> column to establish the order in which changes took place.
165
166 =item AuditLog
167
168 For every table in the original database that is to be audited, an AuditLog
169 table is created. When a row appears in the original database a corresponding
170 row is added here with a ChangeLog ID in the C<create_id> column, and when
171 that original row is deleted the AuditLog is updated to add another ChangeLog
172 ID this time into the C<delete_id> column. A third id column contains the
173 primary key of the original row, so you can find it in the AuditHistory.
174
175 Note that currently only integer-based single column primary keys are
176 supported in your original database tables.
177
178 =item AuditHistory
179
180 For every table in the original database to be audited, an AuditHistory table
181 is created. This is where the actual field data from your original table rows
182 are stored on creation and on each update.
183
184 Each row in the AuditHistory has a C<change_id> field containing the ID of the
185 ChangeLog row. The other fields correspond to all the fields from the original
186 table (with any constraints removed). Each time a column value in the original
187 table is changed, the entire row contents after the change are added as a new
188 row in this table.
189
190 =back
191
192 =head1 CLASS METHODS
193
194 Call these in your Schema Class such as the C<My::Schema> package file, as in
195 the SYNOPSIS, above.
196
197 =over 4
198
199 =item journal_connection \@connect_info
200
201 Set the connection information for the database to save your audit information
202 to.
203
204 Leaving this blank assumes you want to store the audit data into your current
205 database. The storage object will be shared by the regular schema and the
206 journalling schema.
207
208 =item journal_components @components
209
210 If you want to add components to your journal
211 (L<DBIx::Class::Schema::Versioned> for example) pass them here.
212
213 =item journal_sources \@source_names
214
215 Set a list of source names you would like to audit. If unset, all sources are
216 used.
217
218 NOTE: Currently only sources with a single-column integer PK are supported, so
219 use this method if you have sources which don't comply with that limitation.
220
221 =item journal_storage_type $type
222
223 Enter the special storage type of your journal schema if needed. See
224 L<DBIx::Class::Storage::DBI> for more information on storage types.
225
226 =item journal_user \@rel
227
228 The user_id column in the L</ChangeSet> will be linked to your user id with a
229 C<belongs_to> relation, if this is set with the appropriate arguments. For
230 example:
231
232  __PACKAGE__->journal_user(['My::Schema::User', {'foreign.userid' => 'self.user_id'}]);
233
234 =back
235
236 =head1 OBJECT METHODS
237
238 Once you have a connection to your database, call these methods to manage the
239 journalling.
240
241 =over 4
242
243 =item bootstrap_journal
244
245 This calls C<journal_schema_deploy> followed by C<prepopulate_journal> to
246 create your journal tables and if necessary populate them with a snapshot of
247 your current original schema data.
248
249 Do not run this method more than once on your database, as redeploying the
250 journal schema is not supported.
251
252 =item journal_schema_deploy
253
254 Will use L<DBIx::Class::Schema/deploy> to set up the tables for journalling in
255 your schema. Use this method to set up your journal.
256
257 Note that if you are retrofitting journalling to an existing database, then as
258 well as creating the journal you will need to populate it with a history so
259 that when rows are deleted they can be mapped back to a (fake) creation.
260
261 Do not run this method more than once on your database, as redeploying the
262 journal schema is not supported.
263
264 =item prepopulate_journal
265
266 Will load the current state of your original source tables into the audit
267 history as fake inserts in a single initial changeset. The advantage to this
268 is that later deletetions of the row will be consistent in the journal with an
269 initial state.
270
271 Note that this can be an intensive and time consuming task, depending on how
272 much data you have in your original sources; all of it will be copied to the
273 journal history. However this step is essential if you are retrofitting
274 Journalling to a schema with existing data, otherwise when you delete a row
275 the Journal will die because it cannot relate that to an initial row insert.
276
277 =item changeset_user $user_id
278
279 Set the C<user_id> for the following changeset(s). This must be an integer.
280
281 =item changeset_session $session_id
282
283 Set the C<session_id> for the following changeset(s). This must be an integer.
284
285 =item deploy
286
287 Overloaded L<DBIx::Class::Schema/deploy> which will deploy your original
288 database schema and following that will deploy the journal schema.
289
290 =item txn_do $code_ref, @args
291
292 Overloaded L<DBIx::Class::Schema/txn_do>, this must be used to start a new
293 ChangeSet to cover a group of changes. Each subsequent change to an audited
294 table will use the C<changeset_id> created in the most recent C<txn_do> call.
295
296 Currently nested C<txn_do> calls cause a single ChangeSet object to be created.
297
298 =back
299
300 =head2 Deprecated Methods
301
302 =over 4
303
304 =item journal_deploy_on_connect $bool
305
306 If set to a true value will cause C<journal_schema_deploy> to be called on
307 C<connect>. Not recommended (because re-deploy of a schema is not supported),
308 but present for backwards compatibility.
309
310 =back
311
312 =head1 TROUBLESHOOTING
313
314 For PostgreSQL databases you must enable quoting on SQL command generation by
315 passing C<< { quote_char => q{`}, name_sep => q{.} } >> when connecting to the
316 database.
317
318 =head1 SEE ALSO
319
320 =over 4
321
322 =item *
323
324 L<DBIx::Class> - You'll need it to use this.
325
326 =back
327
328 =head1 LIMITATIONS
329
330 =over 4
331
332 =item *
333
334 Only single-column integer primary key'd tables are supported for auditing.
335
336 =item *
337
338 Updates made via L<DBIx::Class::ResultSet/update> are not yet supported.
339
340 =item *
341
342 No API for viewing or restoring changes yet.
343
344 =back
345
346 Patches for the above are welcome ;-)
347
348 =head1 AUTHOR
349
350 Jess Robinson <castaway@desert-island.me.uk>
351
352 Matt S. Trout <mst@shadowcatsystems.co.uk> (ideas and prodding)
353
354 =head1 LICENCE
355
356 You may distribute this code under the same terms as Perl itself.
357
358 =cut
359
360 1;