OK, sequences finally working.
[dbsrgits/DBIx-Class-ResultSource-MultipleTableInheritance.git] / lib / DBIx / Class / ResultSource / MultipleTableInheritance.pm
CommitLineData
876f6525 1package DBIx::Class::ResultSource::MultipleTableInheritance;
2
3use strict;
4use warnings;
5use parent qw(DBIx::Class::ResultSource::View);
876f6525 6use Method::Signatures::Simple;
7use Carp::Clan qw/^DBIx::Class/;
ca79850d 8use aliased 'DBIx::Class::ResultSource::Table';
7abe3af2 9use aliased 'DBIx::Class::ResultClass::HashRefInflator';
05fd2477 10use String::TT qw(strip tt);
92ebfc06 11use Scalar::Util qw(blessed);
ca79850d 12use namespace::autoclean;
70d56286 13
146ec120 14our $VERSION = 0.01;
70d56286 15
803ffff2 16__PACKAGE__->mk_group_accessors(simple => qw(parent_source additional_parents));
876f6525 17
e7189506 18# how this works:
19#
20# On construction, we hook $self->result_class->result_source_instance
21# if present to get the superclass' source object
5fa55fff 22#
e7189506 23# When attached to a schema, we need to add sources to that schema with
24# appropriate relationships for the foreign keys so the concrete tables
25# get generated
26#
27# We also generate our own view definition using this class' concrete table
28# and the view for the superclass, and stored procedures for the insert,
29# update and delete operations on this view.
30#
31# deploying the postgres rules through SQLT may be a pain though.
32
876f6525 33method new ($class: @args) {
34 my $new = $class->next::method(@args);
35 my $rc = $new->result_class;
36 if (my $meth = $rc->can('result_source_instance')) {
7abe3af2 37 my $source = $rc->$meth;
38 if ($source->result_class ne $new->result_class
39 && $new->result_class->isa($source->result_class)) {
40 $new->parent_source($source);
41 }
876f6525 42 }
43 return $new;
44}
45
4e4f71e3 46method add_additional_parents (@classes) {
47 foreach my $class (@classes) {
48 Class::C3::Componentised->ensure_class_loaded($class);
49 $self->add_additional_parent(
50 $class->result_source_instance
51 );
52 }
53}
54
803ffff2 55method add_additional_parent ($source) {
56 my ($our_pk, $their_pk) = map {
57 join('|',sort $_->primary_columns)
58 } ($self, $source);
59
60 confess "Can't attach additional parent ${\$source->name} - it has different PKs ($their_pk versus our $our_pk)"
61 unless $their_pk eq $our_pk;
62 $self->additional_parents([
63 @{$self->additional_parents||[]}, $source
64 ]);
65 $self->add_columns(
66 map {
67 $_ => # put the extra key first to default it
68 { originally_defined_in => $source->name, %{$source->column_info($_)}, },
69 } grep !$self->has_column($_), $source->columns
70 );
71 foreach my $rel ($source->relationships) {
72 my $rel_info = $source->relationship_info($rel);
73 $self->add_relationship(
74 $rel, $rel_info->{source}, $rel_info->{cond},
75 # extra key first to default it
76 {originally_defined_in => $source->name, %{$rel_info->{attrs}}},
77 );
78 }
a010ebf9 79 { no strict 'refs';
80 push(@{$self->result_class.'::ISA'}, $source->result_class);
81 }
803ffff2 82}
83
8b229aa6 84method _source_by_name ($name) {
85 my $schema = $self->schema;
5fa55fff 86 my ($source) =
8b229aa6 87 grep { $_->name eq $name }
88 map $schema->source($_), $schema->sources;
89 confess "Couldn't find attached source for parent $name - did you use load_classes? This module is only compatible with load_namespaces"
90 unless $source;
91 return $source;
92}
93
7abe3af2 94method schema (@args) {
95 my $ret = $self->next::method(@args);
96 if (@args) {
c73d582b 97 if ($self->parent_source) {
c73d582b 98 my $parent_name = $self->parent_source->name;
8b229aa6 99 $self->parent_source($self->_source_by_name($parent_name));
c73d582b 100 }
8b229aa6 101 $self->additional_parents([
102 map { $self->_source_by_name($_->name) }
103 @{$self->additional_parents||[]}
104 ]);
7abe3af2 105 }
106 return $ret;
107}
108
c73d582b 109method attach_additional_sources () {
4d88a8d7 110 my $raw_name = $self->raw_source_name;
ca79850d 111 my $schema = $self->schema;
112
113 # if the raw source is already present we can assume we're done
114 return if grep { $_ eq $raw_name } $schema->sources;
4d88a8d7 115
ca79850d 116 # our parent should've been registered already actually due to DBIC
117 # attaching subclass sources later in load_namespaces
4d88a8d7 118
ca79850d 119 my $parent;
120 if ($self->parent_source) {
121 my $parent_name = $self->parent_source->name;
5fa55fff 122 ($parent) =
ca79850d 123 grep { $_->name eq $parent_name }
124 map $schema->source($_), $schema->sources;
125 confess "Couldn't find attached source for parent $parent_name - did you use load_classes? This module is only compatible with load_namespaces"
126 unless $parent;
05fd2477 127 $self->parent_source($parent); # so our parent is the one in this schema
ca79850d 128 }
4d88a8d7 129
130 # create the raw table source
131
132 my $table = Table->new({ name => $self->raw_table_name });
133
ca79850d 134 # we don't need to add the PK cols explicitly if we're the root table
4d88a8d7 135 # since they'll get added below
136
803ffff2 137 my %pk_join;
138
ca79850d 139 if ($parent) {
ca79850d 140 foreach my $pri ($self->primary_columns) {
141 my %info = %{$self->column_info($pri)};
142 delete @info{qw(is_auto_increment sequence auto_nextval)};
7abe3af2 143 $table->add_column($pri => \%info);
803ffff2 144 $pk_join{"foreign.${pri}"} = "self.${pri}";
ca79850d 145 }
4d88a8d7 146 # have to use source name lookups rather than result class here
147 # because we don't actually have a result class on the raw sources
803ffff2 148 $table->add_relationship('parent', $parent->raw_source_name, \%pk_join);
c8e085ba 149 $self->deploy_depends_on->{$parent->result_class} = 1;
803ffff2 150 }
151
152 foreach my $add (@{$self->additional_parents||[]}) {
153 $table->add_relationship(
154 'parent_'.$add->name, $add->source_name, \%pk_join
155 );
c965b761 156 $self->deploy_depends_on->{$add->result_class} = 1 if $add->isa('DBIx::Class::ResultSource::View');
ca79850d 157 }
4d88a8d7 158 $table->add_columns(
159 map { ($_ => { %{$self->column_info($_)} }) }
160 grep { $self->column_info($_)->{originally_defined_in} eq $self->name }
161 $self->columns
162 );
ca79850d 163 $table->set_primary_key($self->primary_columns);
5fa55fff 164
90cf1bcf 165 ## Attempting to re-add sequence here -- AKB
5fa55fff 166 #for my $pk ( $self->primary_columns ) {
167 #if ($parent) {
168
169##use 5.012; use Devel::Dwarn; say Dwarn $schema->source($table->_relationships->{parent}->{class}) if $table->_relationships->{parent}->{class};
170 #$table->columns_info->{$pk}->{sequence} =
171 #$self->set_sequence(
172 #$schema->source( $table->_relationships->{parent}->{class} )->name,
173 #$self->primary_columns )
174 #if $table->columns_info->{$pk}->{originally_defined_in} ne $self->name
175 #&& $table->_relationships->{parent}->{class};
176 #}
177 #}
490d5481 178
179 # we need to copy our rels to the raw object as well
180 # note that ->add_relationship on a source object doesn't create an
181 # accessor so we can leave that part in the attributes
182
183 # if the other side is a table then we need to copy any rels it has
184 # back to us, as well, so that they point at the raw table. if the
185 # other side is an MTI view then we need to create the rels to it to
186 # point at -its- raw table; we don't need to worry about backrels because
187 # it's going to run this method too (and its raw source might not exist
188 # yet so we can't, anyway)
189
190 foreach my $rel ($self->relationships) {
191 my $rel_info = $self->relationship_info($rel);
192
803ffff2 193 # if we got this from the superclass, -its- raw table will nail this.
194 # if we got it from an additional parent, it's its problem.
195 next unless $rel_info->{attrs}{originally_defined_in} eq $self->name;
196
490d5481 197 my $f_source = $schema->source($rel_info->{source});
198
199 # __PACKAGE__ is correct here because subclasses should be caught
200
201 my $one_of_us = $f_source->isa(__PACKAGE__);
202
203 my $f_source_name = $f_source->${\
204 ($one_of_us ? 'raw_source_name' : 'source_name')
205 };
5fa55fff 206
490d5481 207 $table->add_relationship(
208 '_'.$rel, $f_source_name, @{$rel_info}{qw(cond attrs)}
209 );
210
211 unless ($one_of_us) {
212 my $reverse = do {
213 # we haven't been registered yet, so reverse_ cries
214 # XXX this is evil and will probably break eventually
215 local @{$schema->source_registrations}
216 {map $self->$_, qw(source_name result_class)}
217 = ($self, $self);
218 $self->reverse_relationship_info($rel);
219 };
220 foreach my $rev_rel (keys %$reverse) {
221 $f_source->add_relationship(
222 '_raw_'.$rev_rel, $raw_name, @{$reverse->{$rev_rel}}{qw(cond attrs)}
223 );
224 }
225 }
226 }
227
ca79850d 228 $schema->register_source($raw_name => $table);
229}
230
231method set_primary_key (@args) {
232 if ($self->parent_source) {
233 confess "Can't set primary key on a subclass";
234 }
235 return $self->next::method(@args);
876f6525 236}
237
e96b2eeb 238method set_sequence ($table_name, @pks) {
239 return $table_name . '_' . join('_',@pks) . '_' . 'seq';
240}
241
4d88a8d7 242method raw_source_name () {
876f6525 243 my $base = $self->source_name;
05fd2477 244 confess "Can't generate raw source name for ${\$self->name} when we don't have a source_name"
876f6525 245 unless $base;
246 return 'Raw::'.$base;
247}
70d56286 248
4d88a8d7 249method raw_table_name () {
250 return '_'.$self->name;
251}
252
876f6525 253method add_columns (@args) {
254 my $ret = $self->next::method(@args);
255 $_->{originally_defined_in} ||= $self->name for values %{$self->_columns};
256 return $ret;
70d56286 257}
258
803ffff2 259method add_relationship ($name, $f_source, $cond, $attrs) {
260 $self->next::method(
261 $name, $f_source, $cond,
262 { originally_defined_in => $self->name, %{$attrs||{}}, }
263 );
264}
265
487f4489 266BEGIN {
267
268 # helper routines, constructed as anon subs so autoclean nukes them
269
270 use signatures;
271
272 *argify = sub (@names) {
273 map '_'.$_, @names;
274 };
275
276 *qualify_with = sub ($source, @names) {
92ebfc06 277 my $name = blessed($source) ? $source->name : $source;
278 map join('.', $name, $_), @names;
487f4489 279 };
280
281 *body_cols = sub ($source) {
282 my %pk; @pk{$source->primary_columns} = ();
283 map +{ %{$source->column_info($_)}, name => $_ },
284 grep !exists $pk{$_}, $source->columns;
285 };
286
287 *pk_cols = sub ($source) {
288 map +{ %{$source->column_info($_)}, name => $_ },
289 $source->primary_columns;
290 };
291
92ebfc06 292 *names_of = sub (@cols) { map $_->{name}, @cols };
487f4489 293
c8e085ba 294 *function_body = sub {
295 my ($name,$args,$body_parts) = @_;
05fd2477 296 my $arglist = join(
297 ', ',
388d83fc 298 map "_${\$_->{name}} ${\uc($_->{data_type})}",
05fd2477 299 @$args
300 );
301 my $body = join("\n", '', map " $_;", @$body_parts);
302 return strip tt q{
303 CREATE OR REPLACE FUNCTION [% name %]
304 ([% arglist %])
305 RETURNS VOID AS $function$
306 BEGIN
307 [%- body %]
308 END;
309 $function$ LANGUAGE plpgsql;
310 };
487f4489 311 };
c8e085ba 312 #*function_body = sub ($name,$args,$body_parts) {
313 #my $arglist = join(
314 #', ',
315 #map "_${\$_->{name}} ${\uc($_->{data_type})}",
316 #@$args
317 #);
318 #my $body = join("\n", '', map " $_;", @$body_parts);
319 #return strip tt q{
320 #CREATE OR REPLACE FUNCTION [% name %]
321 #([% arglist %])
322 #RETURNS VOID AS $function$
323 #BEGIN
324 #[%- body %]
325 #END;
326 #$function$ LANGUAGE plpgsql;
327 #};
328 #};
487f4489 329}
330
05fd2477 331BEGIN {
332
333 use signatures;
334
335 *arg_hash = sub ($source) {
336 map +($_ => \(argify $_)), names_of body_cols $source;
337 };
92ebfc06 338
339 *rule_body = sub ($on, $to, $oldlist, $newlist) {
340 my $arglist = join(', ',
341 (qualify_with 'OLD', names_of @$oldlist),
342 (qualify_with 'NEW', names_of @$newlist),
343 );
344 $to = $to->name if blessed($to);
345 return strip tt q{
346 CREATE RULE _[% to %]_[% on %]_rule AS
347 ON [% on | upper %] TO [% to %]
348 DO INSTEAD (
3c259cfb 349 SELECT [% to %]_[% on %]([% arglist %])
92ebfc06 350 );
351 };
352 };
05fd2477 353}
354
355method root_table () {
356 $self->parent_source
357 ? $self->parent_source->root_table
358 : $self->schema->source($self->raw_source_name)
359}
360
487f4489 361method view_definition () {
362 my $schema = $self->schema;
363 confess "Can't generate view without connected schema, sorry"
364 unless $schema && $schema->storage;
365 my $sqla = $schema->storage->sql_maker;
2816c8ed 366 my $table = $self->schema->source($self->raw_source_name);
487f4489 367 my $super_view = $self->parent_source;
2816c8ed 368 my @all_parents = my @other_parents = @{$self->additional_parents||[]};
369 push(@all_parents, $super_view) if defined($super_view);
370 my @sources = ($table, @all_parents);
487f4489 371 my @body_cols = map body_cols($_), @sources;
d8c2caa7 372
373 # Order body_cols to match the columns order.
374 # Must match or you get typecast errors.
375 my %body_cols = map { $_->{name} => $_ } @body_cols;
376 @body_cols =
377 map { $body_cols{$_} }
378 grep { defined $body_cols{$_} }
379 $self->columns;
487f4489 380 my @pk_cols = pk_cols $self;
92ebfc06 381
d8c2caa7 382 # Grab sequence from root table. Only works with one PK named id...
383 # TBD: fix this so it's more flexible.
384 for my $pk_col (@pk_cols) {
385 $self->columns_info->{ $pk_col->{name} }->{sequence} =
386 $self->root_table->name . '_id_seq';
387 }
388
92ebfc06 389 # SELECT statement
390
2816c8ed 391 my $am_root = !($super_view || @other_parents);
392
487f4489 393 my $select = $sqla->select(
2816c8ed 394 ($am_root
395 ? ($table->name)
396 : ([ # FROM _tbl _tbl
487f4489 397 { $table->name => $table->name },
2816c8ed 398 map {
399 my $parent = $_;
400 [ # JOIN view view
401 { $parent->name => $parent->name },
402 # ON _tbl.id = view.id
403 { map +(qualify_with($parent, $_), qualify_with($table, $_)),
404 names_of @pk_cols }
405 ]
406 } @all_parents
487f4489 407 ])
2816c8ed 408 ),
487f4489 409 [ (qualify_with $table, names_of @pk_cols), names_of @body_cols ],
05fd2477 410 ).';';
92ebfc06 411
2816c8ed 412 my ($now, @next) = grep defined, $super_view, $table, @other_parents;
92ebfc06 413
414 # INSERT function
415
05fd2477 416 # NOTE: this assumes a single PK col called id with a sequence somewhere
417 # but nothing else -should- so fixing this should make everything work
418 my $insert_func =
c8e085ba 419 function_body
05fd2477 420 $self->name.'_insert',
421 \@body_cols,
422 [
2816c8ed 423 $sqla->insert( # INSERT INTO tbl/super_view (foo, ...) VALUES (_foo, ...)
05fd2477 424 $now->name,
425 { arg_hash $now },
426 ),
2816c8ed 427 (map {
428 $sqla->insert( # INSERT INTO parent (id, ...)
429 # VALUES (currval('_root_tbl_id_seq'), ...)
430 $_->name,
431 {
432 (arg_hash $_),
433 id => \"currval('${\$self->root_table->name}_id_seq')",
434 }
435 )
436 } @next)
05fd2477 437 ];
92ebfc06 438
05fd2477 439 # note - similar to arg_hash but not quite enough to share code sanely
440 my $pk_where = { # id = _id AND id2 = _id2 ...
441 map +($_ => \"= ${\argify $_}"), names_of @pk_cols
442 };
92ebfc06 443
444 # UPDATE function
445
05fd2477 446 my $update_func =
c8e085ba 447 function_body
05fd2477 448 $self->name.'_update',
449 [ @pk_cols, @body_cols ],
450 [ map $sqla->update(
451 $_->name, # UPDATE foo
452 { arg_hash $_ }, # SET a = _a
453 $pk_where,
454 ), @sources
455 ];
92ebfc06 456
457 # DELETE function
458
05fd2477 459 my $delete_func =
c8e085ba 460 function_body
05fd2477 461 $self->name.'_delete',
462 [ @pk_cols ],
463 [ map $sqla->delete($_->name, $pk_where), @sources ];
92ebfc06 464
465 my @rules = (
466 (rule_body insert => $self, [], \@body_cols),
467 (rule_body update => $self, \@pk_cols, \@body_cols),
468 (rule_body delete => $self, \@pk_cols, []),
469 );
470 return join("\n\n", $select, $insert_func, $update_func, $delete_func, @rules);
487f4489 471}
472
70d56286 4731;
146ec120 474
475__END__
f5c54951 476
146ec120 477=head1 NAME
478
f5c54951 479DBIx::Class::ResultSource::MultipleTableInheritance
5fa55fff 480Use multiple tables to define your classes
f5c54951 481
482=head1 NOTICE
483
484This only works with PostgreSQL for the moment.
146ec120 485
486=head1 SYNOPSIS
487
146ec120 488 {
f8864134 489 package Cafe::Result::Coffee;
146ec120 490
f8864134 491 use strict;
492 use warnings;
493 use parent 'DBIx::Class::Core';
494 use aliased 'DBIx::Class::ResultSource::MultipleTableInheritance'
495 => 'MTI';
496
497 __PACKAGE__->table_class(MTI);
146ec120 498 __PACKAGE__->table('coffee');
499 __PACKAGE__->add_columns(
f8864134 500 "id", { data_type => "integer" },
501 "flavor", {
502 data_type => "text",
503 default_value => "good" },
146ec120 504 );
505
506 __PACKAGE__->set_primary_key("id");
507
508 1;
509 }
510
511 {
f8864134 512 package Cafe::Result::Sumatra;
146ec120 513
f8864134 514 use parent 'Cafe::Result::Coffee';
146ec120 515
516 __PACKAGE__->table('sumatra');
517
f8864134 518 __PACKAGE__->add_columns( "aroma",
519 { data_type => "text" }
146ec120 520 );
521
522 1;
523 }
5fa55fff 524
146ec120 525 ...
526
f8864134 527 my $schema = Cafe->connect($dsn,$user,$pass);
146ec120 528
f8864134 529 my $cup = $schema->resultset('Sumatra');
146ec120 530
f8864134 531 print STDERR Dwarn $cup->result_source->columns;
146ec120 532
f8864134 533 "id"
534 "flavor"
535 "aroma"
536 ..
146ec120 537
f5c54951 538Inherit from this package and you can make a resultset class from a view, but
539that's more than a little bit misleading: the result is B<transparently
540writable>.
146ec120 541
f5c54951 542This is accomplished through the use of stored procedures that map changes
543written to the view to changes to the underlying concrete tables.
146ec120 544
545=head1 WHY?
546
f5c54951 547In many applications, many classes are subclasses of others. Let's say you
548have this schema:
146ec120 549
550 # Conceptual domain model
5fa55fff 551
146ec120 552 class User {
f5c54951 553 has id,
554 has name,
555 has password
146ec120 556 }
557
558 class Investor {
559 has id,
560 has name,
561 has password,
562 has dollars
563 }
564
565That's redundant. Hold on a sec...
566
567 class User {
f5c54951 568 has id,
569 has name,
570 has password
146ec120 571 }
572
e7189506 573 class Investor extends User {
146ec120 574 has dollars
575 }
576
577Good idea, but how to put this into code?
578
f5c54951 579One far-too common and absolutely horrendous solution is to have a "checkbox"
580in your database: a nullable "investor" column, which entails a nullable
581"dollars" column, in the user table.
146ec120 582
583 create table "user" (
584 "id" integer not null primary key autoincrement,
585 "name" text not null,
586 "password" text not null,
587 "investor" tinyint(1),
588 "dollars" integer
589 );
590
591Let's not discuss that further.
592
f5c54951 593A second, better, solution is to break out the two tables into user and
594investor:
146ec120 595
596 create table "user" (
597 "id" integer not null primary key autoincrement,
598 "name" text not null,
599 "password" text not null
600 );
5fa55fff 601
146ec120 602 create table "investor" (
603 "id" integer not null references user("id"),
604 "dollars" integer
605 );
606
f5c54951 607So that investor's PK is just an FK to the user. We can clearly see the class
608hierarchy here, in which investor is a subclass of user. In DBIx::Class
609applications, this second strategy looks like:
5fa55fff 610
146ec120 611 my $user_rs = $schema->resultset('User');
612 my $new_user = $user_rs->create(
613 name => $args->{name},
614 password => $args->{password},
615 );
616
617 ...
618
619 my $new_investor = $schema->resultset('Investor')->create(
620 id => $new_user->id,
621 dollars => $args->{dollars},
622 );
623
f5c54951 624One can cope well with the second strategy, and it seems to be the most popular
625smart choice.
e7189506 626
146ec120 627=head1 HOW?
628
f5c54951 629There is a third strategy implemented here. Make the database do more of the
630work: hide the nasty bits so we don't have to handle them unless we really want
631to. It'll save us some typing and it'll make for more expressive code. What if
632we could do this:
146ec120 633
634 my $new_investor = $schema->resultset('Investor')->create(
635 name => $args->{name},
636 password => $args->{password},
637 dollars => $args->{dollars},
638 );
5fa55fff 639
e7189506 640And have it Just Work? The user...
641
642 {
643 name => $args->{name},
644 password => $args->{password},
645 }
646
f5c54951 647should be created behind the scenes, and the use of either user or investor
648in your code should require no special handling. Deleting and updating
649$new_investor should also delete or update the user row.
146ec120 650
f5c54951 651It does. User and investor are both views, their concrete tables abstracted
652away behind a set of rules and triggers. You would expect the above DBIC
653create statement to look like this in SQL:
146ec120 654
655 INSERT INTO investor ("name","password","dollars") VALUES (...);
656
657But using MTI, it is really this:
658
659 INSERT INTO _user_table ("username","password") VALUES (...);
660 INSERT INTO _investor_table ("id","dollars") VALUES (currval('_user_table_id_seq',...) );
661
f5c54951 662For deletes, the triggers fire in reverse, to preserve referential integrity
663(foreign key constraints). For instance:
146ec120 664
665 my $investor = $schema->resultset('Investor')->find({id => $args->{id}});
666 $investor->delete;
667
668Becomes:
669
670 DELETE FROM _investor_table WHERE ("id" = ?);
671 DELETE FROM _user_table WHERE ("id" = ?);
672
673
e7189506 674=head1 METHODS
675
676=over
677
678=item new
679
680
f5c54951 681MTI find the parents, if any, of your resultset class and adds them to the
682list of parent_sources for the table.
e7189506 683
684
685=item add_additional_parents
686
687
688Continuing with coffee:
689
690 __PACKAGE__->result_source_instance->add_additional_parents(
691 qw/
692 MyApp::Schema::Result::Beverage
693 MyApp::Schema::Result::Liquid
694 /
695 );
696
697This just lets you manually add additional parents beyond the ones MTI finds.
698
699=item add_additional_parent
700
701 __PACKAGE__->result_source_instance->add_additional_parent(
702 MyApp::Schema::Result::Beverage
703 );
704
705You can also add just one.
706
707=item attach_additional_sources
708
f5c54951 709MTI takes the parents' sources and relationships, creates a new
710DBIx::Class::Table object from them, and registers this as a new, raw, source
711in the schema, e.g.,
e7189506 712
713 use MyApp::Schema;
714
715 print STDERR map { "$_\n" } MyApp::Schema->sources;
716
5fa55fff 717 # Coffee
e7189506 718 # Beverage
719 # Liquid
720 # Sumatra
721 # Raw::Sumatra
146ec120 722
e7189506 723Raw::Sumatra will be used to generate the view.
146ec120 724
e7189506 725=item view_definition
146ec120 726
e7189506 727This takes the raw table and generates the view (and stored procedures) you will use.
146ec120 728
e7189506 729=back
146ec120 730
731=head1 AUTHOR
732
733Matt S. Trout, E<lt>mst@shadowcatsystems.co.ukE<gt>
734
735=head2 CONTRIBUTORS
736
f5c54951 737Amiri Barksdale, E<lt>amiri@metalabel.comE<gt>
738
739=head1 COPYRIGHT
740
741Copyright (c) 2010 the DBIx::Class::ResultSource::MultipleTableInheritance
742L</AUTHOR> and L</CONTRIBUTORS> as listed above.
146ec120 743
744=head1 LICENSE
745
746This library is free software; you can redistribute it and/or modify
747it under the same terms as Perl itself.
748
749=head1 SEE ALSO
750
751L<DBIx::Class>
752L<DBIx::Class::ResultSource>
753
754=cut