use Scalar::Util qw(blessed);
use namespace::autoclean;
-# how this works:
-#
-# On construction, we hook $self->result_class->result_source_instance
-# if present to get the superclass' source object
-#
-# When attached to a schema, we need to add sources to that schema with
-# appropriate relationships for the foreign keys so the concrete tables
-# get generated
-#
-# We also generate our own view definition using this class' concrete table
-# and the view for the superclass, and stored procedures for the insert,
-# update and delete operations on this view.
-#
-# deploying the postgres rules through SQLT may be a pain though.
+our $VERSION = 0.01;
__PACKAGE__->mk_group_accessors(simple => qw(parent_source additional_parents));
}
1;
+
+__END__
+
+# how this works:
+#
+# On construction, we hook $self->result_class->result_source_instance
+# if present to get the superclass' source object
+#
+# When attached to a schema, we need to add sources to that schema with
+# appropriate relationships for the foreign keys so the concrete tables
+# get generated
+#
+# We also generate our own view definition using this class' concrete table
+# and the view for the superclass, and stored procedures for the insert,
+# update and delete operations on this view.
+#
+# deploying the postgres rules through SQLT may be a pain though.
+
+=encoding utf-8
+
+=head1 NAME
+
+DBIx::Class::ResultSource::MultipleTableInheritance -- Use multiple tables to define your classes
+
+=head1 SYNOPSIS
+
+
+ {
+ package MyApp::Schema::Result::Coffee;
+
+ __PACKAGE__->table_class('DBIx::Class::ResultSource::MultipleTableInheritance');
+ __PACKAGE__->table('coffee');
+ __PACKAGE__->add_columns(
+ "id",
+ {
+ data_type => "integer",
+ default_value => "nextval('coffee_seq'::regclass)",
+ is_auto_increment => 1,
+ is_foreign_key => 1,
+ is_nullable => 0,
+ size => 4,
+ },
+ "flavor",
+ {
+ data_type => "text",
+ default_value => "good",
+ },
+ );
+
+ __PACKAGE__->set_primary_key("id");
+
+ 1;
+ }
+
+ {
+ package MyApp::Schema::Result::Sumatra;
+
+ use parent 'Coffee';
+
+ __PACKAGE__->table('sumatra');
+
+ __PACKAGE__->add_columns(
+ "aroma",
+ {
+ data_type => "text",
+ default_value => undef,
+ is_nullable => 0,
+ },
+ );
+
+ 1;
+ }
+
+ ...
+
+ my $schema = MyApp::Schema->connect($dsn);
+
+ my $cup = $schema->resultset('Sumatra')->new;
+
+ print STDERR Dumper $cup->columns;
+
+ $VAR1 = 'id';
+ $VAR2 = 'flavor';
+ $VAR3 = 'aroma';
+
+
+
+Inherit from this package and you can make a resultset class from a view, but that's more than a little bit misleading: the result is B<transparentlt writable>.
+
+This is accomplished through the use of stored functions that map changes written to the view to changes to the underlying concrete tables.
+
+=head1 WHY?
+
+In many applications, many classes are subclasses of others. Let's say you have this schema:
+
+ # Conceptual domain model
+
+ class User {
+ has id,
+ has name,
+ has password
+ }
+
+ class Investor {
+ has id,
+ has name,
+ has password,
+ has dollars
+ }
+
+That's redundant. Hold on a sec...
+
+ class User {
+ has id,
+ has name,
+ has password
+ }
+
+ class Investor isa User {
+ has dollars
+ }
+
+Good idea, but how to put this into code?
+
+One far-too common and absolutely horrendous solution is to have a "checkbox" in your database: a nullable "investor" column, which entails a nullable "dollars" column, in the user table.
+
+ create table "user" (
+ "id" integer not null primary key autoincrement,
+ "name" text not null,
+ "password" text not null,
+ "investor" tinyint(1),
+ "dollars" integer
+ );
+
+Let's not discuss that further.
+
+A second, better, solution is to break out the two tables into user and investor:
+
+ create table "user" (
+ "id" integer not null primary key autoincrement,
+ "name" text not null,
+ "password" text not null
+ );
+
+ create table "investor" (
+ "id" integer not null references user("id"),
+ "dollars" integer
+ );
+
+So that investor's PK is just an FK to the user. We can clearly see the class hierarchy here, in which investor is a subclass of user. In DBIx::Class applications, this second strategy looks like:
+
+ my $user_rs = $schema->resultset('User');
+ my $new_user = $user_rs->create(
+ name => $args->{name},
+ password => $args->{password},
+ );
+
+ ...
+
+ my $new_investor = $schema->resultset('Investor')->create(
+ id => $new_user->id,
+ dollars => $args->{dollars},
+ );
+
+One can cope well with the second strategy, and it seems to be the most popular smart choice.
+
+=head1 HOW?
+
+There is a third strategy implemented here. Make the database do more of the work. It'll save us some typing and it'll make for more expressive code. What if we could do this:
+
+ my $new_investor = $schema->resultset('Investor')->create(
+ name => $args->{name},
+ password => $args->{password},
+ dollars => $args->{dollars},
+ );
+
+And have it Just Work? The user ( {name => $args->{name}, password => $args->{password} } ) should be created transparently, and the use of either user or investor in your code should require no special handling. Deleting and updating $new_investor should also delete or update the user row.
+
+It does. User and investor are both views, their concrete tables abstracted away behind a set of rules and triggers. You would expect the above DBIC create statement to look like this in SQL:
+
+ INSERT INTO investor ("name","password","dollars") VALUES (...);
+
+But using MTI, it is really this:
+
+ INSERT INTO _user_table ("username","password") VALUES (...);
+ INSERT INTO _investor_table ("id","dollars") VALUES (currval('_user_table_id_seq',...) );
+
+For deletes, the triggers fire in reverse, to preserve referential integrity (foreign key constraints). For instance:
+
+ my $investor = $schema->resultset('Investor')->find({id => $args->{id}});
+ $investor->delete;
+
+Becomes:
+
+ DELETE FROM _investor_table WHERE ("id" = ?);
+ DELETE FROM _user_table WHERE ("id" = ?);
+
+
+
+
+
+
+
+
+=head1 AUTHOR
+
+Matt S. Trout, E<lt>mst@shadowcatsystems.co.ukE<gt>
+
+=head2 CONTRIBUTORS
+
+Docs: Amiri Barksdale, E<lt>amiri@metalabel.comE<gt>
+
+=head1 LICENSE
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+L<DBIx::Class>
+L<DBIx::Class::ResultSource>
+
+=cut