Merge 'DBIx-Class-current' into 'trunk'
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema.pm
index 292aef8..0a39ee8 100644 (file)
@@ -55,11 +55,13 @@ particular which module inherits off which.
 
 =head1 METHODS
 
-=head2 register_class <moniker> <component_class>
+=head2 register_class
+
+=head3 Arguments: <moniker> <component_class>
 
 Registers a class which isa ResultSourceProxy; equivalent to calling
 
-  $schema->register_source($moniker, $class->result_source_instance);
+  $schema->register_source($moniker, $component_class->result_source_instance);
 
 =cut
 
@@ -68,7 +70,9 @@ sub register_class {
   $self->register_source($moniker => $to_register->result_source_instance);
 }
 
-=head2 register_source <moniker> <result source>
+=head2 register_source
+
+=head3 Arguments: <moniker> <result source>
 
 Registers the result source in the schema with the given moniker
 
@@ -143,7 +147,9 @@ sub resultset {
   return $self->source($moniker)->resultset;
 }
 
-=head2  load_classes [<classes>, (<class>, <class>), {<namespace> => [<classes>]}]
+=head2 load_classes
+
+=head3 Arguments: [<classes>, (<class>, <class>), {<namespace> => [<classes>]}]
 
 Uses L<Module::Find> to find all classes under the database class' namespace,
 or uses the classes you select.  Then it loads the component (using L<use>), 
@@ -203,7 +209,9 @@ sub load_classes {
   }
 }
 
-=head2 compose_connection <target> <@db_info>
+=head2 compose_connection
+
+=head3 Arguments: <target> <@db_info>
 
 This is the most important method in this class. it takes a target namespace,
 as well as dbh connection info, and creates a L<DBIx::Class::DB> class as
@@ -289,7 +297,9 @@ sub compose_namespace {
   return $schema;
 }
 
-=head2 setup_connection_class <$target> <@info>
+=head2 setup_connection_class
+
+=head3 Arguments: <$target> <@info>
 
 Sets up a database connection class to inject between the schema
 and the subclasses the schema creates.
@@ -303,7 +313,9 @@ sub setup_connection_class {
   $target->connection(@info);
 }
 
-=head2 connection(@args)
+=head2 connection
+
+=head3 Arguments: (@args)
 
 Instantiates a new Storage object of type storage_type and passes the
 arguments to $storage->connect_info. Sets the connection in-place on
@@ -325,7 +337,9 @@ sub connection {
   return $self;
 }
 
-=head2 connect(@info)
+=head2 connect
+
+=head3 Arguments: (@info)
 
 Conveneience method, equivalent to $schema->clone->connection(@info)
 
@@ -357,6 +371,98 @@ Rolls back the current transaction.
 
 sub txn_rollback { shift->storage->txn_rollback }
 
+=head2 txn_do
+
+=head3 Arguments: <coderef>, [@coderef_args]
+
+Executes <coderef> with (optional) arguments <@coderef_args> transactionally,
+returning its result (if any). If an exception is caught, a rollback is issued
+and the exception is rethrown. If the rollback fails, (i.e. throws an
+exception) an exception is thrown that includes a "Rollback failed" message.
+
+For example,
+
+  my $foo = $schema->resultset('foo')->find(1);
+
+  my $coderef = sub {
+    my ($foo, @bars) = @_;
+
+    # If any one of these fails, the entire transaction fails
+    $foo->create_related('bars', {
+      col => $_
+    }) foreach (@bars);
+
+    return $foo->bars;
+  };
+
+  my $rs;
+  eval {
+    $rs = $schema->txn_do($coderef, $foo, qw/foo bar baz/);
+  };
+
+  if ($@) {
+    my $error = $@;
+    if ($error =~ /Rollback failed/) {
+      die "something terrible has happened!";
+    } else {
+      deal_with_failed_transaction();
+      die $error;
+    }
+  }
+
+Nested transactions should work as expected (i.e. only the outermost
+transaction will issue a txn_commit on the Schema's storage)
+
+=cut
+
+sub txn_do {
+  my ($self, $coderef, @args) = @_;
+
+  ref $self or $self->throw_exception
+    ('Cannot execute txn_do as a class method');
+  ref $coderef eq 'CODE' or $self->throw_exception
+    ('$coderef must be a CODE reference');
+
+  my (@return_values, $return_value);
+
+  $self->txn_begin; # If this throws an exception, no rollback is needed
+
+  my $wantarray = wantarray; # Need to save this since it's reset in eval{}
+
+  eval {
+    # Need to differentiate between scalar/list context to allow for returning
+    # a list in scalar context to get the size of the list
+    if ($wantarray) {
+      @return_values = $coderef->(@args);
+    } else {
+      $return_value = $coderef->(@args);
+    }
+    $self->txn_commit;
+  };
+
+  if ($@) {
+    my $error = $@;
+
+    eval {
+      $self->txn_rollback;
+    };
+
+    if ($@) {
+      my $rollback_error = $@;
+      my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION";
+      $self->throw_exception($error)  # propagate nested rollback
+       if $rollback_error =~ /$exception_class/;
+
+      $self->throw_exception("Transaction aborted: $error. Rollback failed: ".
+                             $rollback_error);
+    } else {
+      $self->throw_exception($error); # txn failed but rollback succeeded
+    }
+  }
+
+  return $wantarray ? @return_values : $return_value;
+}
+
 =head2 clone
 
 Clones the schema and its associated result_source objects and returns the
@@ -375,18 +481,20 @@ sub clone {
   return $clone;
 }
 
-=head2 populate($moniker, \@data);
+=head2 populate
+
+=head3 Arguments: ($moniker, \@data);
 
 Populates the source registered with the given moniker with the supplied data.
 @data should be a list of listrefs, the first containing column names, the
 second matching values - i.e.
 
-$schema->populate('Foo', [
-  [ qw/foo_id foo_string/ ],
-  [ 1, 'One' ],
-  [ 2, 'Two' ],
-  ...
-]);
+  $schema->populate('Foo', [
+    [ qw/foo_id foo_string/ ],
+    [ 1, 'One' ],
+    [ 2, 'Two' ],
+    ...
+  ]);
 
 =cut
 
@@ -412,6 +520,18 @@ sub throw_exception {
   croak @_;
 }
 
+=head2 deploy
+
+Attempts to deploy the schema to the current storage
+
+=cut
+
+sub deploy {
+  my ($self) = shift;
+  $self->throw_exception("Can't deploy without storage") unless $self->storage;
+  $self->storage->deploy($self);
+}
+
 1;
 
 =head1 AUTHORS