ccc1fd382045fec910d7b104914144248fb58990
[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_02';
9 $VERSION = eval $VERSION; # no errors in dev versions
10
11 ## On create/insert, add new entry to AuditLog
12
13 sub _journal_schema {
14     my $self = shift;
15     $self->result_source->schema->_journal_schema;
16 }
17
18 sub insert
19 {
20     my ($self, @args) = @_;
21
22     return if($self->in_storage);
23
24     my $res = $self->next::method(@args);
25
26     $self->journal_log_insert();
27
28     return $res;
29 }
30
31 sub journal_log_insert
32 {
33     my ($self) = @_;
34
35     if ( $self->in_storage ) {
36         my $j = $self->_journal_schema;
37         my $change_id = $j->journal_create_change()->id;
38         $j->journal_update_or_create_log_entry( $self, create_id => $change_id );
39         $j->journal_record_in_history( $self, audit_change_id => $change_id );
40     }
41 }
42
43 ## On delete, update delete_id of AuditLog
44
45 sub delete
46 {
47     my ($self, @rest) = @_;
48     $self->next::method(@rest);
49     $self->journal_log_delete(@rest);
50 }
51
52 sub journal_log_delete
53 {
54     my ($self) = @_;
55
56     unless ($self->in_storage) {
57         my $j = $self->_journal_schema;
58         $j->journal_update_or_create_log_entry( $self, delete_id => $j->journal_create_change->id );
59     }
60 }
61
62 ## On update, copy previous row's contents to AuditHistory
63
64 sub update
65 {
66     my ($self, $upd, @rest) = @_;
67     $self->journal_log_update($upd, @rest);
68     $self->next::method($upd, @rest);
69 }
70
71 sub journal_log_update
72 {
73     my ($self, $upd, @rest) = @_;
74
75     if($self->in_storage)
76     {
77         my $j = $self->_journal_schema;
78
79         my $change = $j->journal_create_change;
80         my $prev = $self->result_source->resultset->find( $self->ident_condition );
81         $j->journal_record_in_history( $prev, audit_change_id => $change );
82     }
83 }
84
85 =head1 NAME
86
87 DBIx::Class::Journal - auditing for tables managed by DBIx::Class
88
89 =head1 SYNOPSIS
90
91  package My::Schema;
92  use base 'DBIx::Class::Schema';
93
94  __PACKAGE__->load_components(qw/Schema::Journal/);
95
96  __PACKAGE__->journal_connection(['dbi:SQLite:t/var/Audit.db']);
97  __PACKAGE__->journal_user(['My::Schema::User', {'foreign.userid' => 'self.user_id'}]);
98
99
100  #######
101
102  $schema->changeset_user($user->id);
103  my $new_artist = $schema->txn_do( sub {
104     return $schema->resultset('Artist')->create({ name => 'Fred' });
105  });
106
107
108 =head1 DESCRIPTION
109
110 The purpose of this L<DBIx::Class> component module is to create an
111 audit-trail for all changes made to the data in your database (via a
112 DBIx::Class schema). It creates changesets and assigns each
113 create/update/delete operation an id. The creation and deletion date
114 of each row is stored, as well as the previous contents of any row
115 that gets changed.
116
117 All queries which need auditing must be called using
118 L<DBIx::Class::Schema/txn_do>, which is used to create changesets for
119 each transaction.
120
121 To track who did which changes, the user_id (an integer) of the
122 current user can be set, a session_id can also be set, both are
123 optional.
124
125 To access the auditing schema to look at the auditdata or revert a
126 change, use C<< $schema->_journal_schema >>.
127
128 =head2 TABLES
129
130 The journal schema contains a number of tables.
131
132 =over
133
134 =item ChangeSet
135
136 Each changeset row has an auto-incremented ID, optional user_id and
137 session_id, and a set_date which defaults to the current datetime.
138
139 A ChangeSet has_many Changes.
140
141 =item ChangeLog
142
143 Each change/operation done in the transaction is recorded as a row in
144 the ChangeLog table. It contains an auto-incrementing ID, the
145 changeset_id and an order column for the ordering of each change in
146 the changeset.
147
148 =item AuditLog
149
150 For every table in the original database that is to be audited, an
151 AuditLog table is created. Each auditlog row has an id which will
152 contain the primary key of the table it is associated with. (NB:
153 currently only supports integer-based single column PKs). The
154 create_id and delete_id fields contain the IDs of the Changes that
155 created or deleted this row.
156
157 =item AuditHistory
158
159 For every table in the original database to be audited, an
160 AuditHistory table is created. Each row has a change_id field
161 containing the ID of the ChangeLog row. The other fields correspond to
162 all the fields from the original table. Each time a column value in
163 the original table is changed, the entire row contents before the
164 change are added as a new row in this table.
165
166 =back
167
168 =head2 METHODS
169
170 =over
171
172 =item journal_connection \@connect_info
173
174 Set the connection information for the database to save your audit
175 information to.
176
177 Leaving this blank assumes you want to store the audit data into your current
178 database. The storage object will be shared by the regular schema and the
179 journalling schema.
180
181 =item journal_sources \@source_names
182
183 Set a list of source names you would like to audit, if unset, all
184 sources are used.
185
186 NOTE: Currently only sources with a single-column PK are supported, so
187 use this method if you have sources with multi-column PKs.
188
189 =item journal_storage_type $type
190
191 Enter the special storage type of your journal schema if needed. See
192 L<DBIx::Class::Storage::DBI> for more information on storage types.
193
194 =item journal_user \@rel
195
196 The user_id column in the L</ChangeSet> will be linked to your user id
197 with a belongs_to relation, if this is set with the appropriate
198 arguments.
199
200 =item journal_deploy_on_connect $bool
201
202 If set to a true value will cause C<journal_schema_deploy> to be called on
203 C<connect>.
204
205 Not reccomended, but present for backwards compatibility.
206
207 =item changeset_user $user_id
208
209 Set the user_id for the following changeset(s). This must be an integer.
210
211 =item changeset_session $session_id
212
213 Set the session_id for the following changeset(s). This must be an integer.
214
215 =item txn_do $code_ref, @args
216
217 Overloaded L<DBIx::Class::Schema/txn_do>, this must be used to start a
218 new changeset to cover a group of changes. Each subsequent change to
219 an audited table will use the changeset_id created in the most recent
220 txn_do call.
221
222 Currently nested C<txn_do> calls cause a single ChangeSet object to be created.
223
224 =back
225
226 =head1 SEE ALSO
227
228 L<DBIx::Class> - You'll need it to use this.
229
230 =head1 NOTES
231
232 Only single-column integer primary key'd tables are supported for auditing so far.
233
234 Updates made via L<DBIx::Class::ResultSet/update> are not yet supported.
235
236 No API for viewing or restoring changes yet.
237
238 Patches for the above welcome ;)
239
240 =head1 AUTHOR
241
242 Jess Robinson <castaway@desert-island.me.uk>
243
244 Matt S. Trout <mst@shadowcatsystems.co.uk> (ideas and prodding)
245
246 =head1 LICENCE
247
248 You may distribute this code under the same terms as Perl itself.
249
250 =cut
251
252 1;