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