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 $al->update_or_create({
33 ( map { $_ => $self->get_column($_)} $self->primary_columns ),
34 created => { changeset_id => $al->result_source->schema->current_changeset },
41 ## On delete, update delete_id of AuditLog
45 my ($self, @rest) = @_;
46 $self->next::method(@rest);
48 if(!$self->in_storage)
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 });
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);
62 ## On update, copy previous row's contents to AuditHistory
66 my ($self, $upd, @rest) = @_;
70 my $s_name = $self->result_source->source_name();
71 my $ah = $self->result_source->schema->_journal_schema->resultset("${s_name}AuditHistory");
73 my $obj = $self->result_source->resultset->find( $self->ident_condition );
79 $self->next::method($upd, @rest);
84 DBIx::Class::Journal - auditing for tables managed by DBIx::Class
89 use base 'DBIx::Class::Schema';
91 __PACKAGE__->load_components(qw/+DBIx::Class::Schema::Journal/);
93 __PACKAGE__->journal_connection(['dbi:SQLite:t/var/Audit.db']);
94 __PACKAGE__->journal_user(['My::Schema::User', {'foreign.userid' => 'self.user_id'}]);
99 $schema->changeset_user($user->id);
100 my $new_artist = $schema->txn_do( sub {
101 return = $schema->resultset('Artist')->create({ name => 'Fred' });
107 The purpose of this L<DBIx::Class> component module is to create an
108 audit-trail for all changes made to the data in your database (via a
109 DBIx::Class schema). It creates changesets and assigns each
110 create/update/delete operation an id. The creation and deletion date
111 of each row is stored, as well as the previous contents of any row
114 All queries which want auditing should be called using
115 L<DBIx::Class::Schema/txn_do>, which is used to create changesets for
118 To track who did which changes, the user_id (an integer) of the
119 current user can be set, a session_id can also be set, both are
122 To access the auditing schema to look at the auditdata or revert a
123 change, use C<< $schema->_journal_schema >>.
127 The journal schema contains a number of tables.
133 Each changeset row has an auto-incremented ID, optional user_id and
134 session_id, and a set_date which defaults to the current datetime.
136 A ChangeSet has_many Changes.
140 Each change/operation done in the transaction is recorded as a row in
141 the Change table. It contains an auto-incrementing ID, the
142 changeset_id and an order column for the ordering of each change in
147 For every table in the original database that is to be audited, an
148 AuditLog table is created. Each auditlog row has an id which will
149 contain the primary key of the table it is associated with. (NB:
150 currently only supports integer-based single column PKs). The
151 create_id and delete_id fields contain the IDs of the Changes that
152 created or deleted this row.
156 For every table in the original database to be audited, an
157 AuditHistory table is created. Each row has a change_id field
158 containing the ID of the Change row. The other fields correspond to
159 all the fields from the original table. Each time a column value in
160 the original table is changed, the entire row contents before the
161 change are added as a new row in this table.
169 =item journal_connection
171 =item Arguments: \@connect_info
175 Set the connection information for the database to save your audit
176 information to. Leaving this blank assumes you want to store the audit
177 data into your current database.
181 =item journal_sources
183 =item Arguments: \@source_names
187 Set a list of source names you would like to audit, if unset, all
190 NOTE: Currently only sources with a single-column PK are supported, so
191 use this method if you have sources with multi-column PKs.
195 =item journal_storage_type
197 =item Arguments: $storage_type
201 Enter the special storage type of your journal schema if needed. See
202 L<DBIx::Class::Storage::DBI> for more information on storage types.
208 =item Arguments: \@relation_args
212 The user_id column in the L</ChangeSet> will be linked to your user id
213 with a belongs_to relation, if this is set with the appropriate
220 =item Arguments: $user_id
224 Set the user_id for the following changeset(s). This must be an integer.
228 =item changeset_session
230 =item Arguments: $user_id
234 Set the session_id for the following changeset(s). This must be an integer.
240 =iitem Arguments: $code_ref
244 Overloaded L<DBIx::Class::Schema/txn_do>, this must be used to start a
245 new changeset to cover a group of changes. Each subsequent change to
246 an audited table will use the changeset_id created in the most recent
251 L<DBIx::Class> - You'll need it to use this.
255 Only single-column integer primary key'd tables are supported for auditing so far.
257 Updates made via L<DBIx::Class::ResultSet/update> are not yet supported.
259 No API for viewing or restoring changes yet.
261 Patches for the above welcome ;)
265 Jess Robinson <castaway@desert-island.me.uk>
267 Matt S. Trout <mst@shadowcatsystems.co.uk> (ideas and prodding)
271 You may distribute this code under the same terms as Perl itself.