Beginning of docs. Making headway. Next is subroutine docs
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / README.html
CommitLineData
146ec120 1<?xml version="1.0" ?>
2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml">
4<head>
5<title>DBIx::Class::ResultSource::MultipleTableInheritance -- Use multiple tables to define your classes</title>
6<meta http-equiv="content-type" content="text/html; charset=utf-8" />
7<link rev="made" href="mailto:amiri@akbuntu.nonet" />
8</head>
9
10<body style="background-color: white">
11
12
13<!-- INDEX BEGIN -->
14<div name="index">
15<p><a name="__index__"></a></p>
16
17<ul>
18
19 <li><a href="#name">NAME</a></li>
20 <li><a href="#synopsis">SYNOPSIS</a></li>
21 <li><a href="#why">WHY?</a></li>
22 <li><a href="#how">HOW?</a></li>
23 <li><a href="#author">AUTHOR</a></li>
24 <ul>
25
26 <li><a href="#contributors">CONTRIBUTORS</a></li>
27 </ul>
28
29 <li><a href="#license">LICENSE</a></li>
30 <li><a href="#see_also">SEE ALSO</a></li>
31</ul>
32
33<hr name="index" />
34</div>
35<!-- INDEX END -->
36
37<p>
38</p>
39<hr />
40<h1><a name="name">NAME</a></h1>
41<p>DBIx::Class::ResultSource::MultipleTableInheritance -- Use multiple tables to define your classes</p>
42<p>
43</p>
44<hr />
45<h1><a name="synopsis">SYNOPSIS</a></h1>
46<pre>
47 {
48 package MyApp::Schema::Result::Coffee;</pre>
49<pre>
50 __PACKAGE__-&gt;table_class('DBIx::Class::ResultSource::MultipleTableInheritance');
51 __PACKAGE__-&gt;table('coffee');
52 __PACKAGE__-&gt;add_columns(
53 &quot;id&quot;,
54 {
55 data_type =&gt; &quot;integer&quot;,
56 default_value =&gt; &quot;nextval('coffee_seq'::regclass)&quot;,
57 is_auto_increment =&gt; 1,
58 is_foreign_key =&gt; 1,
59 is_nullable =&gt; 0,
60 size =&gt; 4,
61 },
62 &quot;flavor&quot;,
63 {
64 data_type =&gt; &quot;text&quot;,
65 default_value =&gt; &quot;good&quot;,
66 },
67 );</pre>
68<pre>
69 __PACKAGE__-&gt;set_primary_key(&quot;id&quot;);</pre>
70<pre>
71 1;
72 }</pre>
73<pre>
74 {
75 package MyApp::Schema::Result::Sumatra;</pre>
76<pre>
77 use parent 'Coffee';</pre>
78<pre>
79 __PACKAGE__-&gt;table('sumatra');</pre>
80<pre>
81 __PACKAGE__-&gt;add_columns(
82 &quot;aroma&quot;,
83 {
84 data_type =&gt; &quot;text&quot;,
85 default_value =&gt; undef,
86 is_nullable =&gt; 0,
87 },
88 );</pre>
89<pre>
90 1;
91 }
92
93 ...</pre>
94<pre>
95 my $schema = MyApp::Schema-&gt;connect($dsn);</pre>
96<pre>
97 my $cup = $schema-&gt;resultset('Sumatra')-&gt;new;</pre>
98<pre>
99 print STDERR Dumper $cup-&gt;columns;</pre>
100<pre>
101 $VAR1 = 'id';
102 $VAR2 = 'flavor';
103 $VAR3 = 'aroma';</pre>
104<p>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 <strong>writable</strong> and updateable--transparently.</p>
105<p>This is accomplished through the use of stored functions that map changes written to the view to changes to the underlying concrete tables.</p>
106<p>
107</p>
108<hr />
109<h1><a name="why">WHY?</a></h1>
110<p>In many applications, many classes are subclasses of others. Let's say you have this schema:</p>
111<pre>
112 # Conceptual domain model
113
114 class User {
115 has id,
116 has name,
117 has password
118 }</pre>
119<pre>
120 class Investor {
121 has id,
122 has name,
123 has password,
124 has dollars
125 }</pre>
126<p>That's redundant. Hold on a sec...</p>
127<pre>
128 class User {
129 has id,
130 has name,
131 has password
132 }</pre>
133<pre>
134 class Investor isa User {
135 has dollars
136 }</pre>
137<p>Good idea, but how to put this into code?</p>
138<p>One far-too common and absolutely horrendous solution is to have a &quot;checkbox&quot; in your database: a nullable &quot;investor&quot; column, which entails a nullable &quot;dollars&quot; column, in the user table.</p>
139<pre>
140 create table &quot;user&quot; (
141 &quot;id&quot; integer not null primary key autoincrement,
142 &quot;name&quot; text not null,
143 &quot;password&quot; text not null,
144 &quot;investor&quot; tinyint(1),
145 &quot;dollars&quot; integer
146 );</pre>
147<p>Let's not discuss that further.</p>
148<p>A second, better, solution is to break out the two tables into user and investor:</p>
149<pre>
150 create table &quot;user&quot; (
151 &quot;id&quot; integer not null primary key autoincrement,
152 &quot;name&quot; text not null,
153 &quot;password&quot; text not null
154 );
155
156 create table &quot;investor&quot; (
157 &quot;id&quot; integer not null references user(&quot;id&quot;),
158 &quot;dollars&quot; integer
159 );</pre>
160<p>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:</p>
161<pre>
162
163 my $user_rs = $schema-&gt;resultset('User');
164 my $new_user = $user_rs-&gt;create(
165 name =&gt; $args-&gt;{name},
166 password =&gt; $args-&gt;{password},
167 );</pre>
168<pre>
169 ...</pre>
170<pre>
171 my $new_investor = $schema-&gt;resultset('Investor')-&gt;create(
172 id =&gt; $new_user-&gt;id,
173 dollars =&gt; $args-&gt;{dollars},
174 );</pre>
175<p>One can cope well with the second strategy, and it seems to be the most popular smart choice.</p>
176<p>
177</p>
178<hr />
179<h1><a name="how">HOW?</a></h1>
180<p>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:</p>
181<pre>
182 my $new_investor = $schema-&gt;resultset('Investor')-&gt;create(
183 name =&gt; $args-&gt;{name},
184 password =&gt; $args-&gt;{password},
185 dollars =&gt; $args-&gt;{dollars},
186 );
187
188And have it Just Work? The user ( {name =&gt; $args-&gt;{name}, password =&gt; $args-&gt;{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.</pre>
189<p>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:</p>
190<pre>
191 INSERT INTO investor (&quot;name&quot;,&quot;password&quot;,&quot;dollars&quot;) VALUES (...);</pre>
192<p>But using MTI, it is really this:</p>
193<pre>
194 INSERT INTO _user_table (&quot;username&quot;,&quot;password&quot;) VALUES (...);
195 INSERT INTO _investor_table (&quot;id&quot;,&quot;dollars&quot;) VALUES (currval('_user_table_id_seq',...) );</pre>
196<p>For deletes, the triggers fire in reverse, to preserve referential integrity (foreign key constraints). For instance:</p>
197<pre>
198 my $investor = $schema-&gt;resultset('Investor')-&gt;find({id =&gt; $args-&gt;{id}});
199 $investor-&gt;delete;</pre>
200<p>Becomes:</p>
201<pre>
202 DELETE FROM _investor_table WHERE (&quot;id&quot; = ?);
203 DELETE FROM _user_table WHERE (&quot;id&quot; = ?);</pre>
204<p></p>
205<p>
206</p>
207<hr />
208<h1><a name="author">AUTHOR</a></h1>
209<p>Matt S. Trout, &lt;<a href="mailto:mst@shadowcatsystems.co.uk">mst@shadowcatsystems.co.uk</a>&gt;</p>
210<p>
211</p>
212<h2><a name="contributors">CONTRIBUTORS</a></h2>
213<p>Docs: Amiri Barksdale, &lt;<a href="mailto:amiri@metalabel.com">amiri@metalabel.com</a>&gt;</p>
214<p>
215</p>
216<hr />
217<h1><a name="license">LICENSE</a></h1>
218<p>This library is free software; you can redistribute it and/or modify
219it under the same terms as Perl itself.</p>
220<p>
221</p>
222<hr />
223<h1><a name="see_also">SEE ALSO</a></h1>
224<p><a href="/DBIx/Class.html">the DBIx::Class manpage</a>
225<a href="/DBIx/Class/ResultSource.html">the DBIx::Class::ResultSource manpage</a></p>
226
227</body>
228
229</html>