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