cb9edce91324ed8552a1331a8293bd400485e213
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / lib / DBIx / Class / ResultSource / MultipleTableInheritance.pm
1 package DBIx::Class::ResultSource::MultipleTableInheritance;
2
3 use strict;
4 use warnings;
5 use parent qw(DBIx::Class::ResultSource::View);
6 use Method::Signatures::Simple;
7 use Carp::Clan qw/^DBIx::Class/;
8 use aliased 'DBIx::Class::ResultSource::Table';
9 use namespace::autoclean;
10
11 # how this works:
12 #
13 # On construction, we hook $self->result_class->result_source_instance
14 # if present to get the superclass' source object
15
16 # When attached to a schema, we need to add sources to that schema with
17 # appropriate relationships for the foreign keys so the concrete tables
18 # get generated
19 #
20 # We also generate our own view definition using this class' concrete table
21 # and the view for the superclass, and stored procedures for the insert,
22 # update and delete operations on this view.
23 #
24 # deploying the postgres rules through SQLT may be a pain though.
25
26 __PACKAGE__->mk_group_accessors(simple => qw(parent_source));
27
28 method new ($class: @args) {
29   my $new = $class->next::method(@args);
30   my $rc = $new->result_class;
31   if (my $meth = $rc->can('result_source_instance')) {
32     $new->parent_source($rc->$meth);
33   }
34   if ($new->schema) {
35     $new->_attach_additional_sources;
36   }
37   return $new;
38 }
39
40 method _attach_additional_sources () {
41   my $raw_name = $self->_raw_source_name;
42   my $schema = $self->schema;
43
44   # if the raw source is already present we can assume we're done
45   return if grep { $_ eq $raw_name } $schema->sources;
46   # our parent should've been registered already actually due to DBIC
47   # attaching subclass sources later in load_namespaces
48   my $parent;
49   if ($self->parent_source) {
50       my $parent_name = $self->parent_source->name;
51     ($parent) = 
52       grep { $_->name eq $parent_name }
53         map $schema->source($_), $schema->sources;
54     confess "Couldn't find attached source for parent $parent_name - did you use load_classes? This module is only compatible with load_namespaces"
55       unless $parent;
56   }
57   my $table = Table->new({ name => '_'.$self->name });
58   $table->add_columns(
59     map { ($_ => { %{$self->column_info($_)} }) }
60       grep { $self->column_info($_)->{originally_defined_in} eq $self->name }
61         $self->columns
62   );
63   # we don't need to add the PK cols explicitly if we're the root table
64   # since they already got added above
65   if ($parent) {
66     my %join;
67     foreach my $pri ($self->primary_columns) {
68       my %info = %{$self->column_info($pri)};
69       delete @info{qw(is_auto_increment sequence auto_nextval)};
70       $self->add_column($pri => \%info);
71       $join{"foreign.${pri}"} = "self.${pri}";
72     }
73     $self->add_relationship('parent', $parent->source_name, \%join);
74   }
75   $table->set_primary_key($self->primary_columns);
76   $schema->register_source($raw_name => $table);
77 }
78
79 method set_primary_key (@args) {
80   if ($self->parent_source) {
81     confess "Can't set primary key on a subclass";
82   }
83   return $self->next::method(@args);
84 }
85
86 method _raw_source_name () {
87   my $base = $self->source_name;
88   confess "Can't generate raw source name when we don't have a source_name"
89     unless $base;
90   return 'Raw::'.$base;
91 }
92
93 method add_columns (@args) {
94   my $ret = $self->next::method(@args);
95   $_->{originally_defined_in} ||= $self->name for values %{$self->_columns};
96   return $ret;
97 }
98
99 1;