1 package DBIx::Class::Journal;
3 use base qw/DBIx::Class/;
10 ## On create/insert, add new entry to AuditLog
14 # my ($class, $attrs, @rest) = @_;
16 # $class->result_source->schema->_journal_schema->current_user(delete $attrs->{user_id});
18 # $class->next::method($attrs, @rest);
25 return if($self->in_storage);
26 ## create new transaction here?
27 my $res = $self->next::method();
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)
35 $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self );
41 # changeset => $self->result_source->schema->_journal_schema->current_changeset(),
49 ## On delete, update delete_id of AuditLog
53 my ($self, @rest) = @_;
54 $self->next::method(@rest);
56 if(!$self->in_storage)
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)
63 $self->throw_exception( "More than one possible key found for auto-inc on ".ref $self );
68 my $alentry = $al->find({ID => $pri});
69 $self->throw_exception( "No audit_log entry found for ".ref($self) . " item $pri" ) if(!$alentry);
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);
80 ## On update, copy previous row's contents to AuditHistory
84 my ($self, $upd, @rest) = @_;
88 my $s_name = $self->result_source->source_name();
89 my $ah = $self->result_source->schema->_journal_schema->resultset("${s_name}AuditHistory");
91 my $obj = $self->result_source->resultset->find( $self->ident_condition );
97 $self->next::method($upd, @rest);
102 DBIx::Class::Journal - auditing for tables managed by DBIx::Class
107 use base 'DBIx::Class::Schema';
109 __PACKAGE__->load_components(qw/+DBIx::Class::Schema::Journal/);
111 __PACKAGE__->journal_connection(['dbi:SQLite:t/var/Audit.db']);
112 __PACKAGE__->journal_user(['My::Schema::User', {'foreign.userid' => 'self.user_id'}]);
117 $schema->changeset_user($user->id);
118 my $new_artist = $schema->txn_do( sub {
119 return = $schema->resultset('Artist')->create({ name => 'Fred' });
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
132 All queries which want auditing should be called using
133 L<DBIx::Class::Schema/txn_do>, which is used to create changesets for
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
140 To access the auditing schema to look at the auditdata or revert a
141 change, use C<< $schema->_journal_schema >>.
145 The journal schema contains a number of tables.
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.
154 A ChangeSet has_many Changes.
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
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.
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.
187 =item journal_connection
189 =item Arguments: \@connect_info
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.
199 =item journal_sources
201 =item Arguments: \@source_names
205 Set a list of source names you would like to audit, if unset, all
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.
213 =item journal_storage_type
215 =item Arguments: $storage_type
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.
226 =item Arguments: \@relation_args
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
238 =item Arguments: $user_id
242 Set the user_id for the following changeset(s). This must be an integer.
246 =item changeset_session
248 =item Arguments: $user_id
252 Set the session_id for the following changeset(s). This must be an integer.
258 =iitem Arguments: $code_ref
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
269 L<DBIx::Class> - You'll need it to use this.
273 Only single-column integer primary key'd tables are supported for auditing so far.
275 Updates made via L<DBIx::Class::ResultSet/update> are not yet supported.
277 No API for viewing or restoring changes yet.
279 Patches for the above welcome ;)
283 Jess Robinson <castaway@desert-island.me.uk>
285 Matt S. Trout <mst@shadowcatsystems.co.uk> (ideas and prodding)
289 You may distribute this code under the same terms as Perl itself.