From: Brandon L. Black Date: Wed, 23 Aug 2006 19:45:20 +0000 (+0000) Subject: Merge 'load_namespaces' into 'DBIx-Class-current' X-Git-Tag: v0.08010~43^2~22 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=382a78385abc2213ea09b3f6c6732694bf522e63;hp=a48e92d70ba95a7559fc2d17c953e2f84ee827ed;p=dbsrgits%2FDBIx-Class.git Merge 'load_namespaces' into 'DBIx-Class-current' 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 --- diff --git a/lib/DBIx/Class/Schema.pm b/lib/DBIx/Class/Schema.pm index 1945f65..ee4b936 100644 --- a/lib/DBIx/Class/Schema.pm +++ b/lib/DBIx/Class/Schema.pm @@ -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 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, any +corresponding ResultSet classes are underneath a sub-namespace of the schema +called C. + +Both of the sub-namespaces are configurable if you don't like the defaults, +via the options C and C. + +If (and only if) you specify the option C, any found +Result classes for which we do not find a corresponding +ResultSet class will have their C set to +C. + +C takes care of calling C 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 index 0000000..7911d8d --- /dev/null +++ b/t/39load_namespaces_1.t @@ -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 index 0000000..6daf05f --- /dev/null +++ b/t/39load_namespaces_2.t @@ -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 index 0000000..f48c838 --- /dev/null +++ b/t/39load_namespaces_3.t @@ -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 index 0000000..b674f30 --- /dev/null +++ b/t/39load_namespaces_4.t @@ -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 index 0000000..9a9aaf5 --- /dev/null +++ b/t/lib/DBICNSTest/OtherRslt/D.pm @@ -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 index 0000000..9786d5f --- /dev/null +++ b/t/lib/DBICNSTest/RSBase.pm @@ -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 index 0000000..4cb415f --- /dev/null +++ b/t/lib/DBICNSTest/RSet/A.pm @@ -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 index 0000000..c43a3fe --- /dev/null +++ b/t/lib/DBICNSTest/RSet/C.pm @@ -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 index 0000000..d2faecb --- /dev/null +++ b/t/lib/DBICNSTest/Result/A.pm @@ -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 index 0000000..e9cdc37 --- /dev/null +++ b/t/lib/DBICNSTest/Result/B.pm @@ -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 index 0000000..c7a86aa --- /dev/null +++ b/t/lib/DBICNSTest/ResultSet/A.pm @@ -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 index 0000000..55ecf1d --- /dev/null +++ b/t/lib/DBICNSTest/ResultSet/C.pm @@ -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 index 0000000..686e329 --- /dev/null +++ b/t/lib/DBICNSTest/Rslt/A.pm @@ -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 index 0000000..fb02f3f --- /dev/null +++ b/t/lib/DBICNSTest/Rslt/B.pm @@ -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;