The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Jifty::Plugin::RecordHistory::Mixin::Model::RecordHistory;
use strict;
use warnings;
use base 'Exporter';

our @EXPORT = qw(
    changes
    start_change end_change current_change
    hide_change_field
);

sub import {
    my $class = shift;
    my %args  = (
        cascaded_delete => 1,
        delete_change   => 0,
        @_,
    );

    my $caller = caller;

    $class->export_to_level(1);

    $caller->add_trigger(after_create => sub {
        my $self = shift;
        my $id   = ${ shift @_ };

        return if !$id; # the actual create failed

        my $change = Jifty::Plugin::RecordHistory::Model::Change->new(current_user => Jifty::CurrentUser->superuser);
        $change->create(
            created_by   => $self->current_user->id,
            record_class => ref($self),
            record_id    => $id,
            type         => 'create',
        );
    });

    $caller->add_trigger(after_set => sub {
        my $self = shift;
        my %args = (
            column    => undef,
            value     => undef,
            old_value => undef,
            %{ shift @_ },
        );

        my $change = $self->current_change || do {
            my $change = Jifty::Plugin::RecordHistory::Model::Change->new(current_user => Jifty::CurrentUser->superuser);
            $change->create(
                created_by   => $self->current_user->id,
                record_class => ref($self),
                record_id    => $self->id,
                type         => 'update',
            );
            $change
        };

        $change->add_change_field(
            field     => $args{column},
            old_value => $args{old_value},
            new_value => $args{value},
        );
    });

    if ($args{cascaded_delete}) {
        # we hook into before_delete so we can still access ->changes etc
        $caller->add_trigger(before_delete => sub {
            my $self = shift;

            my $changes = $self->changes;
            $changes->current_user(Jifty::CurrentUser->superuser);

            while (my $change = $changes->next) {
                my $change_fields = $change->change_fields;
                while (my $change_field = $change_fields->next) {
                    $change_field->delete;
                }

                $change->delete;
            }
        });
    }

    # this is intentionally added AFTER the previous trigger, otherwise we'll
    # create the delete change in this trigger then delete it in the other
    if ($args{delete_change}) {
        $caller->add_trigger(before_delete => sub {
            my $self = shift;

            my $change = Jifty::Plugin::RecordHistory::Model::Change->new(current_user => Jifty::CurrentUser->superuser);
            $change->create(
                created_by   => $self->current_user,
                record_class => ref($self),
                record_id    => $self->id,
                type         => 'delete',
            );
        });
    }

    # wrap update actions in a change so we can group them as one change with
    # many field changes
    $caller->add_trigger(start_update_action => sub {
        my $self = shift;
        $self->start_change;
    });

    $caller->add_trigger(end_update_action => sub {
        my $self = shift;
        $self->end_change;
    });
}

sub changes {
    my $self = shift;
    my $changes = Jifty::Plugin::RecordHistory::Model::ChangeCollection->new;

    $changes->limit(
        column => 'record_class',
        value  => ref($self),
    );
    $changes->limit(
        column => 'record_id',
        value  => $self->id,
    );

    $changes->order_by(
        column => 'id',
        order  => 'asc',
    );

    return $changes;
}

sub start_change {
    my $self = shift;
    my $type = shift || 'update';

    my %args = (
        created_by   => $self->current_user->id,
        record_class => ref($self),
        record_id    => $self->id,
        type         => $type,
        @_,
    );

    my $change = Jifty::Plugin::RecordHistory::Model::Change->new(current_user => Jifty::CurrentUser->superuser);
    if ($type eq 'update') {
        $change->deferred_create(%args);
    }
    else {
        $change->create(%args);
    }

    return $self->{change} = $change;
}

sub end_change {
    my $self = shift;
    return delete $self->{change};
}

sub current_change {
    my $self = shift;
    return $self->{change};
}

sub hide_change_field {
    return 0;
}

1;