use strict;
use warnings;
-use DBIx::Class::DB;
+
+use Carp::Clan qw/^DBIx::Class/;
use base qw/DBIx::Class/;
-__PACKAGE__->load_components(qw/Exception/);
__PACKAGE__->mk_classdata('class_mappings' => {});
__PACKAGE__->mk_classdata('source_registrations' => {});
-__PACKAGE__->mk_classdata('storage_type' => 'DBI');
+__PACKAGE__->mk_classdata('storage_type' => '::DBI');
__PACKAGE__->mk_classdata('storage');
=head1 NAME
=head1 SYNOPSIS
-in My/Schema.pm
-
package My::Schema;
-
use base qw/DBIx::Class::Schema/;
-
+
+ # load My::Schema::Foo, My::Schema::Bar, My::Schema::Baz
__PACKAGE__->load_classes(qw/Foo Bar Baz/);
-in My/Schema/Foo.pm
-
package My::Schema::Foo;
-
use base qw/DBIx::Class/;
-
__PACKAGE__->load_components(qw/PK::Auto::Pg Core/); # for example
__PACKAGE__->table('foo');
- ...
-in My/DB.pm
+ my $schema1 = My::Schema->connect(
+ $dsn,
+ $user,
+ $password,
+ $attrs
+ );
- use My::Schema;
+ my $schema2 = My::Schema->connect( ... );
- My::Schema->compose_connection('My::DB', $dsn, $user, $pass, $attrs);
-
-then in app code
-
- my @obj = My::DB::Foo->search({}); # My::DB::Foo isa My::Schema::Foo My::DB
+ # fetch objects using My::Schema::Foo
+ my $resultset = $schema1->resultset('Foo')->search( ... );
+ my @objects = $schema2->resultset('Foo')->search( ... );
=head1 DESCRIPTION
-Creates database classes based on a schema. This allows you to have more than
-one concurrent connection using the same database classes, by making
-subclasses under a new namespace for each connection. If you only need one
-class, you should probably use L<DBIx::Class::DB> directly instead.
+Creates database classes based on a schema. This is the recommended way to
+use L<DBIx::Class> and allows you to use more than one concurrent connection
+with your classes.
NB: If you're used to L<Class::DBI> it's worth reading the L</SYNOPSIS>
carefully as DBIx::Class does things a little differently. Note in
=head2 register_class <moniker> <component_class>
-Registers the class in the schema's class_registrations. This is a hash
-containing database classes, keyed by their monikers. It's used by
-compose_connection to create/modify all the existing database classes.
+Registers a class which isa ResultSourceProxy; equivalent to calling
+
+ $schema->register_source($moniker, $class->result_source_instance);
=cut
# if we got here, they probably passed a full class name
my $mapped = $self->class_mappings->{$moniker};
- die "Can't find source for ${moniker}"
+ $self->throw_exception("Can't find source for ${moniker}")
unless $mapped && exists $sreg->{$mapped};
return $sreg->{$mapped};
}
}
} else {
eval "require Module::Find;";
- $class->throw("No arguments to load_classes and couldn't load".
+ $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;
die $@ unless $@ =~ /Can't locate/;
}
$class->register_class($comp => $comp_class);
+ # if $class->can('result_source_instance');
}
}
}
sub compose_connection {
my ($self, $target, @info) = @_;
- my $conn_class = "${target}::_db";
- $self->setup_connection_class($conn_class, @info);
- my $schema = $self->compose_namespace($target, $conn_class);
- $schema->storage($conn_class->storage);
+ my $base = 'DBIx::Class::ResultSetProxy';
+ eval "require ${base};";
+ $self->throw_exception("No arguments to load_classes and couldn't load".
+ " ${base} ($@)") if $@;
+
+ if ($self eq $target) {
+ # Pathological case, largely caused by the docs on early C::M::DBIC::Plain
+ foreach my $moniker ($self->sources) {
+ my $source = $self->source($moniker);
+ my $class = $source->result_class;
+ $self->inject_base($class, $base);
+ $class->mk_classdata(resultset_instance => $source->resultset);
+ $class->mk_classdata(class_resolver => $self);
+ }
+ $self->connection(@info);
+ return $self;
+ }
+
+ my $schema = $self->compose_namespace($target, $base);
+ {
+ no strict 'refs';
+ *{"${target}::schema"} = sub { $schema };
+ }
+
+ $schema->connection(@info);
foreach my $moniker ($schema->sources) {
my $source = $schema->source($moniker);
my $class = $source->result_class;
#warn "$moniker $class $source ".$source->storage;
$class->mk_classdata(result_source_instance => $source);
$class->mk_classdata(resultset_instance => $source->resultset);
+ $class->mk_classdata(class_resolver => $schema);
}
return $schema;
}
sub compose_namespace {
- my ($class, $target, $base) = @_;
- my %reg = %{ $class->source_registrations };
+ my ($self, $target, $base) = @_;
+ my %reg = %{ $self->source_registrations };
my %target;
my %map;
- my $schema = bless({ }, $class);
- while (my ($moniker, $source) = each %reg) {
+ my $schema = $self->clone;
+ foreach my $moniker ($schema->sources) {
+ my $source = $schema->source($moniker);
my $target_class = "${target}::${moniker}";
- $class->inject_base(
+ $self->inject_base(
$target_class => $source->result_class, ($base ? $base : ())
);
- my $new_source = $source->new($source);
- $new_source->result_class($target_class);
- $new_source->schema($schema);
- $map{$moniker} = $new_source;
+ $source->result_class($target_class);
}
- $schema->source_registrations(\%map);
{
no strict 'refs';
- *{"${target}::schema"} =
- sub { $schema };
foreach my $meth (qw/class source resultset/) {
*{"${target}::${meth}"} =
sub { shift->schema->$meth(@_) };
}
}
- $base->class_resolver($target);
return $schema;
}
$target->connection(@info);
}
+=head2 connection(@args)
+
+Instantiates a new Storage object of type storage_type and passes the
+arguments to $storage->connect_info. Sets the connection in-place on
+the schema.
+
+=cut
+
+sub connection {
+ my ($self, @info) = @_;
+ my $storage_class = $self->storage_type;
+ $storage_class = 'DBIx::Class::Storage'.$storage_class
+ if $storage_class =~ m/^::/;
+ eval "require ${storage_class};";
+ $self->throw_exception("No arguments to load_classes and couldn't load".
+ " ${storage_class} ($@)") if $@;
+ my $storage = $storage_class->new;
+ $storage->connect_info(\@info);
+ $self->storage($storage);
+ return $self;
+}
+
+=head2 connect(@info)
+
+Conveneience method, equivalent to $schema->clone->connection(@info)
+
+=cut
+
+sub connect { shift->clone->connection(@_) }
+
+=head2 txn_begin
+
+Begins a transaction (does nothing if AutoCommit is off).
+
+=cut
+
+sub txn_begin { shift->storage->txn_begin }
+
+=head2 txn_commit
+
+Commits the current transaction.
+
+=cut
+
+sub txn_commit { shift->storage->txn_commit }
+
+=head2 txn_rollback
+
+Rolls back the current transaction.
+
+=cut
+
+sub txn_rollback { shift->storage->txn_rollback }
+
+=head2 clone
+
+Clones the schema and its associated result_source objects and returns the
+copy.
+
+=cut
+
+sub clone {
+ my ($self) = @_;
+ my $clone = bless({ (ref $self ? %$self : ()) }, ref $self || $self);
+ foreach my $moniker ($self->sources) {
+ my $source = $self->source($moniker);
+ my $new = $source->new($source);
+ $clone->register_source($moniker => $new);
+ }
+ return $clone;
+}
+
+=head2 populate($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' ],
+ ...
+]);
+
+=cut
+
+sub populate {
+ my ($self, $name, $data) = @_;
+ my $rs = $self->resultset($name);
+ my @names = @{shift(@$data)};
+ foreach my $item (@$data) {
+ my %create;
+ @create{@names} = @$item;
+ $rs->create(\%create);
+ }
+}
+
+=head2 throw_exception
+
+Defaults to using Carp::Clan to report errors from user perspective.
+
+=cut
+
+sub throw_exception {
+ my ($self) = shift;
+ croak @_;
+}
+
1;
=head1 AUTHORS