Add some subroutine docs. Must write another test so that I can understand all ins...
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / README
1 NAME
2     DBIx::Class::ResultSource::MultipleTableInheritance -- Use multiple
3     tables to define your classes
4
5 SYNOPSIS
6         {
7             package MyApp::Schema::Result::Coffee;
8
9             __PACKAGE__->table_class('DBIx::Class::ResultSource::MultipleTableInheritance');
10             __PACKAGE__->table('coffee');
11             __PACKAGE__->add_columns(
12               "id",
13               {
14                 data_type => "integer",
15                 default_value => "nextval('coffee_seq'::regclass)",
16                 is_auto_increment => 1,
17                 is_foreign_key => 1,
18                 is_nullable => 0,
19                 size => 4,
20               },
21               "flavor",
22               {
23                 data_type => "text",
24                 default_value => "good",
25               },
26             );
27
28             __PACKAGE__->set_primary_key("id");
29
30             1;
31         }
32
33         {
34             package MyApp::Schema::Result::Sumatra;
35
36             use parent 'Coffee';
37
38             __PACKAGE__->table('sumatra');
39
40             __PACKAGE__->add_columns(
41               "aroma",
42               {
43                 data_type => "text",
44                 default_value => undef,
45                 is_nullable => 0,
46               },
47             );
48
49             1;
50         }
51     
52         ...
53
54         my $schema = MyApp::Schema->connect($dsn);
55
56         my $cup = $schema->resultset('Sumatra')->new;
57
58         print STDERR Dumper $cup->columns;
59
60             $VAR1 = 'id';
61             $VAR2 = 'flavor';
62             $VAR3 = 'aroma';
63
64     Inherit from this package and you can make a resultset class from a
65     view, but that's more than a little bit misleading: the result is
66     transparently writable.
67
68     This is accomplished through the use of stored procedures that map
69     changes written to the view to changes to the underlying concrete
70     tables.
71
72 WHY?
73     In many applications, many classes are subclasses of others. Let's say
74     you have this schema:
75
76         # Conceptual domain model
77     
78         class User {
79                 has id,
80                 has name,
81                 has password
82         }
83
84         class Investor {
85             has id,
86             has name,
87             has password,
88             has dollars
89         }
90
91     That's redundant. Hold on a sec...
92
93         class User {
94                 has id,
95                 has name,
96                 has password
97         }
98
99         class Investor extends User {
100             has dollars
101         }
102
103     Good idea, but how to put this into code?
104
105     One far-too common and absolutely horrendous solution is to have a
106     "checkbox" in your database: a nullable "investor" column, which entails
107     a nullable "dollars" column, in the user table.
108
109         create table "user" (
110             "id" integer not null primary key autoincrement,
111             "name" text not null,
112             "password" text not null,
113             "investor" tinyint(1),
114             "dollars" integer
115         );
116
117     Let's not discuss that further.
118
119     A second, better, solution is to break out the two tables into user and
120     investor:
121
122         create table "user" (
123             "id" integer not null primary key autoincrement,
124             "name" text not null,
125             "password" text not null
126         );
127         
128         create table "investor" (
129             "id" integer not null references user("id"),
130             "dollars" integer
131         );
132
133     So that investor's PK is just an FK to the user. We can clearly see the
134     class hierarchy here, in which investor is a subclass of user. In
135     DBIx::Class applications, this second strategy looks like:
136
137         my $user_rs = $schema->resultset('User');
138         my $new_user = $user_rs->create(
139             name => $args->{name},
140             password => $args->{password},
141         );
142
143         ...
144
145         my $new_investor = $schema->resultset('Investor')->create(
146             id => $new_user->id,
147             dollars => $args->{dollars},
148         );
149
150     One can cope well with the second strategy, and it seems to be the most
151     popular smart choice.
152
153 HOW?
154     There is a third strategy implemented here. Make the database do more of
155     the work: hide the nasty bits so we don't have to handle them unless we
156     really want to. It'll save us some typing and it'll make for more
157     expressive code. What if we could do this:
158
159         my $new_investor = $schema->resultset('Investor')->create(
160             name => $args->{name},
161             password => $args->{password},
162             dollars => $args->{dollars},
163         );
164
165     And have it Just Work? The user...
166
167         {
168             name => $args->{name},
169             password => $args->{password},
170         }
171
172     should be created behind the scenes, and the use of either user or
173     investor in your code should require no special handling. Deleting and
174     updating $new_investor should also delete or update the user row.
175
176     It does. User and investor are both views, their concrete tables
177     abstracted away behind a set of rules and triggers. You would expect the
178     above DBIC create statement to look like this in SQL:
179
180         INSERT INTO investor ("name","password","dollars") VALUES (...);
181
182     But using MTI, it is really this:
183
184         INSERT INTO _user_table ("username","password") VALUES (...);
185         INSERT INTO _investor_table ("id","dollars") VALUES (currval('_user_table_id_seq',...) );
186
187     For deletes, the triggers fire in reverse, to preserve referential
188     integrity (foreign key constraints). For instance:
189
190        my $investor = $schema->resultset('Investor')->find({id => $args->{id}});
191        $investor->delete;
192
193     Becomes:
194
195         DELETE FROM _investor_table WHERE ("id" = ?);
196         DELETE FROM _user_table WHERE ("id" = ?);
197
198 METHODS
199     new MTI find the parents, if any, of your resultset class and adds them
200         to the list of parent_sources for the table.
201
202     add_additional_parents
203         Continuing with coffee:
204
205             __PACKAGE__->result_source_instance->add_additional_parents(
206                 qw/
207                     MyApp::Schema::Result::Beverage
208                     MyApp::Schema::Result::Liquid
209                 /
210             );
211
212         This just lets you manually add additional parents beyond the ones
213         MTI finds.
214
215     add_additional_parent
216             __PACKAGE__->result_source_instance->add_additional_parent(
217                     MyApp::Schema::Result::Beverage
218             );
219
220         You can also add just one.
221
222     attach_additional_sources
223         MTI takes the parents' sources and relationships, creates new
224         DBIx::Class:Table object from them, and registers this as a new,
225         raw, source in the schema, e.g.,
226
227             use MyApp::Schema;
228
229             print STDERR map { "$_\n" } MyApp::Schema->sources;
230
231             # Coffee 
232             # Beverage
233             # Liquid
234             # Sumatra
235             # Raw::Sumatra
236
237         Raw::Sumatra will be used to generate the view.
238
239     view_definition
240         This takes the raw table and generates the view (and stored
241         procedures) you will use.
242
243 AUTHOR
244     Matt S. Trout, <mst@shadowcatsystems.co.uk>
245
246   CONTRIBUTORS
247     Docs: Amiri Barksdale, <amiri@metalabel.com>
248
249 LICENSE
250     This library is free software; you can redistribute it and/or modify it
251     under the same terms as Perl itself.
252
253 SEE ALSO
254     DBIx::Class DBIx::Class::ResultSource
255