# $Id$
## no critic (RequireFinalReturn)
package Handel::Storage::Result;
use strict;
use warnings;

BEGIN {
    use base qw/Class::Accessor::Grouped/;
    use Scalar::Util ();

    __PACKAGE__->mk_group_accessors('simple', qw/storage_result storage/);
};

sub can {
    return unless Scalar::Util::blessed($_[0]);

    return shift->storage_result->can(shift);
};

sub delete {
    Handel::Exception::Virtual->throw;
};

sub discard_changes {
    Handel::Exception::Virtual->throw;
};

sub has_column {
    my ($self, $column) = @_;
    if ($self->can($column)) {
        return 1;
    } else {
        return;
    };
};

sub update {
    Handel::Exception::Virtual->throw;
};

sub add_item {
    return $_[0]->storage->add_item(@_)
};

sub delete_items {
    return $_[0]->storage->delete_items(@_);
};

sub count_items {
    return $_[0]->storage->count_items(@_);
};

sub search_items {
    return $_[0]->storage->search_items(@_);
};

sub items {
    return shift->search_items(@_);
};

sub create_instance {
    my ($self, $result, $storage) = @_;
    my $class = Scalar::Util::blessed $self ? Scalar::Util::blessed $self : $self;

    return bless {
        storage_result => $result,
        storage        => $storage
    }, $class;
};

sub txn_begin {
    return shift->storage->txn_begin;
};

sub txn_commit {
    return shift->storage->txn_commit;
};

sub txn_rollback {
    return shift->storage->txn_rollback;
};

sub AUTOLOAD {
    my $self = shift;
    return if (our $AUTOLOAD) =~ /::DESTROY$/;

    $AUTOLOAD =~ s/^.*:://;

    return $self->storage_result->$AUTOLOAD(@_);
};

1;
__END__

=head1 NAME

Handel::Storage::Result - Generic result object returned by storage operations

=head1 SYNOPSIS

    use Handel::Storage::DBIC::Cart;
    
    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    print $result->id;
    print $result->name;

=head1 DESCRIPTION

Handel::Storage::Result is a generic wrapper around objects returned by various
Handel::Storage operations. Its main purpose is to abstract storage result
objects away from the Cart/Order/Item classes that use them. Each result is
assumed to exposed methods for each 'property' or 'column' it has, as well as
support the methods described below.

=head1 METHODS

=head2 AUTOLOAD

Maps undefined method calls to the underlying result object.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    print $result->shopper;
    
    #is really this:
    print $result->storage_result->shopper;

=head2 add_item

=over

=item Arguments: \%data

=back

Adds a new item to the current result, returning a storage result object.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    my $item = $result->add_item({
        sku => 'ABC123'
    });
    
    print $item->sku;

This method is just a convenience method that forwards to the implementation in
the current storage object. See L<Handel::Storage/add_item> for more details.

=head2 can

=over

=item Arguments: $method

=back

Redirects C<can> requests to the internal storage result object, bypassing
AUTOLOAD and returns the code reference if a method is found.

=head2 count_items

Returns the number of items associated with the current result.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    $result->add_item({
        sku => 'ABC123'
    });
    
    print $result->count_items; # 1

This method is just a convenience method that forwards to the implementation in
the current storage object. See L<Handel::Storage/count_items> for more details.

=head2 create_instance

=over

=item Arguments: $result, $storage

=back

Creates a new instance of Handel::Storage::Result, storing the underlying result
for use by C<AUTOLOAD>.

    my $schema = $storage->schema_instance;
    my $row    = $schema->resultset($storage->schema_source)->create({
        col1 => 'foo',
        col2 => 'bar'
    });
    my $result = $storage->result_class->create_instance($dbresult, $storage);
    
    print $result->foo;

This method is used by the storage object to create storage results from
resultset results and assign the generic results with the given storage object.

=head2 delete

Deletes the current result and all of it's associated items from the current
storage.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    $result->add_item({
        sku => 'ABC123'
    });
    
    $result->delete;

B<This method must be implemented in custom subclasses.>

=head2 delete_items

=over

=item Arguments: \%filter

=back

Deletes items matching the filter from the current result.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    $result->add_item({
        sku => 'ABC123'
    });
    
    $result->delete_items({
        sku => 'ABC%'
    });

This method is just a convenience method that forwards to the implementation in
the current storage object. See L<Handel::Storage/delete_items> for more details.

=head2 discard_changes

Discards all changes made since the last successful update.

B<This method must be implemented in custom subclasses.>

=head2 has_column

=over

=item Arguments: $column

=back

Returns true if the column exists in the current result object.

=head2 items

Same as L</search_items>.

=head2 search_items

=over

=item Arguments: \%filter

=back

Returns items matching the filter associated with the current result.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->search({
        id => '11111111-1111-1111-1111-111111111111'
    });
    
    my $iterator = $result->search_items;

This method is just a convenience method that forwards to the implementation in
the current storage object. See L<Handel::Storage/search_items> for more details.

=head2 storage_result

Returns the original result created by the underlying storage mechanism. This
will be the DBIx::Class::Row result returned from the current schema.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    my @columns = $result->storage_result->columns;

It is probably unwise to use the storage result directly anywhere outside of the
current result object.

=head2 storage

Returns a reference to the storage object used to create the current storage
result.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    print $result->storage; # Handel::Storage::DBIC::Cart

=head2 txn_begin

Starts a transaction on the current storage object.

=head2 txn_commit

Commits the current transaction on the current storage object.

=head2 txn_rollback

Rolls back the current transaction on the current storage object.

=head2 update

=over

=item Arguments: \%data

=back

Updates the current result with the data specified.

    my $storage = Handel::Storage::DBIC::Cart->new;
    my $result = $storage->create({
        shopper => '11111111-1111-1111-1111-111111111111'
    });
    
    $result->update({
        name => 'My Cart'
    });

B<This method must be implemented in custom subclasses.>

=head1 SEE ALSO

L<Handel::Storage>

=head1 AUTHOR

    Christopher H. Laco
    CPAN ID: CLACO
    claco@chrislaco.com
    http://today.icantfocus.com/blog/
