Add some subroutine docs. Must write another test so that I can understand all ins...
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / README
CommitLineData
146ec120 1NAME
2 DBIx::Class::ResultSource::MultipleTableInheritance -- Use multiple
3 tables to define your classes
4
5SYNOPSIS
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
e7189506 66 transparently writable.
146ec120 67
e7189506 68 This is accomplished through the use of stored procedures that map
146ec120 69 changes written to the view to changes to the underlying concrete
70 tables.
71
72WHY?
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
e7189506 99 class Investor extends User {
146ec120 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
153HOW?
154 There is a third strategy implemented here. Make the database do more of
e7189506 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:
146ec120 158
159 my $new_investor = $schema->resultset('Investor')->create(
160 name => $args->{name},
161 password => $args->{password},
162 dollars => $args->{dollars},
163 );
164
e7189506 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.
146ec120 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
e7189506 198METHODS
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
146ec120 243AUTHOR
244 Matt S. Trout, <mst@shadowcatsystems.co.uk>
245
246 CONTRIBUTORS
247 Docs: Amiri Barksdale, <amiri@metalabel.com>
248
249LICENSE
250 This library is free software; you can redistribute it and/or modify it
251 under the same terms as Perl itself.
252
253SEE ALSO
254 DBIx::Class DBIx::Class::ResultSource
255