Tidy up SYNOPSIS
[catagits/Catalyst-Model-DBIC-Schema.git] / lib / Catalyst / Model / DBIC / Schema.pm
CommitLineData
ad91060a 1package Catalyst::Model::DBIC::Schema;
2
0fbbc8d5 3use Moose;
0f3de2c4 4use mro 'c3';
0fbbc8d5 5extends 'Catalyst::Model';
fb691af9 6with 'CatalystX::Component::Traits';
0fbbc8d5 7
ca7cf6f0 8our $VERSION = '0.30';
7dfd616a 9$VERSION = eval $VERSION;
f090a149 10
73f72d28 11use namespace::autoclean;
bd309c0c 12use Carp::Clan '^Catalyst::Model::DBIC::Schema';
bfcd6e3d 13use Data::Dumper;
2201c2e4 14use DBIx::Class ();
ad91060a 15
61ed82a5 16use Catalyst::Model::DBIC::Schema::Types
2fa0a1f1 17 qw/ConnectInfo LoadedClass SchemaClass/;
61ed82a5 18
41bcf32f 19use MooseX::Types::Moose qw/ArrayRef Str ClassName Undef/;
0fbbc8d5 20
ad91060a 21=head1 NAME
22
23Catalyst::Model::DBIC::Schema - DBIx::Class::Schema Model Class
24
25=head1 SYNOPSIS
26
95b41ca8 27=over 4
aabc1d75 28
29=item 1.
30
95b41ca8 31First, prepare your database schema using C<DBIx::Class>.
aabc1d75 32
95b41ca8 33(If you're not sure how to do this, then read L<DBIx::Class::Manual::Intro> first!)
aabc1d75 34
95b41ca8 35This example assumes that you already have a schema called
36C<MyApp::Schema::FilmDB>,
37which defines some tables in C<MyApp::Schema::FilmDB::Actor> and
38C<MyApp::Schema::FilmDB::Film>.
aabc1d75 39
40=item 2.
41
95b41ca8 42Now, to expose it to Catalyst as a model, you should create a DBIC Model in
43MyApp/Model/FilmDB.pm:
aabc1d75 44
95b41ca8 45=over 8
07edc53e 46
95b41ca8 47You can do this:
07edc53e 48
95b41ca8 49=item * With the helper script
aabc1d75 50
95b41ca8 51 script/create.pl model FilmDB DBIC::Schema MyApp::Schema::FilmDB ...options...
aabc1d75 52
95b41ca8 53See L<Catalyst::Helper::Model::DBIC::Schema> for details.
aabc1d75 54
95b41ca8 55=item * Manually
aabc1d75 56
57 package MyApp::Model::FilmDB;
58 use base qw/Catalyst::Model::DBIC::Schema/;
07edc53e 59
aabc1d75 60 __PACKAGE__->config(
61 schema_class => 'MyApp::Schema::FilmDB',
2201c2e4 62 connect_info => {
63 dsn => "DBI:...",
64 user => "username",
65 password => "password",
66 }
aabc1d75 67 );
68
69See below for a full list of the possible config parameters.
70
71=back
72
95b41ca8 73=back
aabc1d75 74
95b41ca8 75Now you have a working Model which accesses your separate DBIC Schema. This can
76be used/accessed in the normal Catalyst manner, via C<< $c->model() >>:
aabc1d75 77
95b41ca8 78 my $db_model = $c->model('FilmDB'); # a Catalyst::Model
79 my $dbic = $c->model('FilmDB')->schema; # the actual DBIC object
aabc1d75 80
95b41ca8 81The Model proxies the DBIC instance so you can do
07edc53e 82
95b41ca8 83 my $rs = $db_model->resultset('Actor'); # ... or ...
84 my $rs = $dbic ->resultset('Actor'); # same!
07edc53e 85
95b41ca8 86There is also a shortcut, which returns a L<DBIx::Class::ResultSet> directly,
87instead of a L<Catalyst::Model>:
07edc53e 88
95b41ca8 89 my $rs = $c->model('FilmDB::Actor');
aabc1d75 90
95b41ca8 91To find out more about which methods can be called on a ResultSet, or how to
92add your own methods to it, please see the ResultSet documentation in the
93L<DBIx::Class> distribution.
aabc1d75 94
95b41ca8 95=head2 Some examples:
aabc1d75 96
f1613faa 97 # to access schema methods directly:
98 $c->model('FilmDB')->schema->source(...);
99
100 # to access the source object, resultset, and class:
07edc53e 101 $c->model('FilmDB')->source(...);
102 $c->model('FilmDB')->resultset(...);
103 $c->model('FilmDB')->class(...);
c12b7310 104
07edc53e 105 # For resultsets, there's an even quicker shortcut:
106 $c->model('FilmDB::Actor')
107 # is the same as $c->model('FilmDB')->resultset('Actor')
ad91060a 108
f1613faa 109 # To get the composed schema for making new connections:
110 my $newconn = $c->model('FilmDB')->composed_schema->connect(...);
111
112 # Or the same thing via a convenience shortcut:
113 my $newconn = $c->model('FilmDB')->connect(...);
114
115 # or, if your schema works on different storage drivers:
116 my $newconn = $c->model('FilmDB')->composed_schema->clone();
117 $newconn->storage_type('::LDAP');
118 $newconn->connection(...);
119
120 # and again, a convenience shortcut
121 my $newconn = $c->model('FilmDB')->clone();
122 $newconn->storage_type('::LDAP');
123 $newconn->connection(...);
124
95b41ca8 125To set up authentication, see L</"Setting up DBIC authentication"> below.
126
ad91060a 127=head1 DESCRIPTION
128
7b39f3f0 129This is a Catalyst Model for L<DBIx::Class::Schema>-based Models. See
ef91bcf9 130the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
131information on generating these Models via Helper scripts.
ad91060a 132
d52bc376 133When your Catalyst app starts up, a thin Model layer is created as an
134interface to your DBIC Schema. It should be clearly noted that the model
135object returned by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or
136resultset object, but merely a wrapper proving L<methods|/METHODS> to access
137the underlying schema.
138
139In addition to this model class, a shortcut class is generated for each
140source in the schema, allowing easy and direct access to a resultset of the
141corresponding type. These generated classes are even thinner than the model
142class, providing no public methods but simply hooking into Catalyst's
143model() accessor via the
144L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete
145contents of each generated class is roughly equivalent to the following:
146
147 package MyApp::Model::FilmDB::Actor
148 sub ACCEPT_CONTEXT {
149 my ($self, $c) = @_;
150 $c->model('FilmDB')->resultset('Actor');
151 }
152
153In short, there are three techniques available for obtaining a DBIC
154resultset object:
155
156 # the long way
157 my $rs = $c->model('FilmDB')->schema->resultset('Actor');
158
159 # using the shortcut method on the model object
160 my $rs = $c->model('FilmDB')->resultset('Actor');
161
162 # using the generated class directly
163 my $rs = $c->model('FilmDB::Actor');
164
c082639a 165In order to add methods to a DBIC resultset, you cannot simply add them to
166the source (row, table) definition class; you must define a separate custom
167resultset class. See L<DBIx::Class::Manual::Cookbook/"Predefined searches">
168for more info.
169
ad91060a 170=head1 CONFIG PARAMETERS
171
b9cc2f76 172Any options in your config not listed here are passed to your schema.
173
c4fee9b8 174=head2 schema_class
ad91060a 175
176This is the classname of your L<DBIx::Class::Schema> Schema. It needs
aabc1d75 177to be findable in C<@INC>, but it does not need to be inside the
178C<Catalyst::Model::> namespace. This parameter is required.
ad91060a 179
c4fee9b8 180=head2 connect_info
ad91060a 181
182This is an arrayref of connection parameters, which are specific to your
b9a72351 183C<storage_type> (see your storage type documentation for more details).
184If you only need one parameter (e.g. the DSN), you can just pass a string
185instead of an arrayref.
ad91060a 186
0f2fd2c0 187This is not required if C<schema_class> already has connection information
d89e6c8a 188defined inside itself (which isn't highly recommended, but can be done)
0f2fd2c0 189
7db6da78 190For L<DBIx::Class::Storage::DBI>, which is the only supported
191C<storage_type> in L<DBIx::Class> at the time of this writing, the
192parameters are your dsn, username, password, and connect options hashref.
193
018eb0e2 194See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
195of the arguments supported.
7db6da78 196
197Examples:
198
2201c2e4 199 connect_info => {
200 dsn => 'dbi:Pg:dbname=mypgdb',
201 user => 'postgres',
202 password => ''
203 }
07edc53e 204
2201c2e4 205 connect_info => {
206 dsn => 'dbi:SQLite:dbname=foo.db',
207 on_connect_do => [
208 'PRAGMA synchronous = OFF',
209 ]
210 }
07edc53e 211
2201c2e4 212 connect_info => {
213 dsn => 'dbi:Pg:dbname=mypgdb',
214 user => 'postgres',
215 password => '',
216 pg_enable_utf8 => 1,
217 on_connect_do => [
218 'some SQL statement',
219 'another SQL statement',
220 ],
221 }
7db6da78 222
8281c933 223Or using L<Config::General>:
224
225 <Model::FilmDB>
226 schema_class MyApp::Schema::FilmDB
c34bcab6 227 traits Caching
8281c933 228 <connect_info>
2201c2e4 229 dsn dbi:Pg:dbname=mypgdb
230 user postgres
42e14c31 231 password ""
2201c2e4 232 auto_savepoint 1
a75b6e58 233 quote_char """
8281c933 234 on_connect_do some SQL statement
235 on_connect_do another SQL statement
236 </connect_info>
b9cc2f76 237 user_defined_schema_accessor foo
8281c933 238 </Model::FilmDB>
239
240or
241
242 <Model::FilmDB>
243 schema_class MyApp::Schema::FilmDB
244 connect_info dbi:SQLite:dbname=foo.db
245 </Model::FilmDB>
246
2201c2e4 247Or using L<YAML>:
248
249 Model::MyDB:
250 schema_class: MyDB
b9cc2f76 251 traits: Caching
2201c2e4 252 connect_info:
253 dsn: dbi:Oracle:mydb
254 user: mtfnpy
255 password: mypass
256 LongReadLen: 1000000
257 LongTruncOk: 1
b9cc2f76 258 on_connect_call: 'datetime_setup'
a75b6e58 259 quote_char: '"'
2201c2e4 260
261The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
262supported:
263
264 connect_info => [
265 'dbi:Pg:dbname=mypgdb',
266 'postgres',
267 '',
268 {
269 pg_enable_utf8 => 1,
270 },
271 {
0fbbc8d5 272 auto_savepoint => 1,
2201c2e4 273 on_connect_do => [
274 'some SQL statement',
275 'another SQL statement',
276 ],
277 }
278 ]
279
c34bcab6 280=head2 traits
0fbbc8d5 281
41bcf32f 282Array of Traits to apply to the instance. Traits are L<Moose::Role>s.
283
fb691af9 284They are relative to the C<< MyApp::TraitFor::Model::DBIC::Schema:: >>, then the C<<
285Catalyst::TraitFor::Model::DBIC::Schema:: >> namespaces, unless prefixed with C<+>
41bcf32f 286in which case they are taken to be a fully qualified name. E.g.:
2201c2e4 287
c34bcab6 288 traits Caching
fb691af9 289 traits +MyApp::TraitFor::Model::Foo
2201c2e4 290
0fbbc8d5 291A new instance is created at application time, so any consumed required
292attributes, coercions and modifiers will work.
2201c2e4 293
fb691af9 294Traits are applied at L<Catalyst::Component/COMPONENT> time using
295L<CatalystX::Component::Traits>.
0fbbc8d5 296
41bcf32f 297C<ref $self> will be an anon class if any traits are applied, C<<
298$self->_original_class_name >> will be the original class.
f090a149 299
c7d7b849 300When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
301L</ACCEPT_CONTEXT>.
0fbbc8d5 302
c34bcab6 303Traits that come with the distribution:
0fbbc8d5 304
305=over 4
2201c2e4 306
fb691af9 307=item L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>
0fbbc8d5 308
fb691af9 309=item L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
c4fee9b8 310
0fbbc8d5 311=back
8281c933 312
c4fee9b8 313=head2 storage_type
ad91060a 314
315Allows the use of a different C<storage_type> than what is set in your
316C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 317L<DBIx::Class>). Completely optional, and probably unnecessary for most
318people until other storage backends become available for L<DBIx::Class>.
ad91060a 319
c7d7b849 320=head1 ATTRIBUTES
321
322The keys you pass in the model configuration are available as attributes.
323
324Other attributes available:
325
326=head2 connect_info
327
328Your connect_info args normalized to hashref form (with dsn/user/password.) See
329L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
330L</connect_info>.
331
332=head2 model_name
333
334The model name L<Catalyst> uses to resolve this model, the part after
335C<::Model::> or C<::M::> in your class name. E.g. if your class name is
336C<MyApp::Model::DB> the L</model_name> will be C<DB>.
337
c7d7b849 338=head2 _default_cursor_class
339
6f6b9c2d 340What to reset your L<DBIx::Class::Storage::DBI/cursor_class> to if a custom one
c7d7b849 341doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
342
fb691af9 343=head1 ATTRIBUTES FROM L<MooseX::Traits::Pluggable>
344
345=head2 _original_class_name
346
347The class name of your model before any L</traits> are applied. E.g.
348C<MyApp::Model::DB>.
349
c7d7b849 350=head2 _traits
351
6f6b9c2d 352Unresolved arrayref of traits passed in the config.
c7d7b849 353
354=head2 _resolved_traits
355
356Traits you used resolved to full class names.
357
ad91060a 358=head1 METHODS
359
46a2eb0c 360Methods not listed here are delegated to the connected schema used by the model
361instance, so the following are equivalent:
362
363 $c->model('DB')->schema->my_accessor('foo');
364 # or
365 $c->model('DB')->my_accessor('foo');
366
367Methods on the model take precedence over schema methods.
368
c4fee9b8 369=head2 new
ad91060a 370
371Instantiates the Model based on the above-documented ->config parameters.
0f2fd2c0 372The only required parameter is C<schema_class>. C<connect_info> is
373required in the case that C<schema_class> does not already have connection
374information defined for it.
ad91060a 375
c4fee9b8 376=head2 schema
f1613faa 377
378Accessor which returns the connected schema being used by the this model.
379There are direct shortcuts on the model class itself for
380schema->resultset, schema->source, and schema->class.
381
c4fee9b8 382=head2 composed_schema
f1613faa 383
384Accessor which returns the composed schema, which has no connection info,
385which was used in constructing the C<schema> above. Useful for creating
386new connections based on the same schema/model. There are direct shortcuts
387from the model object for composed_schema->clone and composed_schema->connect
388
c4fee9b8 389=head2 clone
f1613faa 390
391Shortcut for ->composed_schema->clone
392
c4fee9b8 393=head2 connect
f1613faa 394
395Shortcut for ->composed_schema->connect
396
c4fee9b8 397=head2 source
c12b7310 398
f1613faa 399Shortcut for ->schema->source
400
c4fee9b8 401=head2 class
f1613faa 402
403Shortcut for ->schema->class
404
c4fee9b8 405=head2 resultset
f1613faa 406
407Shortcut for ->schema->resultset
408
c4fee9b8 409=head2 storage
f1613faa 410
411Provides an accessor for the connected schema's storage object.
412Used often for debugging and controlling transactions.
b8427e0b 413
ad91060a 414=cut
415
c34bcab6 416has schema_class => (
0fbbc8d5 417 is => 'ro',
2fa0a1f1 418 isa => SchemaClass,
0fbbc8d5 419 coerce => 1,
420 required => 1
421);
422
c34bcab6 423has storage_type => (is => 'rw', isa => Str);
0fbbc8d5 424
18b829f0 425has connect_info => (is => 'rw', isa => ConnectInfo, coerce => 1);
0fbbc8d5 426
c7d7b849 427has model_name => (
428 is => 'ro',
429 isa => Str,
430 required => 1,
431 lazy_build => 1,
432);
ad91060a 433
c34bcab6 434has _default_cursor_class => (
61ed82a5 435 is => 'ro',
7314403a 436 isa => LoadedClass,
61ed82a5 437 default => 'DBIx::Class::Storage::DBI::Cursor',
438 coerce => 1
439);
440
0fbbc8d5 441sub BUILD {
f27a05ea 442 my ($self, $args) = @_;
f2488839 443 my $class = $self->_original_class_name;
2201c2e4 444 my $schema_class = $self->schema_class;
ad91060a 445
2201c2e4 446 if( !$self->connect_info ) {
f1613faa 447 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 448 $self->connect_info($schema_class->storage->connect_info);
f1613faa 449 }
450 else {
39f5f008 451 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 452 . " or $schema_class must have connect info defined on it."
453 . " Here's what we got:\n"
f1613faa 454 . Dumper($self);
455 }
7db6da78 456 }
457
0fbbc8d5 458 if (exists $self->connect_info->{cursor_class}) {
459 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
460 or croak "invalid connect_info: Cannot load your cursor_class"
461 . " ".$self->connect_info->{cursor_class}.": $@";
462 }
463
0fbbc8d5 464 $self->setup;
465
f1613faa 466 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 467
7bd33abf 468 $self->meta->make_mutable;
46a2eb0c 469 $self->meta->add_attribute('schema',
470 is => 'rw',
180c1a1a 471 isa => 'DBIx::Class::Schema',
46a2eb0c 472 handles => $self->_delegates
473 );
7bd33abf 474 $self->meta->make_immutable;
46a2eb0c 475
f1613faa 476 $self->schema($self->composed_schema->clone);
477
f27a05ea 478 $self->_pass_options_to_schema($args);
7bd33abf 479
2201c2e4 480 $self->schema->storage_type($self->storage_type)
481 if $self->storage_type;
7db6da78 482
2201c2e4 483 $self->schema->connection($self->connect_info);
484
485 $self->_install_rs_models;
2201c2e4 486}
487
488sub clone { shift->composed_schema->clone(@_); }
489
490sub connect { shift->composed_schema->connect(@_); }
491
c4fee9b8 492=head2 setup
2201c2e4 493
e203cd42 494Called at C<BUILD> time before configuration, but after L</connect_info> is
c7d7b849 495set. To do something after configuuration use C<< after BUILD => >>.
2201c2e4 496
497=cut
498
0fbbc8d5 499sub setup { 1 }
2201c2e4 500
c4fee9b8 501=head2 ACCEPT_CONTEXT
2201c2e4 502
73f72d28 503Point of extension for doing things at C<< $c->model >> time with context,
504returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
505more information.
2201c2e4 506
0fbbc8d5 507=cut
2201c2e4 508
0fbbc8d5 509sub ACCEPT_CONTEXT { shift }
2201c2e4 510
511sub _install_rs_models {
512 my $self = shift;
7b1fe8c2 513 my $class = $self->_original_class_name;
2201c2e4 514
ad91060a 515 no strict 'refs';
39f5f008 516
517 my @sources = $self->schema->sources;
518
ca7cf6f0 519 unless (@sources) {
520 warn <<'EOF' unless $ENV{CMDS_NO_SOURCES};
521******************************* WARNING ***************************************
522* No sources found (did you forget to define your tables?) *
523* *
524* To turn off this warning, set the CMDS_NO_SOURCES environment variable. *
525*******************************************************************************
526EOF
527 }
39f5f008 528
529 foreach my $moniker (@sources) {
0b2a7108 530 my $classname = "${class}::$moniker";
7db6da78 531 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 532 shift;
2201c2e4 533 shift->model($self->model_name)->resultset($moniker);
ad91060a 534 }
535 }
2201c2e4 536}
ad91060a 537
61ed82a5 538sub _reset_cursor_class {
539 my $self = shift;
540
541 if ($self->storage->can('cursor_class')) {
542 $self->storage->cursor_class($self->_default_cursor_class)
543 if $self->storage->cursor_class ne $self->_default_cursor_class;
544 }
545}
546
50f488ec 547{
548 my %COMPOSED_CACHE;
549
550 sub composed_schema {
551 my $self = shift;
552 my $class = $self->_original_class_name;
553 my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
554
555 $$store = shift if @_;
556
557 return $$store
558 }
559}
560
c7d7b849 561sub _build_model_name {
562 my $self = shift;
563 my $class = $self->_original_class_name;
564 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
565
566 return $model_name;
41bcf32f 567}
568
180c1a1a 569sub _delegates {
46a2eb0c 570 my $self = shift;
571
b9cc2f76 572 my $schema_meta = Class::MOP::Class->initialize($self->schema_class);
573 my @schema_methods = $schema_meta->get_all_method_names;
46a2eb0c 574
180c1a1a 575# combine with any already added by other schemas
576 my @handles = eval {
577 @{ $self->meta->find_attribute_by_name('schema')->handles }
578 };
579
580# now kill the attribute, otherwise add_attribute in BUILD will not do the right
b9cc2f76 581# thing (it clears the handles for some reason.) May be a Moose bug.
180c1a1a 582 eval { $self->meta->remove_attribute('schema') };
583
584 my %schema_methods;
585 @schema_methods{ @schema_methods, @handles } = ();
586 @schema_methods = keys %schema_methods;
587
588 my @my_methods = $self->meta->get_all_method_names;
46a2eb0c 589 my %my_methods;
590 @my_methods{@my_methods} = ();
591
592 my @delegates;
593 for my $method (@schema_methods) {
594 push @delegates, $method unless exists $my_methods{$method};
595 }
596
597 return \@delegates;
598}
599
7bd33abf 600sub _pass_options_to_schema {
f27a05ea 601 my ($self, $args) = @_;
602
603 my @attributes = map {
604 $_->init_arg || ()
605 } $self->meta->get_all_attributes;
7bd33abf 606
7bd33abf 607 my %attributes;
608 @attributes{@attributes} = ();
609
f27a05ea 610 for my $opt (keys %$args) {
7bd33abf 611 if (not exists $attributes{$opt}) {
294245b0 612 next unless $self->schema->can($opt);
7bd33abf 613 $self->schema->$opt($self->{$opt});
614 }
615 }
616}
617
0fbbc8d5 618__PACKAGE__->meta->make_immutable;
2201c2e4 619
ca7cf6f0 620=head1 ENVIRONMENT
621
622=over 4
623
624=item CMDS_NO_SOURCES
625
626Set this variable if you will be using schemas with no sources (tables) to
627disable the warning. The warning is there because this is usually a mistake.
628
629=back
630
95b41ca8 631=head1 Setting up DBIC authentication
632
633You can set this up with
634L<Catalyst::Authentication::Store::DBIx::Class> in MyApp.pm:
635
636 package MyApp;
637
638 use Catalyst qw/... Authentication .../;
639
640 ...
641
642 __PACKAGE__->config->{authentication} =
643 {
644 default_realm => 'members',
645 realms => {
646 members => {
647 credential => {
648 class => 'Password',
649 password_field => 'password',
650 password_type => 'hashed'
651 password_hash_type => 'SHA-256'
652 },
653 store => {
654 class => 'DBIx::Class',
655 user_model => 'DB::User',
656 role_relation => 'roles',
657 role_field => 'rolename',
658 }
659 }
660 }
661 };
662
ad91060a 663=head1 SEE ALSO
664
7b39f3f0 665General Catalyst Stuff:
666
667L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
668L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
669
670Stuff related to DBIC and this Model style:
671
672L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 673L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
e203cd42 674L<CatalystX::Component::Traits>, L<MooseX::Traits::Pluggable>
ad91060a 675
c34bcab6 676Traits:
c4fee9b8 677
fb691af9 678L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>,
679L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
c4fee9b8 680
ad91060a 681=head1 AUTHOR
682
e203cd42 683Brandon L Black C<blblack at gmail.com>
ad91060a 684
e203cd42 685=head1 CONTRIBUTORS
2ff00e2b 686
e203cd42 687caelum: Rafael Kitover C<rkitover at cpan.org>
2ff00e2b 688
6d9e2623 689Dan Dascalescu C<dandv at cpan.org>
690
49c75c04 691Aran Deltac C<bluefeet@cpan.org>
692
ad91060a 693=head1 COPYRIGHT
694
6d9e2623 695This program is free software. You can redistribute it and/or modify it
ad91060a 696under the same terms as Perl itself.
697
698=cut
699
7001;
c7d7b849 701# vim:sts=4 sw=4 et: