Add some subroutine docs. Must write another test so that I can understand all ins...
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / README.html
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="#synopsis">SYNOPSIS</a></li>
20         <li><a href="#why">WHY?</a></li>
21         <li><a href="#how">HOW?</a></li>
22         <li><a href="#methods">METHODS</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 <h1><a name="synopsis">SYNOPSIS</a></h1>
40 <pre>
41     {
42         package MyApp::Schema::Result::Coffee;</pre>
43 <pre>
44         __PACKAGE__-&gt;table_class('DBIx::Class::ResultSource::MultipleTableInheritance');
45         __PACKAGE__-&gt;table('coffee');
46         __PACKAGE__-&gt;add_columns(
47           &quot;id&quot;,
48           {
49             data_type =&gt; &quot;integer&quot;,
50             default_value =&gt; &quot;nextval('coffee_seq'::regclass)&quot;,
51             is_auto_increment =&gt; 1,
52             is_foreign_key =&gt; 1,
53             is_nullable =&gt; 0,
54             size =&gt; 4,
55           },
56           &quot;flavor&quot;,
57           {
58             data_type =&gt; &quot;text&quot;,
59             default_value =&gt; &quot;good&quot;,
60           },
61         );</pre>
62 <pre>
63         __PACKAGE__-&gt;set_primary_key(&quot;id&quot;);</pre>
64 <pre>
65         1;
66     }</pre>
67 <pre>
68     {
69         package MyApp::Schema::Result::Sumatra;</pre>
70 <pre>
71         use parent 'Coffee';</pre>
72 <pre>
73         __PACKAGE__-&gt;table('sumatra');</pre>
74 <pre>
75         __PACKAGE__-&gt;add_columns(
76           &quot;aroma&quot;,
77           {
78             data_type =&gt; &quot;text&quot;,
79             default_value =&gt; undef,
80             is_nullable =&gt; 0,
81           },
82         );</pre>
83 <pre>
84         1;
85     }
86     
87     ...</pre>
88 <pre>
89     my $schema = MyApp::Schema-&gt;connect($dsn);</pre>
90 <pre>
91     my $cup = $schema-&gt;resultset('Sumatra')-&gt;new;</pre>
92 <pre>
93     print STDERR Dumper $cup-&gt;columns;</pre>
94 <pre>
95         $VAR1 = 'id';
96         $VAR2 = 'flavor';
97         $VAR3 = 'aroma';</pre>
98 <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>transparently writable</strong>.</p>
99 <p>This is accomplished through the use of stored procedures that map changes written to the view to changes to the underlying concrete tables.</p>
100 <p>
101 </p>
102 <hr />
103 <h1><a name="why">WHY?</a></h1>
104 <p>In many applications, many classes are subclasses of others. Let's say you have this schema:</p>
105 <pre>
106     # Conceptual domain model
107     
108     class User {
109             has id,
110             has name,
111             has password
112     }</pre>
113 <pre>
114     class Investor {
115         has id,
116         has name,
117         has password,
118         has dollars
119     }</pre>
120 <p>That's redundant. Hold on a sec...</p>
121 <pre>
122     class User {
123             has id,
124             has name,
125             has password
126     }</pre>
127 <pre>
128     class Investor extends User {
129         has dollars
130     }</pre>
131 <p>Good idea, but how to put this into code?</p>
132 <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>
133 <pre>
134     create table &quot;user&quot; (
135         &quot;id&quot; integer not null primary key autoincrement,
136         &quot;name&quot; text not null,
137         &quot;password&quot; text not null,
138         &quot;investor&quot; tinyint(1),
139         &quot;dollars&quot; integer
140     );</pre>
141 <p>Let's not discuss that further.</p>
142 <p>A second, better, solution is to break out the two tables into user and investor:</p>
143 <pre>
144     create table &quot;user&quot; (
145         &quot;id&quot; integer not null primary key autoincrement,
146         &quot;name&quot; text not null,
147         &quot;password&quot; text not null
148     );
149         
150     create table &quot;investor&quot; (
151         &quot;id&quot; integer not null references user(&quot;id&quot;),
152         &quot;dollars&quot; integer
153     );</pre>
154 <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>
155 <pre>
156
157     my $user_rs = $schema-&gt;resultset('User');
158     my $new_user = $user_rs-&gt;create(
159         name =&gt; $args-&gt;{name},
160         password =&gt; $args-&gt;{password},
161     );</pre>
162 <pre>
163     ...</pre>
164 <pre>
165     my $new_investor = $schema-&gt;resultset('Investor')-&gt;create(
166         id =&gt; $new_user-&gt;id,
167         dollars =&gt; $args-&gt;{dollars},
168     );</pre>
169 <p>One can cope well with the second strategy, and it seems to be the most popular smart choice.</p>
170 <p>
171 </p>
172 <hr />
173 <h1><a name="how">HOW?</a></h1>
174 <p>There is a third strategy implemented here. Make the database do more of the work: hide the nasty bits so we don't have to handle them unless we really want to. It'll save us some typing and it'll make for more expressive code. What if we could do this:</p>
175 <pre>
176     my $new_investor = $schema-&gt;resultset('Investor')-&gt;create(
177         name =&gt; $args-&gt;{name},
178         password =&gt; $args-&gt;{password},
179         dollars =&gt; $args-&gt;{dollars},
180     );
181     
182 And have it Just Work? The user...</pre>
183 <pre>
184     {
185         name =&gt; $args-&gt;{name},
186         password =&gt; $args-&gt;{password},
187     }</pre>
188 <p>should be created behind the scenes, 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.</p>
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>
205 </p>
206 <hr />
207 <h1><a name="methods">METHODS</a></h1>
208 <dl>
209 <dt><strong><a name="new" class="item">new</a></strong></dt>
210
211 <dd>
212 <p>MTI find the parents, if any, of your resultset class and adds them to the list of parent_sources for the table.</p>
213 </dd>
214 <dt><strong><a name="add_additional_parents" class="item">add_additional_parents</a></strong></dt>
215
216 <dd>
217 <p>Continuing with coffee:</p>
218 <pre>
219     __PACKAGE__-&gt;result_source_instance-&gt;add_additional_parents(
220         qw/
221             MyApp::Schema::Result::Beverage
222             MyApp::Schema::Result::Liquid
223         /
224     );</pre>
225 <p>This just lets you manually add additional parents beyond the ones MTI finds.</p>
226 </dd>
227 <dt><strong><a name="add_additional_parent" class="item">add_additional_parent</a></strong></dt>
228
229 <dd>
230 <pre>
231     __PACKAGE__-&gt;result_source_instance-&gt;add_additional_parent(
232             MyApp::Schema::Result::Beverage
233     );</pre>
234 <p>You can also add just one.</p>
235 </dd>
236 <dt><strong><a name="attach_additional_sources" class="item">attach_additional_sources</a></strong></dt>
237
238 <dd>
239 <p>MTI takes the parents' sources and relationships, creates new DBIx::Class:Table object from them, and registers this as a new, raw, source in the schema, e.g.,</p>
240 <pre>
241     use MyApp::Schema;</pre>
242 <pre>
243     print STDERR map { &quot;$_\n&quot; } MyApp::Schema-&gt;sources;</pre>
244 <pre>
245     # Coffee 
246     # Beverage
247     # Liquid
248     # Sumatra
249     # Raw::Sumatra</pre>
250 <p>Raw::Sumatra will be used to generate the view.</p>
251 </dd>
252 <dt><strong><a name="view_definition" class="item">view_definition</a></strong></dt>
253
254 <dd>
255 <p>This takes the raw table and generates the view (and stored procedures) you will use.</p>
256 </dd>
257 </dl>
258 <p>
259 </p>
260 <hr />
261 <h1><a name="author">AUTHOR</a></h1>
262 <p>Matt S. Trout, &lt;<a href="mailto:mst@shadowcatsystems.co.uk">mst@shadowcatsystems.co.uk</a>&gt;</p>
263 <p>
264 </p>
265 <h2><a name="contributors">CONTRIBUTORS</a></h2>
266 <p>Docs: Amiri Barksdale, &lt;<a href="mailto:amiri@metalabel.com">amiri@metalabel.com</a>&gt;</p>
267 <p>
268 </p>
269 <hr />
270 <h1><a name="license">LICENSE</a></h1>
271 <p>This library is free software; you can redistribute it and/or modify
272 it under the same terms as Perl itself.</p>
273 <p>
274 </p>
275 <hr />
276 <h1><a name="see_also">SEE ALSO</a></h1>
277 <p><a href="/DBIx/Class.html">the DBIx::Class manpage</a>
278 <a href="/DBIx/Class/ResultSource.html">the DBIx::Class::ResultSource manpage</a></p>
279
280 </body>
281
282 </html>