package DBIx::Class::Preview;

use warnings;
use strict;
use Storable ();
use base qw/DBIx::Class/;

=head1 VERSION

Version 1.000000

=cut

our $VERSION = '1.000000';

=head1 NAME

DBIx::Class::Preview

=head1 SYNOPSIS

Add component to schema class.

  package MyApp::Schema;

  __PACKAGE__->load_components(qw/Schema::Preview/);

  ...

Add component to result class.

  package MyApp::Schema::Artist;

__PACKAGE__->load_components('Preview');

  ...

And then elsewhere..

  # turn on preview mode
  $schema->preview_active(1);

  # update a row of a previewed class
  my $row = $schema->resultset('Artist')->create({ name => 'luke' });

  # publish changes to live tables
  $schema->publish();

=head1 DESCRIPTION

When preview mode is active, all reads and writes to the previewed sources are redirected to a preview table.
When L</publish> is called, these changes are synced to the live table from the previewed table.

You will need to set these additional preview tables up yourself - see L</SETUP>.

A typical use of this would be for preview mode to be active for moderators for a website but not for normal users.
The moderators would make changes as they see fit then when happy with them they can 'publish' those changes.

=head1 SETUP

For each table to be previewed, a separate table needs to be set up which has the same columns as the original
table and also two extra columns - 'dirty' and 'deleted'. Also, these extra tables need to be populated
with the rows from the original table before they can be used. For example, to setup a preview table for an 
'artist' table in MySQL, you might use the following SQL:

  CREATE TABLE artist_preview LIKE artist;
  ALTER TABLE artist_preview ADD COLUMN dirty TINYINT NOT NULL DEFAULT 0;
  ALTER TABLE artist_preview ADD COLUMN deleted TINYINT NOT NULL DEFAULT 0;
  INSERT INTO artist_preview (SELECT *, 0, 0 FROM artist);

Or in SQLite:

  CREATE TABLE artist_preview (
    artistid INTEGER PRIMARY KEY NOT NULL,
    name varchar(100),
    dirty INTEGER DEFAULT '0',
    deleted INTEGER DEFAULT '0'
  );
  insert into artist_preview select *, 0, 0 from artist;

=head1 SCHEMA METHODS

=head2 preview_active

Accessor to activate or deactivate preview mode. Setting this to 0 will cause reads and writes to go to the
original sources and 1 will cause the previewed sources.

=head2 publish

Copies all 'dirty' changes from the previewed sources to the original sources. Essentially this means finding and
deleting all rows from the original table that are marked as deleted in the previewed table and finding and updating
all rows from the original table with the columns from the rows marked as dirty in the previewed tables.

=head2 ISSUES

- Updating a row in the original source will not cause the change to be made to the previewed source as well.
  Patches welcome to handle this behaviour, though beware of clashes with unpublished changes to the previewed rows.

- Using the resultset methods 'update' and 'delete' will go uncaught by this module - what will happen is that 
  the previewed rows will be updated but will not be marked as dirty or deleted. You should use update_all or delete_all
  instead or alternatively send a patch

- You have to create the previewed tables. Some people might find it preferable for the module to provide a method to
  do it for you. But it doesn't yet.

=head2 BUGS / CONTRIBUTING

The best way is to email the DBIx::Class mailing list (http://lists.scsys.co.uk/mailman/listinfo/dbix-class/)
with a description and/or patch (against svn trunk - http://dev.catalyst.perl.org/repos/bast/DBIx-Class-Preview/1.000/trunk/).
Alternatively email the author directly or use RT.

=cut

__PACKAGE__->mk_group_accessors( 'simple' => qw/_current_partition/ );

sub table {
    my $class       = shift;

    my $table_class = 'DBIx::Class::ResultSource::Table::Previewed';
    $class->ensure_class_loaded($table_class), $class->table_class($table_class)
      unless $class->table_class->isa($table_class);

    my $ret = $class->next::method(@_);
	return $ret;
}

sub update {
    my $self = shift;

	# mark row as dirty
	if ($self->result_source->schema->preview_active() && $self->result_source->can('is_preview_source')) {
		$_[0] = {} unless ref $_[0];
		$_[0]->{dirty} = 1 unless defined $_[0]->{dirty};
	}

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

sub insert {
    my $self = shift;

	# mark row as dirty
	if ($self->result_source->schema->preview_active() && $self->result_source->can('is_preview_source')) {
		$self->set_column('dirty', 1);
	}

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

sub delete {
    my $self = shift;

	# mark row as dirty
	if ($self->result_source->schema->preview_active() && $self->result_source->can('is_preview_source')) {
    return $self->update({ deleted => 1 });
	} else {
    return $self->next::method(@_);
  }
}

# dirty col set here by default value on the table def
sub new {
    my $self = shift;
    return $self->next::method(@_);
}

=head1 AUTHOR

  Luke Saunders <luke.saunders@gmail.com>

  Initial development sponsored by and (c) Takkle, Inc. 2007

=head1 CONTRIBUTORS

  Eden Cardim <eden@shadowcatsystems.co.uk>

=head1 LICENSE

  This library is free software under the same license as perl itself

=cut

1;
