cleanups, deps on mx::traits which is not released yet
[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';
c7d7b849 5with 'MooseX::Traits';
0fbbc8d5 6extends 'Catalyst::Model';
0fbbc8d5 7
f090a149 8our $VERSION = '0.24';
9
73f72d28 10use namespace::autoclean;
bd309c0c 11use Carp::Clan '^Catalyst::Model::DBIC::Schema';
bfcd6e3d 12use Data::Dumper;
2201c2e4 13use DBIx::Class ();
0fbbc8d5 14use Moose::Autobox;
ad91060a 15
61ed82a5 16use Catalyst::Model::DBIC::Schema::Types
17 qw/ConnectInfo SchemaClass CursorClass/;
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
aabc1d75 27Manual creation of a DBIx::Class::Schema and a Catalyst::Model::DBIC::Schema:
28
29=over
30
31=item 1.
32
33Create the DBIx:Class schema in MyApp/Schema/FilmDB.pm:
34
35 package MyApp::Schema::FilmDB;
36 use base qw/DBIx::Class::Schema/;
37
38 __PACKAGE__->load_classes(qw/Actor Role/);
39
40=item 2.
41
42Create some classes for the tables in the database, for example an
43Actor in MyApp/Schema/FilmDB/Actor.pm:
44
45 package MyApp::Schema::FilmDB::Actor;
46 use base qw/DBIx::Class/
07edc53e 47
aabc1d75 48 __PACKAGE__->load_components(qw/Core/);
49 __PACKAGE__->table('actor');
07edc53e 50
aabc1d75 51 ...
52
c8ae74f8 53and a Role in MyApp/Schema/FilmDB/Role.pm:
aabc1d75 54
55 package MyApp::Schema::FilmDB::Role;
56 use base qw/DBIx::Class/
07edc53e 57
aabc1d75 58 __PACKAGE__->load_components(qw/Core/);
4a3e80e9 59 __PACKAGE__->table('role');
07edc53e 60
aabc1d75 61 ...
62
63Notice that the schema is in MyApp::Schema, not in MyApp::Model. This way it's
64usable as a standalone module and you can test/run it without Catalyst.
65
66=item 3.
67
68To expose it to Catalyst as a model, you should create a DBIC Model in
69MyApp/Model/FilmDB.pm:
70
71 package MyApp::Model::FilmDB;
72 use base qw/Catalyst::Model::DBIC::Schema/;
07edc53e 73
aabc1d75 74 __PACKAGE__->config(
75 schema_class => 'MyApp::Schema::FilmDB',
2201c2e4 76 connect_info => {
77 dsn => "DBI:...",
78 user => "username",
79 password => "password",
80 }
aabc1d75 81 );
82
83See below for a full list of the possible config parameters.
84
85=back
86
6dec11b7 87Now you have a working Model which accesses your separate DBIC Schema. This can
aabc1d75 88be used/accessed in the normal Catalyst manner, via $c->model():
89
90 my $actor = $c->model('FilmDB::Actor')->find(1);
91
92You can also use it to set up DBIC authentication with
93Authentication::Store::DBIC in MyApp.pm:
94
95 package MyApp;
07edc53e 96
aabc1d75 97 use Catalyst qw/... Authentication::Store::DBIC/;
07edc53e 98
aabc1d75 99 ...
07edc53e 100
aabc1d75 101 __PACKAGE__->config->{authentication}{dbic} = {
102 user_class => 'FilmDB::Actor',
103 user_field => 'name',
104 password_field => 'password'
105 }
106
d52bc376 107C<< $c->model('Schema::Source') >> returns a L<DBIx::Class::ResultSet> for
108the source name parameter passed. To find out more about which methods can
109be called on a ResultSet, or how to add your own methods to it, please see
110the ResultSet documentation in the L<DBIx::Class> distribution.
aabc1d75 111
112Some examples are given below:
113
f1613faa 114 # to access schema methods directly:
115 $c->model('FilmDB')->schema->source(...);
116
117 # to access the source object, resultset, and class:
07edc53e 118 $c->model('FilmDB')->source(...);
119 $c->model('FilmDB')->resultset(...);
120 $c->model('FilmDB')->class(...);
c12b7310 121
07edc53e 122 # For resultsets, there's an even quicker shortcut:
123 $c->model('FilmDB::Actor')
124 # is the same as $c->model('FilmDB')->resultset('Actor')
ad91060a 125
f1613faa 126 # To get the composed schema for making new connections:
127 my $newconn = $c->model('FilmDB')->composed_schema->connect(...);
128
129 # Or the same thing via a convenience shortcut:
130 my $newconn = $c->model('FilmDB')->connect(...);
131
132 # or, if your schema works on different storage drivers:
133 my $newconn = $c->model('FilmDB')->composed_schema->clone();
134 $newconn->storage_type('::LDAP');
135 $newconn->connection(...);
136
137 # and again, a convenience shortcut
138 my $newconn = $c->model('FilmDB')->clone();
139 $newconn->storage_type('::LDAP');
140 $newconn->connection(...);
141
ad91060a 142=head1 DESCRIPTION
143
7b39f3f0 144This is a Catalyst Model for L<DBIx::Class::Schema>-based Models. See
ef91bcf9 145the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
146information on generating these Models via Helper scripts.
ad91060a 147
d52bc376 148When your Catalyst app starts up, a thin Model layer is created as an
149interface to your DBIC Schema. It should be clearly noted that the model
150object returned by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or
151resultset object, but merely a wrapper proving L<methods|/METHODS> to access
152the underlying schema.
153
154In addition to this model class, a shortcut class is generated for each
155source in the schema, allowing easy and direct access to a resultset of the
156corresponding type. These generated classes are even thinner than the model
157class, providing no public methods but simply hooking into Catalyst's
158model() accessor via the
159L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete
160contents of each generated class is roughly equivalent to the following:
161
162 package MyApp::Model::FilmDB::Actor
163 sub ACCEPT_CONTEXT {
164 my ($self, $c) = @_;
165 $c->model('FilmDB')->resultset('Actor');
166 }
167
168In short, there are three techniques available for obtaining a DBIC
169resultset object:
170
171 # the long way
172 my $rs = $c->model('FilmDB')->schema->resultset('Actor');
173
174 # using the shortcut method on the model object
175 my $rs = $c->model('FilmDB')->resultset('Actor');
176
177 # using the generated class directly
178 my $rs = $c->model('FilmDB::Actor');
179
c082639a 180In order to add methods to a DBIC resultset, you cannot simply add them to
181the source (row, table) definition class; you must define a separate custom
182resultset class. See L<DBIx::Class::Manual::Cookbook/"Predefined searches">
183for more info.
184
ad91060a 185=head1 CONFIG PARAMETERS
186
c4fee9b8 187=head2 schema_class
ad91060a 188
189This is the classname of your L<DBIx::Class::Schema> Schema. It needs
aabc1d75 190to be findable in C<@INC>, but it does not need to be inside the
191C<Catalyst::Model::> namespace. This parameter is required.
ad91060a 192
c4fee9b8 193=head2 connect_info
ad91060a 194
195This is an arrayref of connection parameters, which are specific to your
b9a72351 196C<storage_type> (see your storage type documentation for more details).
197If you only need one parameter (e.g. the DSN), you can just pass a string
198instead of an arrayref.
ad91060a 199
0f2fd2c0 200This is not required if C<schema_class> already has connection information
d89e6c8a 201defined inside itself (which isn't highly recommended, but can be done)
0f2fd2c0 202
7db6da78 203For L<DBIx::Class::Storage::DBI>, which is the only supported
204C<storage_type> in L<DBIx::Class> at the time of this writing, the
205parameters are your dsn, username, password, and connect options hashref.
206
018eb0e2 207See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
208of the arguments supported.
7db6da78 209
210Examples:
211
2201c2e4 212 connect_info => {
213 dsn => 'dbi:Pg:dbname=mypgdb',
214 user => 'postgres',
215 password => ''
216 }
07edc53e 217
2201c2e4 218 connect_info => {
219 dsn => 'dbi:SQLite:dbname=foo.db',
220 on_connect_do => [
221 'PRAGMA synchronous = OFF',
222 ]
223 }
07edc53e 224
2201c2e4 225 connect_info => {
226 dsn => 'dbi:Pg:dbname=mypgdb',
227 user => 'postgres',
228 password => '',
229 pg_enable_utf8 => 1,
230 on_connect_do => [
231 'some SQL statement',
232 'another SQL statement',
233 ],
234 }
7db6da78 235
8281c933 236Or using L<Config::General>:
237
238 <Model::FilmDB>
239 schema_class MyApp::Schema::FilmDB
c34bcab6 240 traits Caching
8281c933 241 <connect_info>
2201c2e4 242 dsn dbi:Pg:dbname=mypgdb
243 user postgres
42e14c31 244 password ""
2201c2e4 245 auto_savepoint 1
a75b6e58 246 quote_char """
8281c933 247 on_connect_do some SQL statement
248 on_connect_do another SQL statement
249 </connect_info>
250 </Model::FilmDB>
251
252or
253
254 <Model::FilmDB>
255 schema_class MyApp::Schema::FilmDB
256 connect_info dbi:SQLite:dbname=foo.db
257 </Model::FilmDB>
258
2201c2e4 259Or using L<YAML>:
260
261 Model::MyDB:
262 schema_class: MyDB
263 connect_info:
264 dsn: dbi:Oracle:mydb
265 user: mtfnpy
266 password: mypass
267 LongReadLen: 1000000
268 LongTruncOk: 1
269 on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
270 cursor_class: 'DBIx::Class::Cursor::Cached'
a75b6e58 271 quote_char: '"'
2201c2e4 272
273The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
274supported:
275
276 connect_info => [
277 'dbi:Pg:dbname=mypgdb',
278 'postgres',
279 '',
280 {
281 pg_enable_utf8 => 1,
282 },
283 {
0fbbc8d5 284 auto_savepoint => 1,
2201c2e4 285 on_connect_do => [
286 'some SQL statement',
287 'another SQL statement',
288 ],
289 }
290 ]
291
c34bcab6 292=head2 traits
0fbbc8d5 293
41bcf32f 294Array of Traits to apply to the instance. Traits are L<Moose::Role>s.
295
296They are relative to the C<< MyApp::Model::DB::Trait:: >>, then the C<<
297Catalyst::Model::DBIC::Schema::Trait:: >> namespaces, unless prefixed with C<+>
298in which case they are taken to be a fully qualified name. E.g.:
2201c2e4 299
c34bcab6 300 traits Caching
301 traits +MyApp::DB::Trait::Foo
2201c2e4 302
0fbbc8d5 303A new instance is created at application time, so any consumed required
304attributes, coercions and modifiers will work.
2201c2e4 305
c7d7b849 306Traits are applied at L<Catalyst::Component/COMPONENT> time using L<MooseX::Traits>.
0fbbc8d5 307
41bcf32f 308C<ref $self> will be an anon class if any traits are applied, C<<
309$self->_original_class_name >> will be the original class.
f090a149 310
c7d7b849 311When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
312L</ACCEPT_CONTEXT>.
0fbbc8d5 313
c34bcab6 314Traits that come with the distribution:
0fbbc8d5 315
316=over 4
2201c2e4 317
c34bcab6 318=item L<Catalyst::Model::DBIC::Schema::Trait::Caching>
0fbbc8d5 319
c34bcab6 320=item L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
c4fee9b8 321
0fbbc8d5 322=back
8281c933 323
c4fee9b8 324=head2 storage_type
ad91060a 325
326Allows the use of a different C<storage_type> than what is set in your
327C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 328L<DBIx::Class>). Completely optional, and probably unnecessary for most
329people until other storage backends become available for L<DBIx::Class>.
ad91060a 330
c7d7b849 331=head1 ATTRIBUTES
332
333The keys you pass in the model configuration are available as attributes.
334
335Other attributes available:
336
337=head2 connect_info
338
339Your connect_info args normalized to hashref form (with dsn/user/password.) See
340L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
341L</connect_info>.
342
343=head2 model_name
344
345The model name L<Catalyst> uses to resolve this model, the part after
346C<::Model::> or C<::M::> in your class name. E.g. if your class name is
347C<MyApp::Model::DB> the L</model_name> will be C<DB>.
348
349=head2 _original_class_name
350
351The class name of your model before any L</traits> are applied. E.g.
352C<MyApp::Model::DB>.
353
354=head2 _default_cursor_class
355
356What to rest your L<DBIx::Class::Storage::DBI/cursor_class> if a custom one
357doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
358
359=head2 _traits
360
361Unresolved arrayref of traits passed at C<COMPONENT> time.
362
363=head2 _resolved_traits
364
365Traits you used resolved to full class names.
366
ad91060a 367=head1 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 => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 417
c34bcab6 418has schema_class => (
0fbbc8d5 419 is => 'ro',
420 isa => SchemaClass,
421 coerce => 1,
422 required => 1
423);
424
c34bcab6 425has storage_type => (is => 'rw', isa => Str);
0fbbc8d5 426
c34bcab6 427has connect_info => (is => 'ro', isa => ConnectInfo, coerce => 1);
0fbbc8d5 428
c7d7b849 429has model_name => (
430 is => 'ro',
431 isa => Str,
432 required => 1,
433 lazy_build => 1,
434);
ad91060a 435
c7d7b849 436has _traits => (is => 'ro', isa => ArrayRef);
437has _resolved_traits => (is => 'ro', isa => ArrayRef);
41bcf32f 438
c34bcab6 439has _default_cursor_class => (
61ed82a5 440 is => 'ro',
441 isa => CursorClass,
442 default => 'DBIx::Class::Storage::DBI::Cursor',
443 coerce => 1
444);
445
41bcf32f 446has _original_class_name => (
447 is => 'ro',
448 required => 1,
449 isa => Str,
450 default => sub { blessed $_[0] },
451);
452
c7d7b849 453sub COMPONENT {
454 my ($class, $app, $args) = @_;
455
456 $args = $class->merge_config_hashes($class->config, $args);
457
458 if (my $traits = delete $args->{traits}) {
459 my @traits = $class->_resolve_traits($traits->flatten);
73f72d28 460 return $class->new_with_traits({
c7d7b849 461 traits => \@traits,
462 _original_class_name => $class,
463 _traits => $traits,
464 _resolved_traits => \@traits,
465 %$args
73f72d28 466 });
c7d7b849 467 }
468
73f72d28 469 return $class->new($args);
c7d7b849 470}
471
0fbbc8d5 472sub BUILD {
473 my $self = shift;
474 my $class = ref $self;
2201c2e4 475 my $schema_class = $self->schema_class;
ad91060a 476
2201c2e4 477 if( !$self->connect_info ) {
f1613faa 478 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 479 $self->connect_info($schema_class->storage->connect_info);
f1613faa 480 }
481 else {
39f5f008 482 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 483 . " or $schema_class must have connect info defined on it."
484 . " Here's what we got:\n"
f1613faa 485 . Dumper($self);
486 }
7db6da78 487 }
488
0fbbc8d5 489 if (exists $self->connect_info->{cursor_class}) {
490 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
491 or croak "invalid connect_info: Cannot load your cursor_class"
492 . " ".$self->connect_info->{cursor_class}.": $@";
493 }
494
0fbbc8d5 495 $self->setup;
496
f1613faa 497 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 498
f1613faa 499 $self->schema($self->composed_schema->clone);
500
2201c2e4 501 $self->schema->storage_type($self->storage_type)
502 if $self->storage_type;
7db6da78 503
2201c2e4 504 $self->schema->connection($self->connect_info);
505
506 $self->_install_rs_models;
2201c2e4 507}
508
509sub clone { shift->composed_schema->clone(@_); }
510
511sub connect { shift->composed_schema->connect(@_); }
512
513sub storage { shift->schema->storage(@_); }
514
5ec3e6cb 515sub resultset { shift->schema->resultset(@_); }
516
c4fee9b8 517=head2 setup
2201c2e4 518
c7d7b849 519Called at C<BUILD>> time before configuration, but after L</connect_info> is
520set. To do something after configuuration use C<< after BUILD => >>.
2201c2e4 521
522=cut
523
0fbbc8d5 524sub setup { 1 }
2201c2e4 525
c4fee9b8 526=head2 ACCEPT_CONTEXT
2201c2e4 527
73f72d28 528Point of extension for doing things at C<< $c->model >> time with context,
529returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
530more information.
2201c2e4 531
0fbbc8d5 532=cut
2201c2e4 533
0fbbc8d5 534sub ACCEPT_CONTEXT { shift }
2201c2e4 535
536sub _install_rs_models {
537 my $self = shift;
7b1fe8c2 538 my $class = $self->_original_class_name;
2201c2e4 539
ad91060a 540 no strict 'refs';
39f5f008 541
542 my @sources = $self->schema->sources;
543
544 die "No sources found (did you forget to define your tables?)"
545 unless @sources;
546
547 foreach my $moniker (@sources) {
0b2a7108 548 my $classname = "${class}::$moniker";
7db6da78 549 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 550 shift;
2201c2e4 551 shift->model($self->model_name)->resultset($moniker);
ad91060a 552 }
553 }
2201c2e4 554}
ad91060a 555
61ed82a5 556sub _reset_cursor_class {
557 my $self = shift;
558
559 if ($self->storage->can('cursor_class')) {
560 $self->storage->cursor_class($self->_default_cursor_class)
561 if $self->storage->cursor_class ne $self->_default_cursor_class;
562 }
563}
564
50f488ec 565{
566 my %COMPOSED_CACHE;
567
568 sub composed_schema {
569 my $self = shift;
570 my $class = $self->_original_class_name;
571 my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
572
573 $$store = shift if @_;
574
575 return $$store
576 }
577}
578
c7d7b849 579sub _resolve_traits {
580 my ($class, @names) = @_;
41bcf32f 581 my $base = 'Trait';
582
41bcf32f 583 my @search_ns = grep !/^(?:Moose|Class::MOP)::/,
584 $class->meta->class_precedence_list;
585
586 my @traits;
587
588 OUTER: for my $name (@names) {
589 if ($name =~ /^\+(.*)/) {
590 push @traits, $1;
591 next;
592 }
593 for my $ns (@search_ns) {
594 my $full = "${ns}::${base}::${name}";
595 if (eval { Class::MOP::load_class($full) }) {
596 push @traits, $full;
597 next OUTER;
598 }
599 }
600 }
601
c7d7b849 602 return @traits;
603}
604
605sub _build_model_name {
606 my $self = shift;
607 my $class = $self->_original_class_name;
608 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
609
610 return $model_name;
41bcf32f 611}
612
0fbbc8d5 613__PACKAGE__->meta->make_immutable;
2201c2e4 614
ad91060a 615=head1 SEE ALSO
616
7b39f3f0 617General Catalyst Stuff:
618
619L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
620L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
621
622Stuff related to DBIC and this Model style:
623
624L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 625L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
626L<MooseX::Object::Pluggable>
ad91060a 627
c34bcab6 628Traits:
c4fee9b8 629
c34bcab6 630L<Catalyst::Model::DBIC::Schema::Trait::Caching>,
631L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
c4fee9b8 632
ad91060a 633=head1 AUTHOR
634
c7d7b849 635Brandon L Black, C<blblack at gmail.com>
ad91060a 636
2ff00e2b 637Contributors:
638
41bcf32f 639Rafael Kitover, C<rkitover at cpan.org>
2ff00e2b 640
ad91060a 641=head1 COPYRIGHT
642
643This program is free software, you can redistribute it and/or modify it
644under the same terms as Perl itself.
645
646=cut
647
6481;
c7d7b849 649# vim:sts=4 sw=4 et: