Package up for version 0.26.
[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
078c5263 8our $VERSION = '0.26';
f090a149 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
7314403a 17 qw/ConnectInfo LoadedClass/;
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
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
fb691af9 311They are relative to the C<< MyApp::TraitFor::Model::DBIC::Schema:: >>, then the C<<
312Catalyst::TraitFor::Model::DBIC::Schema:: >> namespaces, unless prefixed with C<+>
41bcf32f 313in which case they are taken to be a fully qualified name. E.g.:
2201c2e4 314
c34bcab6 315 traits Caching
fb691af9 316 traits +MyApp::TraitFor::Model::Foo
2201c2e4 317
0fbbc8d5 318A new instance is created at application time, so any consumed required
319attributes, coercions and modifiers will work.
2201c2e4 320
fb691af9 321Traits are applied at L<Catalyst::Component/COMPONENT> time using
322L<CatalystX::Component::Traits>.
0fbbc8d5 323
41bcf32f 324C<ref $self> will be an anon class if any traits are applied, C<<
325$self->_original_class_name >> will be the original class.
f090a149 326
c7d7b849 327When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
328L</ACCEPT_CONTEXT>.
0fbbc8d5 329
c34bcab6 330Traits that come with the distribution:
0fbbc8d5 331
332=over 4
2201c2e4 333
fb691af9 334=item L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>
0fbbc8d5 335
fb691af9 336=item L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
c4fee9b8 337
0fbbc8d5 338=back
8281c933 339
c4fee9b8 340=head2 storage_type
ad91060a 341
342Allows the use of a different C<storage_type> than what is set in your
343C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 344L<DBIx::Class>). Completely optional, and probably unnecessary for most
345people until other storage backends become available for L<DBIx::Class>.
ad91060a 346
c7d7b849 347=head1 ATTRIBUTES
348
349The keys you pass in the model configuration are available as attributes.
350
351Other attributes available:
352
353=head2 connect_info
354
355Your connect_info args normalized to hashref form (with dsn/user/password.) See
356L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
357L</connect_info>.
358
359=head2 model_name
360
361The model name L<Catalyst> uses to resolve this model, the part after
362C<::Model::> or C<::M::> in your class name. E.g. if your class name is
363C<MyApp::Model::DB> the L</model_name> will be C<DB>.
364
c7d7b849 365=head2 _default_cursor_class
366
6f6b9c2d 367What to reset your L<DBIx::Class::Storage::DBI/cursor_class> to if a custom one
c7d7b849 368doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
369
fb691af9 370=head1 ATTRIBUTES FROM L<MooseX::Traits::Pluggable>
371
372=head2 _original_class_name
373
374The class name of your model before any L</traits> are applied. E.g.
375C<MyApp::Model::DB>.
376
c7d7b849 377=head2 _traits
378
6f6b9c2d 379Unresolved arrayref of traits passed in the config.
c7d7b849 380
381=head2 _resolved_traits
382
383Traits you used resolved to full class names.
384
ad91060a 385=head1 METHODS
386
c4fee9b8 387=head2 new
ad91060a 388
389Instantiates the Model based on the above-documented ->config parameters.
0f2fd2c0 390The only required parameter is C<schema_class>. C<connect_info> is
391required in the case that C<schema_class> does not already have connection
392information defined for it.
ad91060a 393
c4fee9b8 394=head2 schema
f1613faa 395
396Accessor which returns the connected schema being used by the this model.
397There are direct shortcuts on the model class itself for
398schema->resultset, schema->source, and schema->class.
399
c4fee9b8 400=head2 composed_schema
f1613faa 401
402Accessor which returns the composed schema, which has no connection info,
403which was used in constructing the C<schema> above. Useful for creating
404new connections based on the same schema/model. There are direct shortcuts
405from the model object for composed_schema->clone and composed_schema->connect
406
c4fee9b8 407=head2 clone
f1613faa 408
409Shortcut for ->composed_schema->clone
410
c4fee9b8 411=head2 connect
f1613faa 412
413Shortcut for ->composed_schema->connect
414
c4fee9b8 415=head2 source
c12b7310 416
f1613faa 417Shortcut for ->schema->source
418
c4fee9b8 419=head2 class
f1613faa 420
421Shortcut for ->schema->class
422
c4fee9b8 423=head2 resultset
f1613faa 424
425Shortcut for ->schema->resultset
426
c4fee9b8 427=head2 storage
f1613faa 428
429Provides an accessor for the connected schema's storage object.
430Used often for debugging and controlling transactions.
b8427e0b 431
ad91060a 432=cut
433
c34bcab6 434has schema => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 435
c34bcab6 436has schema_class => (
0fbbc8d5 437 is => 'ro',
7314403a 438 isa => LoadedClass,
0fbbc8d5 439 coerce => 1,
440 required => 1
441);
442
c34bcab6 443has storage_type => (is => 'rw', isa => Str);
0fbbc8d5 444
18b829f0 445has connect_info => (is => 'rw', isa => ConnectInfo, coerce => 1);
0fbbc8d5 446
c7d7b849 447has model_name => (
448 is => 'ro',
449 isa => Str,
450 required => 1,
451 lazy_build => 1,
452);
ad91060a 453
c34bcab6 454has _default_cursor_class => (
61ed82a5 455 is => 'ro',
7314403a 456 isa => LoadedClass,
61ed82a5 457 default => 'DBIx::Class::Storage::DBI::Cursor',
458 coerce => 1
459);
460
0fbbc8d5 461sub BUILD {
462 my $self = shift;
f2488839 463 my $class = $self->_original_class_name;
2201c2e4 464 my $schema_class = $self->schema_class;
ad91060a 465
2201c2e4 466 if( !$self->connect_info ) {
f1613faa 467 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 468 $self->connect_info($schema_class->storage->connect_info);
f1613faa 469 }
470 else {
39f5f008 471 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 472 . " or $schema_class must have connect info defined on it."
473 . " Here's what we got:\n"
f1613faa 474 . Dumper($self);
475 }
7db6da78 476 }
477
0fbbc8d5 478 if (exists $self->connect_info->{cursor_class}) {
479 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
480 or croak "invalid connect_info: Cannot load your cursor_class"
481 . " ".$self->connect_info->{cursor_class}.": $@";
482 }
483
0fbbc8d5 484 $self->setup;
485
f1613faa 486 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 487
f1613faa 488 $self->schema($self->composed_schema->clone);
489
2201c2e4 490 $self->schema->storage_type($self->storage_type)
491 if $self->storage_type;
7db6da78 492
2201c2e4 493 $self->schema->connection($self->connect_info);
494
495 $self->_install_rs_models;
2201c2e4 496}
497
498sub clone { shift->composed_schema->clone(@_); }
499
500sub connect { shift->composed_schema->connect(@_); }
501
502sub storage { shift->schema->storage(@_); }
503
5ec3e6cb 504sub resultset { shift->schema->resultset(@_); }
505
c4fee9b8 506=head2 setup
2201c2e4 507
c7d7b849 508Called at C<BUILD>> time before configuration, but after L</connect_info> is
509set. To do something after configuuration use C<< after BUILD => >>.
2201c2e4 510
511=cut
512
0fbbc8d5 513sub setup { 1 }
2201c2e4 514
c4fee9b8 515=head2 ACCEPT_CONTEXT
2201c2e4 516
73f72d28 517Point of extension for doing things at C<< $c->model >> time with context,
518returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
519more information.
2201c2e4 520
0fbbc8d5 521=cut
2201c2e4 522
0fbbc8d5 523sub ACCEPT_CONTEXT { shift }
2201c2e4 524
525sub _install_rs_models {
526 my $self = shift;
7b1fe8c2 527 my $class = $self->_original_class_name;
2201c2e4 528
ad91060a 529 no strict 'refs';
39f5f008 530
531 my @sources = $self->schema->sources;
532
533 die "No sources found (did you forget to define your tables?)"
534 unless @sources;
535
536 foreach my $moniker (@sources) {
0b2a7108 537 my $classname = "${class}::$moniker";
7db6da78 538 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 539 shift;
2201c2e4 540 shift->model($self->model_name)->resultset($moniker);
ad91060a 541 }
542 }
2201c2e4 543}
ad91060a 544
61ed82a5 545sub _reset_cursor_class {
546 my $self = shift;
547
548 if ($self->storage->can('cursor_class')) {
549 $self->storage->cursor_class($self->_default_cursor_class)
550 if $self->storage->cursor_class ne $self->_default_cursor_class;
551 }
552}
553
50f488ec 554{
555 my %COMPOSED_CACHE;
556
557 sub composed_schema {
558 my $self = shift;
559 my $class = $self->_original_class_name;
560 my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
561
562 $$store = shift if @_;
563
564 return $$store
565 }
566}
567
c7d7b849 568sub _build_model_name {
569 my $self = shift;
570 my $class = $self->_original_class_name;
571 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
572
573 return $model_name;
41bcf32f 574}
575
0fbbc8d5 576__PACKAGE__->meta->make_immutable;
2201c2e4 577
ad91060a 578=head1 SEE ALSO
579
7b39f3f0 580General Catalyst Stuff:
581
582L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
583L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
584
585Stuff related to DBIC and this Model style:
586
587L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 588L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
589L<MooseX::Object::Pluggable>
ad91060a 590
c34bcab6 591Traits:
c4fee9b8 592
fb691af9 593L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>,
594L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
c4fee9b8 595
ad91060a 596=head1 AUTHOR
597
c7d7b849 598Brandon L Black, C<blblack at gmail.com>
ad91060a 599
2ff00e2b 600Contributors:
601
41bcf32f 602Rafael Kitover, C<rkitover at cpan.org>
2ff00e2b 603
ad91060a 604=head1 COPYRIGHT
605
606This program is free software, you can redistribute it and/or modify it
607under the same terms as Perl itself.
608
609=cut
610
6111;
c7d7b849 612# vim:sts=4 sw=4 et: