Commit | Line | Data |
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__->table_class('DBIx::Class::ResultSource::MultipleTableInheritance'); |
51 | __PACKAGE__->table('coffee'); |
52 | __PACKAGE__->add_columns( |
53 | "id", |
54 | { |
55 | data_type => "integer", |
56 | default_value => "nextval('coffee_seq'::regclass)", |
57 | is_auto_increment => 1, |
58 | is_foreign_key => 1, |
59 | is_nullable => 0, |
60 | size => 4, |
61 | }, |
62 | "flavor", |
63 | { |
64 | data_type => "text", |
65 | default_value => "good", |
66 | }, |
67 | );</pre> |
68 | <pre> |
69 | __PACKAGE__->set_primary_key("id");</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__->table('sumatra');</pre> |
80 | <pre> |
81 | __PACKAGE__->add_columns( |
82 | "aroma", |
83 | { |
84 | data_type => "text", |
85 | default_value => undef, |
86 | is_nullable => 0, |
87 | }, |
88 | );</pre> |
89 | <pre> |
90 | 1; |
91 | } |
92 | |
93 | ...</pre> |
94 | <pre> |
95 | my $schema = MyApp::Schema->connect($dsn);</pre> |
96 | <pre> |
97 | my $cup = $schema->resultset('Sumatra')->new;</pre> |
98 | <pre> |
99 | print STDERR Dumper $cup->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 "checkbox" in your database: a nullable "investor" column, which entails a nullable "dollars" column, in the user table.</p> |
139 | <pre> |
140 | create table "user" ( |
141 | "id" integer not null primary key autoincrement, |
142 | "name" text not null, |
143 | "password" text not null, |
144 | "investor" tinyint(1), |
145 | "dollars" 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 "user" ( |
151 | "id" integer not null primary key autoincrement, |
152 | "name" text not null, |
153 | "password" text not null |
154 | ); |
155 | |
156 | create table "investor" ( |
157 | "id" integer not null references user("id"), |
158 | "dollars" 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->resultset('User'); |
164 | my $new_user = $user_rs->create( |
165 | name => $args->{name}, |
166 | password => $args->{password}, |
167 | );</pre> |
168 | <pre> |
169 | ...</pre> |
170 | <pre> |
171 | my $new_investor = $schema->resultset('Investor')->create( |
172 | id => $new_user->id, |
173 | dollars => $args->{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->resultset('Investor')->create( |
183 | name => $args->{name}, |
184 | password => $args->{password}, |
185 | dollars => $args->{dollars}, |
186 | ); |
187 | |
188 | 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.</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 ("name","password","dollars") VALUES (...);</pre> |
192 | <p>But using MTI, it is really this:</p> |
193 | <pre> |
194 | INSERT INTO _user_table ("username","password") VALUES (...); |
195 | INSERT INTO _investor_table ("id","dollars") 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->resultset('Investor')->find({id => $args->{id}}); |
199 | $investor->delete;</pre> |
200 | <p>Becomes:</p> |
201 | <pre> |
202 | DELETE FROM _investor_table WHERE ("id" = ?); |
203 | DELETE FROM _user_table WHERE ("id" = ?);</pre> |
204 | <p></p> |
205 | <p> |
206 | </p> |
207 | <hr /> |
208 | <h1><a name="author">AUTHOR</a></h1> |
209 | <p>Matt S. Trout, <<a href="mailto:mst@shadowcatsystems.co.uk">mst@shadowcatsystems.co.uk</a>></p> |
210 | <p> |
211 | </p> |
212 | <h2><a name="contributors">CONTRIBUTORS</a></h2> |
213 | <p>Docs: Amiri Barksdale, <<a href="mailto:amiri@metalabel.com">amiri@metalabel.com</a>></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 |
219 | it 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> |