package DBIx::Class::AuditLog;
{
  $DBIx::Class::AuditLog::VERSION = '0.3.0';
}

use base qw/DBIx::Class/;

use strict;
use warnings;

sub insert {
    my $self = shift;

    return $self->next::method(@_) if $self->in_storage;

    my $result = $self->next::method(@_);

    my ( $action, $table ) = $self->_action_setup( $result, 'insert' );

    if ($action) {
        my %column_data = $result->get_columns;
        $self->_store_changes( $action, $table, {}, \%column_data );
    }

    return $result;
}

sub update {
    my $self = shift;
    my $stored_row = $self->get_from_storage;
    my %old_data   = $stored_row->get_columns;
    my %new_data   = $self->get_columns;
    my @changed_columns = keys %{$_[0]||{}};

    my $result = $self->next::method(@_);

    if (@changed_columns) {
        @new_data{@changed_columns} = map $self->get_column($_),
            @changed_columns;
    }

    foreach my $col ($self->columns){
	    if($self->_force_audit($col)){
		    $old_data{$col} = $stored_row->get_column($col) 
		    	unless defined $old_data{$col};
		    $new_data{$col} = $self->get_column($col) 
		    	unless defined $new_data{$col};
	    }
    }

    foreach my $key ( keys %new_data ) {
        if (   defined $old_data{$key}
            && defined $new_data{$key}
	    && (! $self->_force_audit($key))
            && $old_data{$key} eq $new_data{$key} )
        {
            delete $new_data{$key};
        }
    }

    if ( keys %new_data ) {
        my ( $action, $table )
            = $self->_action_setup( $stored_row, 'update' );

        if ($action) {
            $self->_store_changes( $action, $table, \%old_data, \%new_data );
        }
    }

    return $result;
}

sub delete {
    my $self = shift;

    my $stored_row = $self->get_from_storage;

    my $result = $self->next::method(@_);

    my ( $action, $table ) = $self->_action_setup( $stored_row, 'delete' );

    if ($action) {
        my %old_data = $stored_row->get_columns;
        $self->_store_changes( $action, $table, \%old_data, {} );
    }

    return $result;
}


sub _audit_log_schema {
    my $self = shift;
    return $self->result_source->schema->audit_log_schema;
}

sub _action_setup {
    my $self = shift;
    my $row  = shift;
    my $type = shift;

    return $self->_audit_log_schema->audit_log_create_action(
        {   row   => $row->id,
            table => $row->result_source_instance->name,
            type  => $type,
        }
    );

}

sub _store_changes {
    my $self       = shift;
    my $action     = shift;
    my $table      = shift;
    my $old_values = shift;
    my $new_values = shift;

    foreach my $column (
        keys %{$new_values} ? keys %{$new_values} : keys %{$old_values} )
    {
        if ( $self->_do_audit($column) ) {
            my $field
                = $table->find_or_create_related( 'Field', { name => $column } );

        my $create_params = {
            field => $field->id,
        };

        if($self->_do_modify_audit_value($column)){
                    $create_params->{new_value} = 
                $self->_modify_audit_value($column,$new_values->{$column});
                    $create_params->{old_value} = 
                $self->_modify_audit_value($column,$old_values->{$column});
        }else{
                    $create_params->{new_value} = $new_values->{$column};
                    $create_params->{old_value} = $old_values->{$column};
        }

            $action->create_related(
                'Change',
		$create_params,
            );
        }
    }
}

sub _force_audit{
    my ($self, $column) = @_;

    my $info = $self->column_info($column);
    return
        defined $info->{force_audit_log_column};

}

sub _do_audit {
    my $self   = shift;
    my $column = shift;

    return 1 if $self->_force_audit($column);

    my $info = $self->column_info($column);
    return
        defined $info->{audit_log_column}
        && $info->{audit_log_column} == 0 ? 0 : 1;
}

sub _do_modify_audit_value{
    my $self   = shift;
    my $column = shift;

    my $info = $self->column_info($column);

    return
        $info->{modify_audit_value} ? 1 : 0; 
}

sub _modify_audit_value{
    my $self   = shift;
    my $column = shift;
    my $value = shift;

    my $info = $self->column_info($column);
    my $meth = $info->{modify_audit_value};
    return $value unless 
        defined $meth;

    return &$meth($self, $value)
        if ref($meth) eq 'CODE';

    $meth = "modify_audit_$column"
        unless $self->can($meth);

    return $self->$meth($value)
        if $self->can($meth);

    die "unable to find modify_audit_method ($meth) for $column in $self";

}

# ABSTRACT: Simple activity audit logging for DBIx::Class

1;



=pod

=head1 NAME

DBIx::Class::AuditLog - Simple activity audit logging for DBIx::Class

=head1 VERSION

version 0.3.0

=head1 NAME

DBIx::Class::AuditLog - Simple activity audit logging for DBIx::Class

=head1 VERSION

version 0.2.6

=head1 DBIx::Class OVERRIDDEN METHODS

=head2 insert

=head2 update

=head2 delete

=head1 HELPER METHODS

=head2 _audit_log_schema

Returns the AuditLog schema from storage.

    my $al_schema = $schmea->audit_log_schema;

=head2 _action_setup

Creates a new AuditLog Action for a specific type.

Requires:
    row: primary key of the table that is being audited
    type: action type, 1 of insert/update/delete

=head2 _store_changes

Store the column data that has changed

Requires:
    action: the action object that has associated changes
    old_values: the old values are being replaced
    new_values: the new values that are replacing the old
    table: dbic object of the audit_log_table object

=head2 _do_audit

Returns 1 or 0 if the column should be audited or not.

Requires:
    column: the name of the column/field to check

=head2 _force_audit

Returns 1 or 0 if the column should be audited even if its value did not change.

Requires:
    column: the name of the column/field to check

=head2 _do_modify_audit_value

Returns 1 or 0 if the columns value should be modified before audit.

Requires:
    column: the name of the column/field to check

=head2 _modify_audit_value

Modifies the colums audit-value. Dies if no modify-method could be found.

Returns:
    the modified value

Requires:
    column: the name of the column/field to check
    value: the original value

=head1 AUTHOR

Mark Jubenville <ioncache@gmail.com>

=head1 CONTRIBUTORS

Lukas Thiemeier <lukast@cpan.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Mark Jubenville.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=head1 AUTHOR

Mark Jubenville <ioncache@gmail.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2012 by Mark Jubenville.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut


__END__

