Merge 'trunk' into 'DBIx-Class-current'
Matt S Trout [Sat, 26 Aug 2006 11:41:20 +0000 (11:41 +0000)]
r26633@cain (orig r2733):  jguenther | 2006-08-24 17:58:38 +0000
fixed set_$rel (from many_to_many) to accept a listref
r26639@cain (orig r2739):  jguenther | 2006-08-24 21:56:31 +0000
documented storage accessor in Schema
r26640@cain (orig r2740):  blblack | 2006-08-25 20:49:52 +0000
bump C3 reqs again
r26641@cain (orig r2741):  matthewt | 2006-08-25 21:49:09 +0000
fixes to many-many

1  2 
lib/DBIx/Class/Schema.pm

diff --combined lib/DBIx/Class/Schema.pm
@@@ -5,7 -5,6 +5,7 @@@ use warnings
  
  use Carp::Clan qw/^DBIx::Class/;
  use Scalar::Util qw/weaken/;
 +require Module::Find;
  
  use base qw/DBIx::Class/;
  
@@@ -13,7 -12,6 +13,7 @@@ __PACKAGE__->mk_classdata('class_mappin
  __PACKAGE__->mk_classdata('source_registrations' => {});
  __PACKAGE__->mk_classdata('storage_type' => '::DBI');
  __PACKAGE__->mk_classdata('storage');
 +__PACKAGE__->mk_classdata('exception_action');
  
  =head1 NAME
  
@@@ -171,6 -169,12 +171,12 @@@ For example
  
  sub sources { return keys %{shift->source_registrations}; }
  
+ =head2 storage
+   my $storage = $schema->storage;
+ Returns the L<DBIx::Class::Storage> object for this Schema.
  =head2 resultset
  
  =over 4
@@@ -249,6 -253,10 +255,6 @@@ sub load_classes 
        }
      }
    } else {
 -    eval "require Module::Find;";
 -    $class->throw_exception(
 -      "No arguments to load_classes and couldn't load Module::Find ($@)"
 -    ) if $@;
      my @comp = map { substr $_, length "${class}::"  }
                   Module::Find::findallmod($class);
      $comps_for{$class} = \@comp;
    }
  }
  
 +=head2 load_namespaces
 +
 +=over 4
 +
 +=item Arguments: %options?
 +
 +=back
 +
 +This is an alternative to L</load_classes> above which assumes an alternative
 +layout for automatic class loading.  It assumes that all result
 +classes are underneath a sub-namespace of the schema called C<Result>, any
 +corresponding ResultSet classes are underneath a sub-namespace of the schema
 +called C<ResultSet>.
 +
 +Both of the sub-namespaces are configurable if you don't like the defaults,
 +via the options C<result_namespace> and C<resultset_namespace>.
 +
 +If (and only if) you specify the option C<default_resultset_class>, any found
 +Result classes for which we do not find a corresponding
 +ResultSet class will have their C<resultset_class> set to
 +C<default_resultset_class>.
 +
 +C<load_namespaces> takes care of calling C<resultset_class> for you where
 +neccessary if you didn't do it for yourself.
 +
 +All of the namespace and classname options to this method are relative to
 +the schema classname by default.  To specify a fully-qualified name, prefix
 +it with a literal C<+>.
 +
 +Examples:
 +
 +  # load My::Schema::Result::CD, My::Schema::Result::Artist,
 +  #    My::Schema::ResultSet::CD, etc...
 +  My::Schema->load_namespaces;
 +
 +  # Override everything to use ugly names.
 +  # In this example, if there is a My::Schema::Res::Foo, but no matching
 +  #   My::Schema::RSets::Foo, then Foo will have its
 +  #   resultset_class set to My::Schema::RSetBase
 +  My::Schema->load_namespaces(
 +    result_namespace => 'Res',
 +    resultset_namespace => 'RSets',
 +    default_resultset_class => 'RSetBase',
 +  );
 +
 +  # Put things in other namespaces
 +  My::Schema->load_namespaces(
 +    result_namespace => '+Some::Place::Results',
 +    resultset_namespace => '+Another::Place::RSets',
 +  );
 +
 +If you'd like to use multiple namespaces of each type, simply use an arrayref
 +of namespaces for that option.  In the case that the same result
 +(or resultset) class exists in multiple namespaces, the latter entries in
 +your list of namespaces will override earlier ones.
 +
 +  My::Schema->load_namespaces(
 +    # My::Schema::Results_C::Foo takes precedence over My::Schema::Results_B::Foo :
 +    result_namespace => [ 'Results_A', 'Results_B', 'Results_C' ],
 +    resultset_namespace => [ '+Some::Place::RSets', 'RSets' ],
 +  );
 +
 +=cut
 +
 +# Pre-pends our classname to the given relative classname or
 +#   class namespace, unless there is a '+' prefix, which will
 +#   be stripped.
 +sub _expand_relative_name {
 +  my ($class, $name) = @_;
 +  return if !$name;
 +  $name = $class . '::' . $name if ! ($name =~ s/^\+//);
 +  return $name;
 +}
 +
 +# returns a hash of $shortname => $fullname for every package
 +#  found in the given namespaces ($shortname is with the $fullname's
 +#  namespace stripped off)
 +sub _map_namespaces {
 +  my ($class, @namespaces) = @_;
 +
 +  my @results_hash;
 +  foreach my $namespace (@namespaces) {
 +    push(
 +      @results_hash,
 +      map { (substr($_, length "${namespace}::"), $_) }
 +      Module::Find::findallmod($namespace)
 +    );
 +  }
 +
 +  @results_hash;
 +}
 +
 +sub load_namespaces {
 +  my ($class, %args) = @_;
 +
 +  my $result_namespace = delete $args{result_namespace} || 'Result';
 +  my $resultset_namespace = delete $args{resultset_namespace} || 'ResultSet';
 +  my $default_resultset_class = delete $args{default_resultset_class};
 +
 +  $class->throw_exception('load_namespaces: unknown option(s): '
 +    . join(q{,}, map { qq{'$_'} } keys %args))
 +      if scalar keys %args;
 +
 +  $default_resultset_class
 +    = $class->_expand_relative_name($default_resultset_class);
 +
 +  for my $arg ($result_namespace, $resultset_namespace) {
 +    $arg = [ $arg ] if !ref($arg) && $arg;
 +
 +    $class->throw_exception('load_namespaces: namespace arguments must be '
 +      . 'a simple string or an arrayref')
 +        if ref($arg) ne 'ARRAY';
 +
 +    $_ = $class->_expand_relative_name($_) for (@$arg);
 +  }
 +
 +  my %results = $class->_map_namespaces(@$result_namespace);
 +  my %resultsets = $class->_map_namespaces(@$resultset_namespace);
 +
 +  my @to_register;
 +  {
 +    no warnings 'redefine';
 +    local *Class::C3::reinitialize = sub { };
 +    use warnings 'redefine';
 +
 +    foreach my $result (keys %results) {
 +      my $result_class = $results{$result};
 +      $class->ensure_class_loaded($result_class);
 +      $result_class->source_name($result) unless $result_class->source_name;
 +
 +      my $rs_class = delete $resultsets{$result};
 +      my $rs_set = $result_class->resultset_class;
 +      if($rs_set && $rs_set ne 'DBIx::Class::ResultSet') {
 +        if($rs_class && $rs_class ne $rs_set) {
 +          warn "We found ResultSet class '$rs_class' for '$result', but it seems "
 +             . "that you had already set '$result' to use '$rs_set' instead";
 +        }
 +      }
 +      elsif($rs_class ||= $default_resultset_class) {
 +        $class->ensure_class_loaded($rs_class);
 +        $result_class->resultset_class($rs_class);
 +      }
 +
 +      push(@to_register, [ $result_class->source_name, $result_class ]);
 +    }
 +  }
 +
 +  foreach (sort keys %resultsets) {
 +    warn "load_namespaces found ResultSet class $_ with no "
 +      . 'corresponding Result class';
 +  }
 +
 +  Class::C3->reinitialize;
 +  $class->register_class(@$_) for (@to_register);
 +
 +  return;
 +}
 +
  =head2 compose_connection
  
  =over 4
@@@ -593,10 -443,8 +599,10 @@@ sub setup_connection_class 
  
  Instantiates a new Storage object of type
  L<DBIx::Class::Schema/"storage_type"> and passes the arguments to
 -$storage->connect_info. Sets the connection in-place on the schema. See
 -L<DBIx::Class::Storage::DBI/"connect_info"> for more information.
 +$storage->connect_info. Sets the connection in-place on the schema.
 +
 +See L<DBIx::Class::Storage::DBI/"connect_info"> for DBI-specific syntax,
 +or L<DBIx::Class::Storage> in general.
  
  =cut
  
@@@ -610,7 -458,7 +616,7 @@@ sub connection 
    $self->throw_exception(
      "No arguments to load_classes and couldn't load ${storage_class} ($@)"
    ) if $@;
 -  my $storage = $storage_class->new;
 +  my $storage = $storage_class->new($self);
    $storage->connect_info(\@info);
    $self->storage($storage);
    return $self;
@@@ -634,83 -482,138 +640,83 @@@ information
  
  sub connect { shift->clone->connection(@_) }
  
 -=head2 txn_begin
 -
 -Begins a transaction (does nothing if AutoCommit is off). Equivalent to
 -calling $schema->storage->txn_begin. See
 -L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
 +=head2 txn_do
  
 -=cut
 +=over 4
  
 -sub txn_begin { shift->storage->txn_begin }
 +=item Arguments: C<$coderef>, @coderef_args?
  
 -=head2 txn_commit
 +=item Return Value: The return value of $coderef
  
 -Commits the current transaction. Equivalent to calling
 -$schema->storage->txn_commit. See L<DBIx::Class::Storage::DBI/"txn_commit">
 -for more information.
 +=back
  
 -=cut
 +Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
 +returning its result (if any). Equivalent to calling $schema->storage->txn_do.
 +See L<DBIx::Class::Storage/"txn_do"> for more information.
  
 -sub txn_commit { shift->storage->txn_commit }
 +This interface is preferred over using the individual methods L</txn_begin>,
 +L</txn_commit>, and L</txn_rollback> below.
  
 -=head2 txn_rollback
 +=cut
  
 -Rolls back the current transaction. Equivalent to calling
 -$schema->storage->txn_rollback. See
 -L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
 +sub txn_do {
 +  my $self = shift;
  
 -=cut
 +  $self->storage or $self->throw_exception
 +    ('txn_do called on $schema without storage');
  
 -sub txn_rollback { shift->storage->txn_rollback }
 +  $self->storage->txn_do(@_);
 +}
  
 -=head2 txn_do
 +=head2 txn_begin
  
 -=over 4
 +Begins a transaction (does nothing if AutoCommit is off). Equivalent to
 +calling $schema->storage->txn_begin. See
 +L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
  
 -=item Arguments: C<$coderef>, @coderef_args?
 +=cut
  
 -=item Return Value: The return value of $coderef
 +sub txn_begin {
 +  my $self = shift;
  
 -=back
 +  $self->storage or $self->throw_exception
 +    ('txn_begin called on $schema without storage');
  
 -Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
 -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.
 +  $self->storage->txn_begin;
 +}
  
 -For example,
 +=head2 txn_commit
  
 -  my $author_rs = $schema->resultset('Author')->find(1);
 -  my @titles = qw/Night Day It/;
 +Commits the current transaction. Equivalent to calling
 +$schema->storage->txn_commit. See L<DBIx::Class::Storage::DBI/"txn_commit">
 +for more information.
  
 -  my $coderef = sub {
 -    # If any one of these fails, the entire transaction fails
 -    $author_rs->create_related('books', {
 -      title => $_
 -    }) foreach (@titles);
 +=cut
  
 -    return $author->books;
 -  };
 +sub txn_commit {
 +  my $self = shift;
  
 -  my $rs;
 -  eval {
 -    $rs = $schema->txn_do($coderef);
 -  };
 +  $self->storage or $self->throw_exception
 +    ('txn_commit called on $schema without storage');
  
 -  if ($@) {                                  # Transaction failed
 -    die "something terrible has happened!"   #
 -      if ($@ =~ /Rollback failed/);          # Rollback failed
 +  $self->storage->txn_commit;
 +}
  
 -    deal_with_failed_transaction();
 -  }
 +=head2 txn_rollback
  
 -In a nested transaction (calling txn_do() from within a txn_do() coderef) only
 -the outermost transaction will issue a L<DBIx::Class::Schema/"txn_commit"> on
 -the Schema's storage, and txn_do() can be called in void, scalar and list
 -context and it will behave as expected.
 +Rolls back the current transaction. Equivalent to calling
 +$schema->storage->txn_rollback. See
 +L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
  
  =cut
  
 -sub txn_do {
 -  my ($self, $coderef, @args) = @_;
 +sub txn_rollback {
 +  my $self = shift;
  
    $self->storage or $self->throw_exception
 -    ('txn_do called on $schema without storage');
 -  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 the context
 -                             # inside the eval{} block is independent
 -                             # of the context that called txn_do()
 -  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) {
 -      # list context
 -      @return_values = $coderef->(@args);
 -    } elsif (defined $wantarray) {
 -      # scalar context
 -      $return_value = $coderef->(@args);
 -    } else {
 -      # void context
 -      $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
 -    }
 -  }
 +    ('txn_rollback called on $schema without storage');
  
 -  return $wantarray ? @return_values : $return_value;
 +  $self->storage->txn_rollback;
  }
  
  =head2 clone
@@@ -736,7 -639,6 +742,7 @@@ sub clone 
      my $new = $source->new($source);
      $clone->register_source($moniker => $new);
    }
 +  $clone->storage->set_schema($clone) if $clone->storage;
    return $clone;
  }
  
@@@ -776,38 -678,6 +782,38 @@@ sub populate 
    return @created;
  }
  
 +=head2 exception_action
 +
 +=over 4
 +
 +=item Arguments: $code_reference
 +
 +=back
 +
 +If C<exception_action> is set for this class/object, L</throw_exception>
 +will prefer to call this code reference with the exception as an argument,
 +rather than its normal <croak> action.
 +
 +Your subroutine should probably just wrap the error in the exception
 +object/class of your choosing and rethrow.  If, against all sage advice,
 +you'd like your C<exception_action> to suppress a particular exception
 +completely, simply have it return true.
 +
 +Example:
 +
 +   package My::Schema;
 +   use base qw/DBIx::Class::Schema/;
 +   use My::ExceptionClass;
 +   __PACKAGE__->exception_action(sub { My::ExceptionClass->throw(@_) });
 +   __PACKAGE__->load_classes;
 +
 +   # or:
 +   my $schema_obj = My::Schema->connect( .... );
 +   $schema_obj->exception_action(sub { My::ExceptionClass->throw(@_) });
 +
 +   # suppress all exceptions, like a moron:
 +   $schema_obj->exception_action(sub { 1 });
 +
  =head2 throw_exception
  
  =over 4
  =back
  
  Throws an exception. Defaults to using L<Carp::Clan> to report errors from
 -user's perspective.
 +user's perspective.  See L</exception_action> for details on overriding
 +this method's behavior.
  
  =cut
  
  sub throw_exception {
 -  my ($self) = shift;
 -  croak @_;
 +  my $self = shift;
 +  croak @_ if !$self->exception_action || !$self->exception_action->(@_);
  }
  
  =head2 deploy (EXPERIMENTAL)