Merge 'load_namespaces' into 'DBIx-Class-current'
Brandon L. Black [Wed, 23 Aug 2006 19:45:20 +0000 (19:45 +0000)]
r11157@evoc8 (orig r2602):  blblack | 2006-07-23 14:16:27 -0500
new feature branch load_namespaces
r11159@evoc8 (orig r2604):  blblack | 2006-07-23 14:47:36 -0500
docs + draft untested code for load_namespaces
r11161@evoc8 (orig r2606):  blblack | 2006-07-23 16:25:42 -0500
overridable namespaces and overridable base resultset class
r11255@evoc8 (orig r2630):  blblack | 2006-07-27 00:57:48 -0500
load_namespaces now does result_class and "+" syntax as well
r11256@evoc8 (orig r2631):  blblack | 2006-07-27 00:58:36 -0500
tests for load_namespaces
r11258@evoc8 (orig r2633):  blblack | 2006-07-27 01:08:10 -0500
proxy result_class, and fix load_namespace to use it correctly
r11265@evoc8 (orig r2640):  blblack | 2006-07-27 13:15:25 -0500
Module::Find is a real dep in Build.PL
r13039@evoc8 (orig r2647):  blblack | 2006-07-29 19:39:45 -0500
re-word options/code/pod to be less confusing wrt ResultSource vs source-definition class/file
r13040@evoc8 (orig r2648):  blblack | 2006-07-29 19:47:08 -0500
rename the load_namespaces tests
r13041@evoc8 (orig r2649):  blblack | 2006-07-31 01:22:26 -0500
load_namespaces arrayref support
r13121@evoc8 (orig r2656):  blblack | 2006-08-03 18:32:34 -0500
small improves to doc/code clarity
r13393@evoc8 (orig r2724):  blblack | 2006-08-23 13:59:32 -0500
remove result_class stuff from load_namespaces, better to not do it than to do it wrong
r13394@evoc8 (orig r2725):  blblack | 2006-08-23 14:39:31 -0500
rename Source to Result, because that is what it should be called

15 files changed:
lib/DBIx/Class/Schema.pm
t/39load_namespaces_1.t [new file with mode: 0644]
t/39load_namespaces_2.t [new file with mode: 0644]
t/39load_namespaces_3.t [new file with mode: 0644]
t/39load_namespaces_4.t [new file with mode: 0644]
t/lib/DBICNSTest/OtherRslt/D.pm [new file with mode: 0644]
t/lib/DBICNSTest/RSBase.pm [new file with mode: 0644]
t/lib/DBICNSTest/RSet/A.pm [new file with mode: 0644]
t/lib/DBICNSTest/RSet/C.pm [new file with mode: 0644]
t/lib/DBICNSTest/Result/A.pm [new file with mode: 0644]
t/lib/DBICNSTest/Result/B.pm [new file with mode: 0644]
t/lib/DBICNSTest/ResultSet/A.pm [new file with mode: 0644]
t/lib/DBICNSTest/ResultSet/C.pm [new file with mode: 0644]
t/lib/DBICNSTest/Rslt/A.pm [new file with mode: 0644]
t/lib/DBICNSTest/Rslt/B.pm [new file with mode: 0644]

index 1945f65..ee4b936 100644 (file)
@@ -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/;
 
@@ -248,10 +249,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;
@@ -279,6 +276,164 @@ sub load_classes {
   }
 }
 
+=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
diff --git a/t/39load_namespaces_1.t b/t/39load_namespaces_1.t
new file mode 100644 (file)
index 0000000..7911d8d
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+unshift(@INC, './t/lib');
+
+plan tests => 6;
+
+my $warnings;
+eval {
+    local $SIG{__WARN__} = sub { $warnings .= shift };
+    package DBICNSTest;
+    use base qw/DBIx::Class::Schema/;
+    __PACKAGE__->load_namespaces;
+};
+ok(!$@) or diag $@;
+like($warnings, qr/load_namespaces found ResultSet class C with no corresponding Result class/);
+
+my $source_a = DBICNSTest->source('A');
+isa_ok($source_a, 'DBIx::Class::ResultSource::Table');
+my $rset_a   = DBICNSTest->resultset('A');
+isa_ok($rset_a, 'DBICNSTest::ResultSet::A');
+
+my $source_b = DBICNSTest->source('B');
+isa_ok($source_b, 'DBIx::Class::ResultSource::Table');
+my $rset_b   = DBICNSTest->resultset('B');
+isa_ok($rset_b, 'DBIx::Class::ResultSet');
diff --git a/t/39load_namespaces_2.t b/t/39load_namespaces_2.t
new file mode 100644 (file)
index 0000000..6daf05f
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+unshift(@INC, './t/lib');
+
+plan tests => 6;
+
+my $warnings;
+eval {
+    local $SIG{__WARN__} = sub { $warnings .= shift };
+    package DBICNSTest;
+    use base qw/DBIx::Class::Schema/;
+    __PACKAGE__->load_namespaces(
+        result_namespace => 'Rslt',
+        resultset_namespace => 'RSet',
+    );
+};
+ok(!$@) or diag $@;
+like($warnings, qr/load_namespaces found ResultSet class C with no corresponding Result class/);
+
+my $source_a = DBICNSTest->source('A');
+isa_ok($source_a, 'DBIx::Class::ResultSource::Table');
+my $rset_a   = DBICNSTest->resultset('A');
+isa_ok($rset_a, 'DBICNSTest::RSet::A');
+
+my $source_b = DBICNSTest->source('B');
+isa_ok($source_b, 'DBIx::Class::ResultSource::Table');
+my $rset_b   = DBICNSTest->resultset('B');
+isa_ok($rset_b, 'DBIx::Class::ResultSet');
diff --git a/t/39load_namespaces_3.t b/t/39load_namespaces_3.t
new file mode 100644 (file)
index 0000000..f48c838
--- /dev/null
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+unshift(@INC, './t/lib');
+
+plan tests => 7;
+
+my $warnings;
+eval {
+    local $SIG{__WARN__} = sub { $warnings .= shift };
+    package DBICNSTestOther;
+    use base qw/DBIx::Class::Schema/;
+    __PACKAGE__->load_namespaces(
+        result_namespace => [ '+DBICNSTest::Rslt', '+DBICNSTest::OtherRslt' ],
+        resultset_namespace => '+DBICNSTest::RSet',
+    );
+};
+ok(!$@) or diag $@;
+like($warnings, qr/load_namespaces found ResultSet class C with no corresponding Result class/);
+
+my $source_a = DBICNSTestOther->source('A');
+isa_ok($source_a, 'DBIx::Class::ResultSource::Table');
+my $rset_a   = DBICNSTestOther->resultset('A');
+isa_ok($rset_a, 'DBICNSTest::RSet::A');
+
+my $source_b = DBICNSTestOther->source('B');
+isa_ok($source_b, 'DBIx::Class::ResultSource::Table');
+my $rset_b   = DBICNSTestOther->resultset('B');
+isa_ok($rset_b, 'DBIx::Class::ResultSet');
+
+my $source_d = DBICNSTestOther->source('D');
+isa_ok($source_d, 'DBIx::Class::ResultSource::Table');
diff --git a/t/39load_namespaces_4.t b/t/39load_namespaces_4.t
new file mode 100644 (file)
index 0000000..b674f30
--- /dev/null
@@ -0,0 +1,29 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use Test::More;
+
+unshift(@INC, './t/lib');
+
+plan tests => 6;
+
+my $warnings;
+eval {
+    local $SIG{__WARN__} = sub { $warnings .= shift };
+    package DBICNSTest;
+    use base qw/DBIx::Class::Schema/;
+    __PACKAGE__->load_namespaces( default_resultset_class => 'RSBase' );
+};
+ok(!$@) or diag $@;
+like($warnings, qr/load_namespaces found ResultSet class C with no corresponding Result class/);
+
+my $source_a = DBICNSTest->source('A');
+isa_ok($source_a, 'DBIx::Class::ResultSource::Table');
+my $rset_a   = DBICNSTest->resultset('A');
+isa_ok($rset_a, 'DBICNSTest::ResultSet::A');
+
+my $source_b = DBICNSTest->source('B');
+isa_ok($source_b, 'DBIx::Class::ResultSource::Table');
+my $rset_b   = DBICNSTest->resultset('B');
+isa_ok($rset_b, 'DBICNSTest::RSBase');
diff --git a/t/lib/DBICNSTest/OtherRslt/D.pm b/t/lib/DBICNSTest/OtherRslt/D.pm
new file mode 100644 (file)
index 0000000..9a9aaf5
--- /dev/null
@@ -0,0 +1,6 @@
+package DBICNSTest::OtherRslt::D;
+use base qw/DBIx::Class/;
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('d');
+__PACKAGE__->add_columns('d');
+1;
diff --git a/t/lib/DBICNSTest/RSBase.pm b/t/lib/DBICNSTest/RSBase.pm
new file mode 100644 (file)
index 0000000..9786d5f
--- /dev/null
@@ -0,0 +1,3 @@
+package DBICNSTest::RSBase;
+use base qw/DBIx::Class::ResultSet/;
+1;
diff --git a/t/lib/DBICNSTest/RSet/A.pm b/t/lib/DBICNSTest/RSet/A.pm
new file mode 100644 (file)
index 0000000..4cb415f
--- /dev/null
@@ -0,0 +1,3 @@
+package DBICNSTest::RSet::A;
+use base qw/DBIx::Class::ResultSet/;
+1;
diff --git a/t/lib/DBICNSTest/RSet/C.pm b/t/lib/DBICNSTest/RSet/C.pm
new file mode 100644 (file)
index 0000000..c43a3fe
--- /dev/null
@@ -0,0 +1,3 @@
+package DBICNSTest::RSet::C;
+use base qw/DBIx::Class::ResultSet/;
+1;
diff --git a/t/lib/DBICNSTest/Result/A.pm b/t/lib/DBICNSTest/Result/A.pm
new file mode 100644 (file)
index 0000000..d2faecb
--- /dev/null
@@ -0,0 +1,6 @@
+package DBICNSTest::Result::A;
+use base qw/DBIx::Class/;
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('a');
+__PACKAGE__->add_columns('a');
+1;
diff --git a/t/lib/DBICNSTest/Result/B.pm b/t/lib/DBICNSTest/Result/B.pm
new file mode 100644 (file)
index 0000000..e9cdc37
--- /dev/null
@@ -0,0 +1,6 @@
+package DBICNSTest::Result::B;
+use base qw/DBIx::Class/;
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('b');
+__PACKAGE__->add_columns('b');
+1;
diff --git a/t/lib/DBICNSTest/ResultSet/A.pm b/t/lib/DBICNSTest/ResultSet/A.pm
new file mode 100644 (file)
index 0000000..c7a86aa
--- /dev/null
@@ -0,0 +1,3 @@
+package DBICNSTest::ResultSet::A;
+use base qw/DBIx::Class::ResultSet/;
+1;
diff --git a/t/lib/DBICNSTest/ResultSet/C.pm b/t/lib/DBICNSTest/ResultSet/C.pm
new file mode 100644 (file)
index 0000000..55ecf1d
--- /dev/null
@@ -0,0 +1,3 @@
+package DBICNSTest::ResultSet::C;
+use base qw/DBIx::Class::ResultSet/;
+1;
diff --git a/t/lib/DBICNSTest/Rslt/A.pm b/t/lib/DBICNSTest/Rslt/A.pm
new file mode 100644 (file)
index 0000000..686e329
--- /dev/null
@@ -0,0 +1,6 @@
+package DBICNSTest::Rslt::A;
+use base qw/DBIx::Class/;
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('a');
+__PACKAGE__->add_columns('a');
+1;
diff --git a/t/lib/DBICNSTest/Rslt/B.pm b/t/lib/DBICNSTest/Rslt/B.pm
new file mode 100644 (file)
index 0000000..fb02f3f
--- /dev/null
@@ -0,0 +1,6 @@
+package DBICNSTest::Rslt::B;
+use base qw/DBIx::Class/;
+__PACKAGE__->load_components(qw/PK::Auto Core/);
+__PACKAGE__->table('b');
+__PACKAGE__->add_columns('b');
+1;