C::M::DBIC::Schema - minor cleanups
[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';
6with 'MooseX::Object::Pluggable';
7
f090a149 8our $VERSION = '0.24';
9
bd309c0c 10use Carp::Clan '^Catalyst::Model::DBIC::Schema';
bfcd6e3d 11use Data::Dumper;
2201c2e4 12use DBIx::Class ();
13use Scalar::Util 'reftype';
0fbbc8d5 14use MooseX::ClassAttribute;
15use Moose::Autobox;
ad91060a 16
61ed82a5 17use Catalyst::Model::DBIC::Schema::Types
18 qw/ConnectInfo SchemaClass CursorClass/;
19
3f139b02 20use MooseX::Types::Moose qw/ArrayRef Str ClassName/;
0fbbc8d5 21
22use namespace::clean -except => 'meta';
f1613faa 23
ad91060a 24=head1 NAME
25
26Catalyst::Model::DBIC::Schema - DBIx::Class::Schema Model Class
27
28=head1 SYNOPSIS
29
aabc1d75 30Manual creation of a DBIx::Class::Schema and a Catalyst::Model::DBIC::Schema:
31
32=over
33
34=item 1.
35
36Create the DBIx:Class schema in MyApp/Schema/FilmDB.pm:
37
38 package MyApp::Schema::FilmDB;
39 use base qw/DBIx::Class::Schema/;
40
41 __PACKAGE__->load_classes(qw/Actor Role/);
42
43=item 2.
44
45Create some classes for the tables in the database, for example an
46Actor in MyApp/Schema/FilmDB/Actor.pm:
47
48 package MyApp::Schema::FilmDB::Actor;
49 use base qw/DBIx::Class/
07edc53e 50
aabc1d75 51 __PACKAGE__->load_components(qw/Core/);
52 __PACKAGE__->table('actor');
07edc53e 53
aabc1d75 54 ...
55
c8ae74f8 56and a Role in MyApp/Schema/FilmDB/Role.pm:
aabc1d75 57
58 package MyApp::Schema::FilmDB::Role;
59 use base qw/DBIx::Class/
07edc53e 60
aabc1d75 61 __PACKAGE__->load_components(qw/Core/);
4a3e80e9 62 __PACKAGE__->table('role');
07edc53e 63
aabc1d75 64 ...
65
66Notice that the schema is in MyApp::Schema, not in MyApp::Model. This way it's
67usable as a standalone module and you can test/run it without Catalyst.
68
69=item 3.
70
71To expose it to Catalyst as a model, you should create a DBIC Model in
72MyApp/Model/FilmDB.pm:
73
74 package MyApp::Model::FilmDB;
75 use base qw/Catalyst::Model::DBIC::Schema/;
07edc53e 76
aabc1d75 77 __PACKAGE__->config(
78 schema_class => 'MyApp::Schema::FilmDB',
2201c2e4 79 connect_info => {
80 dsn => "DBI:...",
81 user => "username",
82 password => "password",
83 }
aabc1d75 84 );
85
86See below for a full list of the possible config parameters.
87
88=back
89
6dec11b7 90Now you have a working Model which accesses your separate DBIC Schema. This can
aabc1d75 91be used/accessed in the normal Catalyst manner, via $c->model():
92
93 my $actor = $c->model('FilmDB::Actor')->find(1);
94
95You can also use it to set up DBIC authentication with
96Authentication::Store::DBIC in MyApp.pm:
97
98 package MyApp;
07edc53e 99
aabc1d75 100 use Catalyst qw/... Authentication::Store::DBIC/;
07edc53e 101
aabc1d75 102 ...
07edc53e 103
aabc1d75 104 __PACKAGE__->config->{authentication}{dbic} = {
105 user_class => 'FilmDB::Actor',
106 user_field => 'name',
107 password_field => 'password'
108 }
109
d52bc376 110C<< $c->model('Schema::Source') >> returns a L<DBIx::Class::ResultSet> for
111the source name parameter passed. To find out more about which methods can
112be called on a ResultSet, or how to add your own methods to it, please see
113the ResultSet documentation in the L<DBIx::Class> distribution.
aabc1d75 114
115Some examples are given below:
116
f1613faa 117 # to access schema methods directly:
118 $c->model('FilmDB')->schema->source(...);
119
120 # to access the source object, resultset, and class:
07edc53e 121 $c->model('FilmDB')->source(...);
122 $c->model('FilmDB')->resultset(...);
123 $c->model('FilmDB')->class(...);
c12b7310 124
07edc53e 125 # For resultsets, there's an even quicker shortcut:
126 $c->model('FilmDB::Actor')
127 # is the same as $c->model('FilmDB')->resultset('Actor')
ad91060a 128
f1613faa 129 # To get the composed schema for making new connections:
130 my $newconn = $c->model('FilmDB')->composed_schema->connect(...);
131
132 # Or the same thing via a convenience shortcut:
133 my $newconn = $c->model('FilmDB')->connect(...);
134
135 # or, if your schema works on different storage drivers:
136 my $newconn = $c->model('FilmDB')->composed_schema->clone();
137 $newconn->storage_type('::LDAP');
138 $newconn->connection(...);
139
140 # and again, a convenience shortcut
141 my $newconn = $c->model('FilmDB')->clone();
142 $newconn->storage_type('::LDAP');
143 $newconn->connection(...);
144
ad91060a 145=head1 DESCRIPTION
146
7b39f3f0 147This is a Catalyst Model for L<DBIx::Class::Schema>-based Models. See
ef91bcf9 148the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
149information on generating these Models via Helper scripts.
ad91060a 150
d52bc376 151When your Catalyst app starts up, a thin Model layer is created as an
152interface to your DBIC Schema. It should be clearly noted that the model
153object returned by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or
154resultset object, but merely a wrapper proving L<methods|/METHODS> to access
155the underlying schema.
156
157In addition to this model class, a shortcut class is generated for each
158source in the schema, allowing easy and direct access to a resultset of the
159corresponding type. These generated classes are even thinner than the model
160class, providing no public methods but simply hooking into Catalyst's
161model() accessor via the
162L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete
163contents of each generated class is roughly equivalent to the following:
164
165 package MyApp::Model::FilmDB::Actor
166 sub ACCEPT_CONTEXT {
167 my ($self, $c) = @_;
168 $c->model('FilmDB')->resultset('Actor');
169 }
170
171In short, there are three techniques available for obtaining a DBIC
172resultset object:
173
174 # the long way
175 my $rs = $c->model('FilmDB')->schema->resultset('Actor');
176
177 # using the shortcut method on the model object
178 my $rs = $c->model('FilmDB')->resultset('Actor');
179
180 # using the generated class directly
181 my $rs = $c->model('FilmDB::Actor');
182
c082639a 183In order to add methods to a DBIC resultset, you cannot simply add them to
184the source (row, table) definition class; you must define a separate custom
185resultset class. See L<DBIx::Class::Manual::Cookbook/"Predefined searches">
186for more info.
187
ad91060a 188=head1 CONFIG PARAMETERS
189
c4fee9b8 190=head2 schema_class
ad91060a 191
192This is the classname of your L<DBIx::Class::Schema> Schema. It needs
aabc1d75 193to be findable in C<@INC>, but it does not need to be inside the
194C<Catalyst::Model::> namespace. This parameter is required.
ad91060a 195
c4fee9b8 196=head2 connect_info
ad91060a 197
198This is an arrayref of connection parameters, which are specific to your
b9a72351 199C<storage_type> (see your storage type documentation for more details).
200If you only need one parameter (e.g. the DSN), you can just pass a string
201instead of an arrayref.
ad91060a 202
0f2fd2c0 203This is not required if C<schema_class> already has connection information
d89e6c8a 204defined inside itself (which isn't highly recommended, but can be done)
0f2fd2c0 205
7db6da78 206For L<DBIx::Class::Storage::DBI>, which is the only supported
207C<storage_type> in L<DBIx::Class> at the time of this writing, the
208parameters are your dsn, username, password, and connect options hashref.
209
018eb0e2 210See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
211of the arguments supported.
7db6da78 212
213Examples:
214
2201c2e4 215 connect_info => {
216 dsn => 'dbi:Pg:dbname=mypgdb',
217 user => 'postgres',
218 password => ''
219 }
07edc53e 220
2201c2e4 221 connect_info => {
222 dsn => 'dbi:SQLite:dbname=foo.db',
223 on_connect_do => [
224 'PRAGMA synchronous = OFF',
225 ]
226 }
07edc53e 227
2201c2e4 228 connect_info => {
229 dsn => 'dbi:Pg:dbname=mypgdb',
230 user => 'postgres',
231 password => '',
232 pg_enable_utf8 => 1,
233 on_connect_do => [
234 'some SQL statement',
235 'another SQL statement',
236 ],
237 }
7db6da78 238
8281c933 239Or using L<Config::General>:
240
241 <Model::FilmDB>
242 schema_class MyApp::Schema::FilmDB
f090a149 243 roles Caching
8281c933 244 <connect_info>
2201c2e4 245 dsn dbi:Pg:dbname=mypgdb
246 user postgres
247 password ''
248 auto_savepoint 1
8281c933 249 on_connect_do some SQL statement
250 on_connect_do another SQL statement
251 </connect_info>
252 </Model::FilmDB>
253
254or
255
256 <Model::FilmDB>
257 schema_class MyApp::Schema::FilmDB
258 connect_info dbi:SQLite:dbname=foo.db
259 </Model::FilmDB>
260
2201c2e4 261Or using L<YAML>:
262
263 Model::MyDB:
264 schema_class: MyDB
265 connect_info:
266 dsn: dbi:Oracle:mydb
267 user: mtfnpy
268 password: mypass
269 LongReadLen: 1000000
270 LongTruncOk: 1
271 on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
272 cursor_class: 'DBIx::Class::Cursor::Cached'
273
274The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
275supported:
276
277 connect_info => [
278 'dbi:Pg:dbname=mypgdb',
279 'postgres',
280 '',
281 {
282 pg_enable_utf8 => 1,
283 },
284 {
0fbbc8d5 285 auto_savepoint => 1,
2201c2e4 286 on_connect_do => [
287 'some SQL statement',
288 'another SQL statement',
289 ],
290 }
291 ]
292
c4fee9b8 293=head2 roles
0fbbc8d5 294
295Array of Roles to apply at BUILD time. Roles are relative to the
296C<<MyApp::Model::DB::Role::> then C<<Catalyst::Model::DBIC::Schema::Role::>>
297namespaces, unless prefixed with C<+> in which case they are taken to be a
298fully qualified name. E.g.:
2201c2e4 299
0fbbc8d5 300 roles Caching
301 roles +MyApp::DB::Role::Foo
2201c2e4 302
0fbbc8d5 303This is done using L<MooseX::Object::Pluggable>.
2201c2e4 304
0fbbc8d5 305A new instance is created at application time, so any consumed required
306attributes, coercions and modifiers will work.
2201c2e4 307
c4fee9b8 308Roles are applied before setup, schema and connection are set.
0fbbc8d5 309
f090a149 310C<ref $self> will be an anon class if any roles are applied.
311
312You cannot modify C<new> or C<BUILD>, modify C<setup> instead.
0fbbc8d5 313
c4fee9b8 314L</ACCEPT_CONTEXT> and L</finalize> can also be modified.
0fbbc8d5 315
316Roles that come with the distribution:
317
318=over 4
2201c2e4 319
0fbbc8d5 320=item L<Catalyst::Model::DBIC::Schema::Role::Caching>
321
c4fee9b8 322=item L<Catalyst::Model::DBIC::Schema::Role::Replicated>
323
0fbbc8d5 324=back
8281c933 325
c4fee9b8 326=head2 storage_type
ad91060a 327
328Allows the use of a different C<storage_type> than what is set in your
329C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 330L<DBIx::Class>). Completely optional, and probably unnecessary for most
331people until other storage backends become available for L<DBIx::Class>.
ad91060a 332
ad91060a 333=head1 METHODS
334
c4fee9b8 335=head2 new
ad91060a 336
337Instantiates the Model based on the above-documented ->config parameters.
0f2fd2c0 338The only required parameter is C<schema_class>. C<connect_info> is
339required in the case that C<schema_class> does not already have connection
340information defined for it.
ad91060a 341
c4fee9b8 342=head2 schema
f1613faa 343
344Accessor which returns the connected schema being used by the this model.
345There are direct shortcuts on the model class itself for
346schema->resultset, schema->source, and schema->class.
347
c4fee9b8 348=head2 composed_schema
f1613faa 349
350Accessor which returns the composed schema, which has no connection info,
351which was used in constructing the C<schema> above. Useful for creating
352new connections based on the same schema/model. There are direct shortcuts
353from the model object for composed_schema->clone and composed_schema->connect
354
c4fee9b8 355=head2 clone
f1613faa 356
357Shortcut for ->composed_schema->clone
358
c4fee9b8 359=head2 connect
f1613faa 360
361Shortcut for ->composed_schema->connect
362
c4fee9b8 363=head2 source
c12b7310 364
f1613faa 365Shortcut for ->schema->source
366
c4fee9b8 367=head2 class
f1613faa 368
369Shortcut for ->schema->class
370
c4fee9b8 371=head2 resultset
f1613faa 372
373Shortcut for ->schema->resultset
374
c4fee9b8 375=head2 storage
f1613faa 376
377Provides an accessor for the connected schema's storage object.
378Used often for debugging and controlling transactions.
b8427e0b 379
ad91060a 380=cut
381
2ff00e2b 382class_has 'composed_schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 383
2ff00e2b 384has 'schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 385
386has 'schema_class' => (
387 is => 'ro',
388 isa => SchemaClass,
389 coerce => 1,
390 required => 1
391);
392
3f139b02 393has 'storage_type' => (is => 'rw', isa => Str);
0fbbc8d5 394
395has 'connect_info' => (is => 'ro', isa => ConnectInfo, coerce => 1);
396
397# ref $self changes to anon after roles are applied, and _original_class_name is
2ff00e2b 398# broken in MX::O::P 0.0009
3f139b02 399has '_class_name' => (is => 'ro', isa => ClassName, default => sub {
0fbbc8d5 400 ref shift
401});
402
3f139b02 403has 'model_name' => (is => 'ro', isa => Str, default => sub {
0fbbc8d5 404 my $self = shift;
405
2201c2e4 406 my $class = ref $self;
0fbbc8d5 407 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
2201c2e4 408
0fbbc8d5 409 $model_name
410});
ad91060a 411
3f139b02 412has 'roles' => (is => 'ro', isa => ArrayRef|Str);
ad91060a 413
61ed82a5 414has '_default_cursor_class' => (
415 is => 'ro',
416 isa => CursorClass,
417 default => 'DBIx::Class::Storage::DBI::Cursor',
418 coerce => 1
419);
420
0fbbc8d5 421sub BUILD {
422 my $self = shift;
423 my $class = ref $self;
2201c2e4 424 my $schema_class = $self->schema_class;
ad91060a 425
2201c2e4 426 if( !$self->connect_info ) {
f1613faa 427 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 428 $self->connect_info($schema_class->storage->connect_info);
f1613faa 429 }
430 else {
39f5f008 431 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 432 . " or $schema_class must have connect info defined on it."
433 . " Here's what we got:\n"
f1613faa 434 . Dumper($self);
435 }
7db6da78 436 }
437
0fbbc8d5 438 if (exists $self->connect_info->{cursor_class}) {
439 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
440 or croak "invalid connect_info: Cannot load your cursor_class"
441 . " ".$self->connect_info->{cursor_class}.": $@";
442 }
443
444 $self->_plugin_ns('Role');
445
446 $self->load_plugins($self->roles->flatten) if $self->roles;
447
448 $self->setup;
449
f1613faa 450 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 451
f1613faa 452 $self->schema($self->composed_schema->clone);
453
2201c2e4 454 $self->schema->storage_type($self->storage_type)
455 if $self->storage_type;
7db6da78 456
2201c2e4 457 $self->schema->connection($self->connect_info);
458
459 $self->_install_rs_models;
c4fee9b8 460
461 $self->finalize;
2201c2e4 462}
463
464sub clone { shift->composed_schema->clone(@_); }
465
466sub connect { shift->composed_schema->connect(@_); }
467
468sub storage { shift->schema->storage(@_); }
469
c4fee9b8 470=head2 setup
2201c2e4 471
c4fee9b8 472Called at C<<BUILD>> time before configuration.
2201c2e4 473
474=cut
475
0fbbc8d5 476sub setup { 1 }
2201c2e4 477
c4fee9b8 478=head2 finalize
479
480Called at the end of C<BUILD> after everything has been configured.
481
482=cut
483
484sub finalize { 1 }
485
486=head2 ACCEPT_CONTEXT
2201c2e4 487
0fbbc8d5 488Point of extension for doing things at C<<$c->model>> time, returns the model
489instance, see L<Catalyst::Manual::Intro> for more information.
2201c2e4 490
0fbbc8d5 491=cut
2201c2e4 492
0fbbc8d5 493sub ACCEPT_CONTEXT { shift }
2201c2e4 494
495sub _install_rs_models {
496 my $self = shift;
0fbbc8d5 497 my $class = $self->_class_name;
2201c2e4 498
ad91060a 499 no strict 'refs';
39f5f008 500
501 my @sources = $self->schema->sources;
502
503 die "No sources found (did you forget to define your tables?)"
504 unless @sources;
505
506 foreach my $moniker (@sources) {
0b2a7108 507 my $classname = "${class}::$moniker";
7db6da78 508 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 509 shift;
2201c2e4 510 shift->model($self->model_name)->resultset($moniker);
ad91060a 511 }
512 }
2201c2e4 513}
ad91060a 514
61ed82a5 515sub _reset_cursor_class {
516 my $self = shift;
517
518 if ($self->storage->can('cursor_class')) {
519 $self->storage->cursor_class($self->_default_cursor_class)
520 if $self->storage->cursor_class ne $self->_default_cursor_class;
521 }
522}
523
0fbbc8d5 524__PACKAGE__->meta->make_immutable;
2201c2e4 525
ad91060a 526=head1 SEE ALSO
527
7b39f3f0 528General Catalyst Stuff:
529
530L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
531L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
532
533Stuff related to DBIC and this Model style:
534
535L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 536L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
537L<MooseX::Object::Pluggable>
ad91060a 538
c4fee9b8 539Roles:
540
541L<Catalyst::Model::DBIC::Schema::Role::Caching>,
542L<Catalyst::Model::DBIC::Schema::Role::Replicated>
543
ad91060a 544=head1 AUTHOR
545
546Brandon L Black, C<blblack@gmail.com>
547
2ff00e2b 548Contributors:
549
550Rafael Kitover, C<<rkitover at cpan.org>>
551
ad91060a 552=head1 COPYRIGHT
553
554This program is free software, you can redistribute it and/or modify it
555under the same terms as Perl itself.
556
557=cut
558
5591;