add test for on_connect_do=string parsing in helper, release
[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
51062974 8our $VERSION = '0.40';
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
d816d7bf 17 qw/ConnectInfo LoadedClass SchemaClass Schema/;
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
cbe03ea7 27First, prepare your database schema using L<DBIx::Class>, see
28L<Catalyst::Helper::Model::DBIC::Schema> for how to generate a
29L<DBIx::Class::Schema> from your database using the Helper script, and
30L<DBIx::Class::Schema::Loader::Base>.
07edc53e 31
cbe03ea7 32A typical usage of the helper script would be:
aabc1d75 33
cbe03ea7 34 script/myapp_create.pl model FilmDB DBIC::Schema MyApp::Schema::FilmDB \
35 create=static dbi:mysql:filmdb dbusername dbpass \
36 quote_char='`' name_sep='.'
aabc1d75 37
cbe03ea7 38If you are unfamiliar with L<DBIx::Class>, see L<DBIx::Class::Manual::Intro>
39first.
aabc1d75 40
cbe03ea7 41These examples assume that you already have a schema called
42C<MyApp::Schema::FilmDB>, which defines some Result classes for tables in
43C<MyApp::Schema::FilmDB::Result::Actor> and
44C<MyApp::Schema::FilmDB::Result::Film>. Either created by the helper script (as
45shown above) or manually.
aabc1d75 46
cbe03ea7 47The helper also creates a Model in C<lib/MyApp/Model/FilmDB.pm>, if you already
48have a schema you can create just the Model using:
07edc53e 49
cbe03ea7 50 script/myapp_create.pl model FilmDB DBIC::Schema MyApp::Schema::FilmDB
51 dbi:mysql:filmdb dbusername dbpass
aabc1d75 52
cbe03ea7 53The connect_info is optional and will be hardcoded into the Model if provided.
54It's better to configure it in your L<Catalyst> config file, which will also
55override any hardcoded config, see L</connect_info> for examples.
aabc1d75 56
95b41ca8 57Now you have a working Model which accesses your separate DBIC Schema. This can
58be used/accessed in the normal Catalyst manner, via C<< $c->model() >>:
aabc1d75 59
95b41ca8 60 my $db_model = $c->model('FilmDB'); # a Catalyst::Model
61 my $dbic = $c->model('FilmDB')->schema; # the actual DBIC object
aabc1d75 62
95b41ca8 63There is also a shortcut, which returns a L<DBIx::Class::ResultSet> directly,
64instead of a L<Catalyst::Model>:
07edc53e 65
95b41ca8 66 my $rs = $c->model('FilmDB::Actor');
aabc1d75 67
cbe03ea7 68See L<DBIx::Class::ResultSet> to find out more about which methods can be
69called on ResultSets.
70
71You can also define your own ResultSet methods to encapsulate the
72database/business logic of your applications. These go into, for example,
73C<lib/MyApp/Schema/FilmDB/ResultSet/Actor.pm>. The class must inherit from
74L<DBIx::Class::ResultSet> and is automatically loaded.
75
76Then call your methods like any other L<DBIx::Class::ResultSet> method:
77
78 $c->model('FilmDB::Actor')->SAG_members
aabc1d75 79
95b41ca8 80=head2 Some examples:
aabc1d75 81
f1613faa 82 # to access schema methods directly:
83 $c->model('FilmDB')->schema->source(...);
84
85 # to access the source object, resultset, and class:
07edc53e 86 $c->model('FilmDB')->source(...);
87 $c->model('FilmDB')->resultset(...);
88 $c->model('FilmDB')->class(...);
c12b7310 89
07edc53e 90 # For resultsets, there's an even quicker shortcut:
91 $c->model('FilmDB::Actor')
92 # is the same as $c->model('FilmDB')->resultset('Actor')
ad91060a 93
f1613faa 94 # To get the composed schema for making new connections:
95 my $newconn = $c->model('FilmDB')->composed_schema->connect(...);
96
97 # Or the same thing via a convenience shortcut:
98 my $newconn = $c->model('FilmDB')->connect(...);
99
100 # or, if your schema works on different storage drivers:
101 my $newconn = $c->model('FilmDB')->composed_schema->clone();
102 $newconn->storage_type('::LDAP');
103 $newconn->connection(...);
104
105 # and again, a convenience shortcut
106 my $newconn = $c->model('FilmDB')->clone();
107 $newconn->storage_type('::LDAP');
108 $newconn->connection(...);
109
95b41ca8 110To set up authentication, see L</"Setting up DBIC authentication"> below.
111
ad91060a 112=head1 DESCRIPTION
113
7b39f3f0 114This is a Catalyst Model for L<DBIx::Class::Schema>-based Models. See
ef91bcf9 115the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
116information on generating these Models via Helper scripts.
ad91060a 117
cbe03ea7 118When your Catalyst app starts up, a thin Model layer is created as an interface
119to your DBIC Schema. It should be clearly noted that the model object returned
120by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or resultset object,
121but merely a wrapper proving L<methods|/METHODS> to access the underlying
ae3d05c2 122schema.
d52bc376 123
124In addition to this model class, a shortcut class is generated for each
125source in the schema, allowing easy and direct access to a resultset of the
126corresponding type. These generated classes are even thinner than the model
127class, providing no public methods but simply hooking into Catalyst's
128model() accessor via the
129L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete
130contents of each generated class is roughly equivalent to the following:
131
132 package MyApp::Model::FilmDB::Actor
133 sub ACCEPT_CONTEXT {
134 my ($self, $c) = @_;
135 $c->model('FilmDB')->resultset('Actor');
136 }
137
138In short, there are three techniques available for obtaining a DBIC
139resultset object:
140
141 # the long way
142 my $rs = $c->model('FilmDB')->schema->resultset('Actor');
143
144 # using the shortcut method on the model object
145 my $rs = $c->model('FilmDB')->resultset('Actor');
146
147 # using the generated class directly
148 my $rs = $c->model('FilmDB::Actor');
149
c082639a 150In order to add methods to a DBIC resultset, you cannot simply add them to
151the source (row, table) definition class; you must define a separate custom
cbe03ea7 152resultset class. This is just a matter of making a
153C<lib/MyApp/Schema/ResultSet/Actor.pm> class that inherits from
154L<DBIx::Class::ResultSet>, if you are using
155L<DBIx::Class::Schema/load_namespaces>, the default for helper script generated
156schemas.
157
158See L<DBIx::Class::Manual::Cookbook/"Predefined searches">
159for information on definining your own L<DBIx::Class::ResultSet> classes for
160use with L<DBIx::Class::Schema/load_classes>, the old default.
c082639a 161
ad91060a 162=head1 CONFIG PARAMETERS
163
c4fee9b8 164=head2 schema_class
ad91060a 165
166This is the classname of your L<DBIx::Class::Schema> Schema. It needs
aabc1d75 167to be findable in C<@INC>, but it does not need to be inside the
168C<Catalyst::Model::> namespace. This parameter is required.
ad91060a 169
c4fee9b8 170=head2 connect_info
ad91060a 171
172This is an arrayref of connection parameters, which are specific to your
b9a72351 173C<storage_type> (see your storage type documentation for more details).
174If you only need one parameter (e.g. the DSN), you can just pass a string
175instead of an arrayref.
ad91060a 176
0f2fd2c0 177This is not required if C<schema_class> already has connection information
d89e6c8a 178defined inside itself (which isn't highly recommended, but can be done)
0f2fd2c0 179
7db6da78 180For L<DBIx::Class::Storage::DBI>, which is the only supported
181C<storage_type> in L<DBIx::Class> at the time of this writing, the
182parameters are your dsn, username, password, and connect options hashref.
183
018eb0e2 184See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
185of the arguments supported.
7db6da78 186
187Examples:
188
2201c2e4 189 connect_info => {
190 dsn => 'dbi:Pg:dbname=mypgdb',
191 user => 'postgres',
192 password => ''
193 }
07edc53e 194
2201c2e4 195 connect_info => {
196 dsn => 'dbi:SQLite:dbname=foo.db',
197 on_connect_do => [
198 'PRAGMA synchronous = OFF',
199 ]
200 }
07edc53e 201
2201c2e4 202 connect_info => {
203 dsn => 'dbi:Pg:dbname=mypgdb',
204 user => 'postgres',
205 password => '',
206 pg_enable_utf8 => 1,
207 on_connect_do => [
208 'some SQL statement',
209 'another SQL statement',
210 ],
211 }
7db6da78 212
8281c933 213Or using L<Config::General>:
214
215 <Model::FilmDB>
216 schema_class MyApp::Schema::FilmDB
c34bcab6 217 traits Caching
8281c933 218 <connect_info>
2201c2e4 219 dsn dbi:Pg:dbname=mypgdb
220 user postgres
42e14c31 221 password ""
2201c2e4 222 auto_savepoint 1
a75b6e58 223 quote_char """
8281c933 224 on_connect_do some SQL statement
225 on_connect_do another SQL statement
226 </connect_info>
b9cc2f76 227 user_defined_schema_accessor foo
8281c933 228 </Model::FilmDB>
229
230or
231
232 <Model::FilmDB>
233 schema_class MyApp::Schema::FilmDB
234 connect_info dbi:SQLite:dbname=foo.db
235 </Model::FilmDB>
236
2201c2e4 237Or using L<YAML>:
238
239 Model::MyDB:
240 schema_class: MyDB
b9cc2f76 241 traits: Caching
2201c2e4 242 connect_info:
243 dsn: dbi:Oracle:mydb
244 user: mtfnpy
245 password: mypass
246 LongReadLen: 1000000
247 LongTruncOk: 1
b9cc2f76 248 on_connect_call: 'datetime_setup'
a75b6e58 249 quote_char: '"'
2201c2e4 250
251The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
252supported:
253
254 connect_info => [
255 'dbi:Pg:dbname=mypgdb',
256 'postgres',
257 '',
258 {
259 pg_enable_utf8 => 1,
260 },
261 {
0fbbc8d5 262 auto_savepoint => 1,
2201c2e4 263 on_connect_do => [
264 'some SQL statement',
265 'another SQL statement',
266 ],
267 }
268 ]
269
c34bcab6 270=head2 traits
0fbbc8d5 271
41bcf32f 272Array of Traits to apply to the instance. Traits are L<Moose::Role>s.
273
d816d7bf 274They are relative to the C<< MyApp::TraitFor::Model::DBIC::Schema:: >>, then
275the C<< Catalyst::TraitFor::Model::DBIC::Schema:: >> namespaces, unless
276prefixed with C<+> in which case they are taken to be a fully qualified name.
277E.g.:
2201c2e4 278
c34bcab6 279 traits Caching
fb691af9 280 traits +MyApp::TraitFor::Model::Foo
2201c2e4 281
0fbbc8d5 282A new instance is created at application time, so any consumed required
283attributes, coercions and modifiers will work.
2201c2e4 284
fb691af9 285Traits are applied at L<Catalyst::Component/COMPONENT> time using
286L<CatalystX::Component::Traits>.
0fbbc8d5 287
41bcf32f 288C<ref $self> will be an anon class if any traits are applied, C<<
289$self->_original_class_name >> will be the original class.
f090a149 290
c7d7b849 291When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
292L</ACCEPT_CONTEXT>.
0fbbc8d5 293
c34bcab6 294Traits that come with the distribution:
0fbbc8d5 295
296=over 4
2201c2e4 297
fb691af9 298=item L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>
0fbbc8d5 299
fb691af9 300=item L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
c4fee9b8 301
d816d7bf 302=item L<Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy>
303
0fbbc8d5 304=back
8281c933 305
c4fee9b8 306=head2 storage_type
ad91060a 307
308Allows the use of a different C<storage_type> than what is set in your
309C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 310L<DBIx::Class>). Completely optional, and probably unnecessary for most
311people until other storage backends become available for L<DBIx::Class>.
ad91060a 312
c7d7b849 313=head1 ATTRIBUTES
314
315The keys you pass in the model configuration are available as attributes.
316
317Other attributes available:
318
319=head2 connect_info
320
321Your connect_info args normalized to hashref form (with dsn/user/password.) See
322L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
323L</connect_info>.
324
325=head2 model_name
326
327The model name L<Catalyst> uses to resolve this model, the part after
328C<::Model::> or C<::M::> in your class name. E.g. if your class name is
329C<MyApp::Model::DB> the L</model_name> will be C<DB>.
330
c7d7b849 331=head2 _default_cursor_class
332
6f6b9c2d 333What to reset your L<DBIx::Class::Storage::DBI/cursor_class> to if a custom one
c7d7b849 334doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
335
fb691af9 336=head1 ATTRIBUTES FROM L<MooseX::Traits::Pluggable>
337
338=head2 _original_class_name
339
340The class name of your model before any L</traits> are applied. E.g.
341C<MyApp::Model::DB>.
342
c7d7b849 343=head2 _traits
344
6f6b9c2d 345Unresolved arrayref of traits passed in the config.
c7d7b849 346
347=head2 _resolved_traits
348
349Traits you used resolved to full class names.
350
ad91060a 351=head1 METHODS
352
c4fee9b8 353=head2 new
ad91060a 354
355Instantiates the Model based on the above-documented ->config parameters.
0f2fd2c0 356The only required parameter is C<schema_class>. C<connect_info> is
357required in the case that C<schema_class> does not already have connection
358information defined for it.
ad91060a 359
c4fee9b8 360=head2 schema
f1613faa 361
362Accessor which returns the connected schema being used by the this model.
363There are direct shortcuts on the model class itself for
364schema->resultset, schema->source, and schema->class.
365
c4fee9b8 366=head2 composed_schema
f1613faa 367
368Accessor which returns the composed schema, which has no connection info,
369which was used in constructing the C<schema> above. Useful for creating
370new connections based on the same schema/model. There are direct shortcuts
371from the model object for composed_schema->clone and composed_schema->connect
372
c4fee9b8 373=head2 clone
f1613faa 374
375Shortcut for ->composed_schema->clone
376
c4fee9b8 377=head2 connect
f1613faa 378
379Shortcut for ->composed_schema->connect
380
c4fee9b8 381=head2 source
c12b7310 382
f1613faa 383Shortcut for ->schema->source
384
c4fee9b8 385=head2 class
f1613faa 386
387Shortcut for ->schema->class
388
c4fee9b8 389=head2 resultset
f1613faa 390
391Shortcut for ->schema->resultset
392
d816d7bf 393=head2 txn_do
394
395Shortcut for ->schema->txn_do
396
397=head2 txn_scope_guard
398
399Shortcut for ->schema->txn_scope_guard
400
c4fee9b8 401=head2 storage
f1613faa 402
403Provides an accessor for the connected schema's storage object.
404Used often for debugging and controlling transactions.
b8427e0b 405
ad91060a 406=cut
407
c34bcab6 408has schema_class => (
0fbbc8d5 409 is => 'ro',
2fa0a1f1 410 isa => SchemaClass,
0fbbc8d5 411 coerce => 1,
412 required => 1
413);
414
c34bcab6 415has storage_type => (is => 'rw', isa => Str);
0fbbc8d5 416
18b829f0 417has connect_info => (is => 'rw', isa => ConnectInfo, coerce => 1);
0fbbc8d5 418
c7d7b849 419has model_name => (
420 is => 'ro',
421 isa => Str,
422 required => 1,
423 lazy_build => 1,
424);
ad91060a 425
c34bcab6 426has _default_cursor_class => (
61ed82a5 427 is => 'ro',
7314403a 428 isa => LoadedClass,
61ed82a5 429 default => 'DBIx::Class::Storage::DBI::Cursor',
430 coerce => 1
431);
432
d816d7bf 433has schema => (is => 'rw', isa => Schema);
434
0fbbc8d5 435sub BUILD {
f27a05ea 436 my ($self, $args) = @_;
f2488839 437 my $class = $self->_original_class_name;
2201c2e4 438 my $schema_class = $self->schema_class;
ad91060a 439
2201c2e4 440 if( !$self->connect_info ) {
f1613faa 441 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 442 $self->connect_info($schema_class->storage->connect_info);
f1613faa 443 }
444 else {
39f5f008 445 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 446 . " or $schema_class must have connect info defined on it."
447 . " Here's what we got:\n"
ae3d05c2 448 . Dumper($args);
f1613faa 449 }
7db6da78 450 }
451
0fbbc8d5 452 if (exists $self->connect_info->{cursor_class}) {
453 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
454 or croak "invalid connect_info: Cannot load your cursor_class"
455 . " ".$self->connect_info->{cursor_class}.": $@";
456 }
457
d816d7bf 458 $self->setup($args);
0fbbc8d5 459
d816d7bf 460 my $is_installed = defined $self->composed_schema;
2201c2e4 461
d816d7bf 462 $self->composed_schema($schema_class->compose_namespace($class))
463 unless $is_installed;
46a2eb0c 464
ae3d05c2 465 $self->schema($self->composed_schema->clone)
466 unless $self->schema;
f1613faa 467
2201c2e4 468 $self->schema->storage_type($self->storage_type)
469 if $self->storage_type;
7db6da78 470
2201c2e4 471 $self->schema->connection($self->connect_info);
472
d816d7bf 473 $self->_install_rs_models unless $is_installed;
2201c2e4 474}
475
476sub clone { shift->composed_schema->clone(@_); }
477
478sub connect { shift->composed_schema->connect(@_); }
479
ae3d05c2 480# some proxy methods, see also SchemaProxy
d816d7bf 481
482sub resultset { shift->schema->resultset(@_); }
483
484sub txn_do { shift->schema->txn_do(@_); }
485
486sub txn_scope_guard { shift->schema->txn_scope_guard(@_); }
487
1d67a585 488sub storage { shift->schema->storage(@_); }
489
c4fee9b8 490=head2 setup
2201c2e4 491
e203cd42 492Called at C<BUILD> time before configuration, but after L</connect_info> is
c7d7b849 493set. To do something after configuuration use C<< after BUILD => >>.
2201c2e4 494
d816d7bf 495Receives a hashref of args passed to C<BUILD>.
496
2201c2e4 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
0fbbc8d5 569__PACKAGE__->meta->make_immutable;
2201c2e4 570
ca7cf6f0 571=head1 ENVIRONMENT
572
573=over 4
574
575=item CMDS_NO_SOURCES
576
cbe03ea7 577Set this variable if you will be using schemas with no sources (Result classes)
578to disable the warning. The warning is there because having no Result classes
579is usually a mistake.
ca7cf6f0 580
581=back
582
95b41ca8 583=head1 Setting up DBIC authentication
584
585You can set this up with
586L<Catalyst::Authentication::Store::DBIx::Class> in MyApp.pm:
587
588 package MyApp;
589
590 use Catalyst qw/... Authentication .../;
591
592 ...
593
10c73a30 594 __PACKAGE__->config('Plugin::Authentication' =>
595 {
95b41ca8 596 default_realm => 'members',
10c73a30 597 members => {
598 credential => {
599 class => 'Password',
600 password_field => 'password',
601 password_type => 'hashed'
602 password_hash_type => 'SHA-256'
603 },
604 store => {
605 class => 'DBIx::Class',
606 user_model => 'DB::User',
607 role_relation => 'roles',
608 role_field => 'rolename',
95b41ca8 609 }
610 }
10c73a30 611 });
95b41ca8 612
ae3d05c2 613=head1 METHOD PROXYING
614
615The automatic proxying to the underlying L<DBIx::Class::Schema> has been
616removed as of version C<0.34>, to enable this feature add C<SchemaProxy> to
617L</traits>.
618
619See L<Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy>.
620
ad91060a 621=head1 SEE ALSO
622
7b39f3f0 623General Catalyst Stuff:
624
625L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
626L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
627
628Stuff related to DBIC and this Model style:
629
630L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 631L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
e203cd42 632L<CatalystX::Component::Traits>, L<MooseX::Traits::Pluggable>
ad91060a 633
c34bcab6 634Traits:
c4fee9b8 635
fb691af9 636L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>,
cbe03ea7 637L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>,
d816d7bf 638L<Catalyst::TraitFor::Model::DBIC::Schema::SchemaProxy>,
cbe03ea7 639L<Catalyst::TraitFor::Model::DBIC::Schema::QueryLog>
c4fee9b8 640
ad91060a 641=head1 AUTHOR
642
e203cd42 643Brandon L Black C<blblack at gmail.com>
ad91060a 644
e203cd42 645=head1 CONTRIBUTORS
2ff00e2b 646
e203cd42 647caelum: Rafael Kitover C<rkitover at cpan.org>
2ff00e2b 648
4e251d1a 649dandv: Dan Dascalescu C<dandv at cpan.org>
6d9e2623 650
4e251d1a 651bluefeet: Aran Deltac C<bluefeet@cpan.org>
652
653t0m: Tomas Doran C<bobtfish@bobtfish.net>
654
655osfameron: C<osfameron@cpan.org>
49c75c04 656
cbe03ea7 657ozum: Ozum Eldogan C<ozum@ozum.net>
ce9e19dc 658
87145c6c 659Pavel I. Shaydo C<zwon@trinitum.org>
660
ad91060a 661=head1 COPYRIGHT
662
4e251d1a 663Copyright (c) 2006 - 2009
664the Catalyst::Model::DBIC::Schema L</AUTHOR> and L</CONTRIBUTORS>
665as listed above.
666
667=head1 LICENSE
668
6d9e2623 669This program is free software. You can redistribute it and/or modify it
ad91060a 670under the same terms as Perl itself.
671
672=cut
673
6741;
c7d7b849 675# vim:sts=4 sw=4 et: