X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSource%2FMultipleTableInheritance.pm;h=8f5fe21f1e7d0e6f41a235e6c0e37f715d831949;hb=487f448916043c532f406e0b5a7fd8846b154d43;hp=a25090e4f447fb5baf08bb3c3bbdc42a7f688802;hpb=70d56286cc79272c74c9ee70a5253d40fa954bb1;p=dbsrgits%2FDBIx-Class-ResultSource-MultipleTableInheritance.git diff --git a/lib/DBIx/Class/ResultSource/MultipleTableInheritance.pm b/lib/DBIx/Class/ResultSource/MultipleTableInheritance.pm index a25090e..8f5fe21 100644 --- a/lib/DBIx/Class/ResultSource/MultipleTableInheritance.pm +++ b/lib/DBIx/Class/ResultSource/MultipleTableInheritance.pm @@ -1,4 +1,13 @@ -use MooseX::Declare; +package DBIx::Class::ResultSource::MultipleTableInheritance; + +use strict; +use warnings; +use parent qw(DBIx::Class::ResultSource::View); +use Method::Signatures::Simple; +use Carp::Clan qw/^DBIx::Class/; +use aliased 'DBIx::Class::ResultSource::Table'; +use aliased 'DBIx::Class::ResultClass::HashRefInflator'; +use namespace::autoclean; # how this works: # @@ -15,11 +24,162 @@ use MooseX::Declare; # # deploying the postgres rules through SQLT may be a pain though. -class DBIx::Class::ResultSource::MultipleTableInheritance - extends DBIx::Class::ResultSource::View { +__PACKAGE__->mk_group_accessors(simple => qw(parent_source)); + +method new ($class: @args) { + my $new = $class->next::method(@args); + my $rc = $new->result_class; + if (my $meth = $rc->can('result_source_instance')) { + my $source = $rc->$meth; + if ($source->result_class ne $new->result_class + && $new->result_class->isa($source->result_class)) { + $new->parent_source($source); + } + } + return $new; +} + +method schema (@args) { + my $ret = $self->next::method(@args); + if (@args) { + $self->_attach_additional_sources; + } + return $ret; +} + +method _attach_additional_sources () { + my $raw_name = $self->raw_source_name; + my $schema = $self->schema; + + # if the raw source is already present we can assume we're done + return if grep { $_ eq $raw_name } $schema->sources; + + # our parent should've been registered already actually due to DBIC + # attaching subclass sources later in load_namespaces + + my $parent; + if ($self->parent_source) { + my $parent_name = $self->parent_source->name; + ($parent) = + grep { $_->name eq $parent_name } + map $schema->source($_), $schema->sources; + confess "Couldn't find attached source for parent $parent_name - did you use load_classes? This module is only compatible with load_namespaces" + unless $parent; + } + + # create the raw table source + + my $table = Table->new({ name => $self->raw_table_name }); + + # we don't need to add the PK cols explicitly if we're the root table + # since they'll get added below + + if ($parent) { + my %join; + foreach my $pri ($self->primary_columns) { + my %info = %{$self->column_info($pri)}; + delete @info{qw(is_auto_increment sequence auto_nextval)}; + $table->add_column($pri => \%info); + $join{"foreign.${pri}"} = "self.${pri}"; + } + # have to use source name lookups rather than result class here + # because we don't actually have a result class on the raw sources + $table->add_relationship('parent', $parent->raw_source_name, \%join); + } + + # add every column that's actually a concrete part of us + + $table->add_columns( + map { ($_ => { %{$self->column_info($_)} }) } + grep { $self->column_info($_)->{originally_defined_in} eq $self->name } + $self->columns + ); + $table->set_primary_key($self->primary_columns); + $schema->register_source($raw_name => $table); +} + +method set_primary_key (@args) { + if ($self->parent_source) { + confess "Can't set primary key on a subclass"; + } + return $self->next::method(@args); +} + +method raw_source_name () { + my $base = $self->source_name; + confess "Can't generate raw source name when we don't have a source_name" + unless $base; + return 'Raw::'.$base; +} + +method raw_table_name () { + return '_'.$self->name; +} + +method add_columns (@args) { + my $ret = $self->next::method(@args); + $_->{originally_defined_in} ||= $self->name for values %{$self->_columns}; + return $ret; +} + +BEGIN { + + # helper routines, constructed as anon subs so autoclean nukes them + + use signatures; + + *argify = sub (@names) { + map '_'.$_, @names; + }; + + *qualify_with = sub ($source, @names) { + map join('.', $source->name, $_), @names; + }; + + *body_cols = sub ($source) { + my %pk; @pk{$source->primary_columns} = (); + map +{ %{$source->column_info($_)}, name => $_ }, + grep !exists $pk{$_}, $source->columns; + }; + + *pk_cols = sub ($source) { + map +{ %{$source->column_info($_)}, name => $_ }, + $source->primary_columns; + }; + + *names_of = sub (@cols) { map $_->{name}, @cols }; + + *arglist = sub (@cols) { + map join(' ', @{$_}{qw(name data_type)}), @cols; + }; + +} - - +method view_definition () { + my $schema = $self->schema; + confess "Can't generate view without connected schema, sorry" + unless $schema && $schema->storage; + my $sqla = $schema->storage->sql_maker; + my @sources = my $table = $self->schema->source($self->raw_source_name); + my $super_view = $self->parent_source; + push(@sources, $super_view) if defined($super_view); + my @body_cols = map body_cols($_), @sources; + my @pk_cols = pk_cols $self; + my $select = $sqla->select( + ($super_view + ? ([ # FROM _tbl _tbl + { $table->name => $table->name }, + [ # JOIN view view + { $super_view->name => $super_view->name }, + # ON _tbl.id = view.id + { map +(qualify_with($super_view, $_), qualify_with($table, $_)), + names_of @pk_cols } + ] + ]) + : ($table->name)), + [ (qualify_with $table, names_of @pk_cols), names_of @body_cols ], + ); + return $select; } 1;