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