EntityModel

 view release on metacpan or  search on metacpan

lib/EntityModel/Storage.pm  view on Meta::CPAN

package EntityModel::Storage;
{
  $EntityModel::Storage::VERSION = '0.102';
}
use EntityModel::Class {
	_isa		=> [qw(Mixin::Event::Dispatch)],
	entity		=> { type => 'array', subclass => 'EntityModel::Entity' },
	transaction	=> { type => 'array', subclass => 'EntityModel::Transaction' },
};
no if $] >= 5.017011, warnings => "experimental::smartmatch";

=head1 NAME

EntityModel::Storage - backend storage interface for L<EntityModel>

=head1 VERSION

version 0.102

=head1 SYNOPSIS

See L<EntityModel>.

=head1 DESCRIPTION

See L<EntityModel> for more details.

=head1 METHODS

=cut

=head2 register

Register with L<EntityModel> so that callbacks trigger when further definitions are loaded/processed.

The base storage engine doesn't provide any callbacks - but we define the method anyway so that we don't
need to check for ->can.

=cut

sub register {
	my $class = shift;
}

=head2 apply_model

Apply the given model.

=cut

sub apply_model {
	my $self = shift;
	my $model = shift;
	$self->apply_model_and_schema($model);
}

=head2 apply_model_and_schema

Apply the given model to the storage layer.

This delegates most of the work to L</apply_entity>.

=cut

sub apply_model_and_schema {
	my $self = shift;
	my $model = shift;
	my %args = @_;

	# Start off assuming that we need all the listed entities
	my @pending = $model->entity->list;
	# Nothing applied yet (should be $self->entity->list)
	my %existing;
	my @pendingNames = map { $_->name } @pending;

	my $code;
	# Called when everything's been applied successfully
	my $done; $done = sub {
#		$done = sub { die "Tried to hit the same completion callback twice\n" };
		$args{on_complete}->() if exists $args{on_complete};
		0
	};
	my %incomplete;
	# Process a single entity
	$code = sub {
		# We may be a leftover event, bail out if there's nothing to do
		unless(@pending) {
			return 1 if keys %incomplete; # try us again later
			$done->();
			return 0; # all complete
		}

		# Next item in queue, no idea what state it's in yet
		my $entity = shift(@pending);

		# Also remove current entry so we don't match ourselves when checking deps

lib/EntityModel/Storage.pm  view on Meta::CPAN

	return $self;
}

sub backend_ready { shift->{backend_ready} }

sub wait_for_backend {
	my $self = shift;
	my $code = shift;
	return $code->($self) if $self->backend_ready;
	$self->add_handler_for_event( backend_ready => sub { $code->(@_); 0 });
	return $self;
}

sub DESTROY {
	my $self = shift;
	die "Active transactions" if $self->transaction->count;
}

1;

__END__

=head1 SUBCLASSING

This module provides the abstract base class for all storage modules. Here's how to build
your own.

=head2 INITIAL SETUP

L</setup> will be called when this storage class is attached to the model via
L<EntityModel/add_storage>, and this will receive the $model as the first parameter along
with any additional options. Typically this will include storage-specific connection
information.

Each entity added to the model will be applied to the storage engine through L</apply_entity>.
It is the responsibility of the storage engine to verify that it is able to handle the given
entities and fields, either creating the underlying storage structure (database tables, etc.)
or raising an error if this isn't appropriate.

=head2 USAGE

Most of the work is handled by the following methods:

=over 4

=item * L</read> - retrieves data from the backend storage engine for the given entity and ID

=item * L</create> - writes new data to storage for given entity, data and optional ID

=item * L</store> - updates an existing entry in storage for the given entity, data and ID

=item * L</remove> - deletes an existing entry from storage, takes entity and ID

=back

Each of these applies to a single entity instance only. Since they operate on a callback
basis, multiple operations can be aggregated if desired:

 select * from storage where id in (x,y,z)

Two callbacks are required for each of the above operations:

=over 4

=item * on_complete - the operation completed successfully and the data is guaranteed to
have been written to storage. The strength of this guarantee depends on the storage engine
but it should be safe for calling code to assume that any further operations will not result
in losing the data - for example, a database engine would commit the data before sending this
event.

=item * on_fail - the operation was not successful and storage has been rolled back to
the previous state. This could be the case when trying to create an item with a pre-existing
ID or possibly transaction deadlock, although in the latter case it would be preferable to
attempt retry some reasonable number of times before signalling a failure.

=back

Neither callback is mandatory - default behaviour if there is no C<on_fail> is to die() on failure,
and no-op if C<on_complete> is not specified.

=head1 AUTHOR

Tom Molesworth <cpan@entitymodel.com>

=head1 LICENSE

Copyright Tom Molesworth 2008-2011. Licensed under the same terms as Perl itself.



( run in 0.719 second using v1.01-cache-2.11-cpan-524268b4103 )