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