2 char quote_char support and s/roles/traits/
[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
c34bcab6 243 traits Caching
8281c933 244 <connect_info>
2201c2e4 245 dsn dbi:Pg:dbname=mypgdb
246 user postgres
42e14c31 247 password ""
2201c2e4 248 auto_savepoint 1
a75b6e58 249 quote_char """
8281c933 250 on_connect_do some SQL statement
251 on_connect_do another SQL statement
252 </connect_info>
253 </Model::FilmDB>
254
255or
256
257 <Model::FilmDB>
258 schema_class MyApp::Schema::FilmDB
259 connect_info dbi:SQLite:dbname=foo.db
260 </Model::FilmDB>
261
2201c2e4 262Or using L<YAML>:
263
264 Model::MyDB:
265 schema_class: MyDB
266 connect_info:
267 dsn: dbi:Oracle:mydb
268 user: mtfnpy
269 password: mypass
270 LongReadLen: 1000000
271 LongTruncOk: 1
272 on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
273 cursor_class: 'DBIx::Class::Cursor::Cached'
a75b6e58 274 quote_char: '"'
2201c2e4 275
276The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
277supported:
278
279 connect_info => [
280 'dbi:Pg:dbname=mypgdb',
281 'postgres',
282 '',
283 {
284 pg_enable_utf8 => 1,
285 },
286 {
0fbbc8d5 287 auto_savepoint => 1,
2201c2e4 288 on_connect_do => [
289 'some SQL statement',
290 'another SQL statement',
291 ],
292 }
293 ]
294
c34bcab6 295=head2 traits
0fbbc8d5 296
c34bcab6 297Array of Traits to apply at BUILD time. Traits are relative to the
298C<<MyApp::Model::DB::Trait::> then C<<Catalyst::Model::DBIC::Schema::Trait::>>
0fbbc8d5 299namespaces, unless prefixed with C<+> in which case they are taken to be a
300fully qualified name. E.g.:
2201c2e4 301
c34bcab6 302 traits Caching
303 traits +MyApp::DB::Trait::Foo
2201c2e4 304
0fbbc8d5 305This is done using L<MooseX::Object::Pluggable>.
2201c2e4 306
0fbbc8d5 307A new instance is created at application time, so any consumed required
308attributes, coercions and modifiers will work.
2201c2e4 309
c34bcab6 310Traits are applied before setup, schema and connection are set.
0fbbc8d5 311
c34bcab6 312C<ref $self> will be an anon class if any traits are applied.
f090a149 313
314You cannot modify C<new> or C<BUILD>, modify C<setup> instead.
0fbbc8d5 315
c4fee9b8 316L</ACCEPT_CONTEXT> and L</finalize> can also be modified.
0fbbc8d5 317
c34bcab6 318Traits that come with the distribution:
0fbbc8d5 319
320=over 4
2201c2e4 321
c34bcab6 322=item L<Catalyst::Model::DBIC::Schema::Trait::Caching>
0fbbc8d5 323
c34bcab6 324=item L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
c4fee9b8 325
0fbbc8d5 326=back
8281c933 327
c4fee9b8 328=head2 storage_type
ad91060a 329
330Allows the use of a different C<storage_type> than what is set in your
331C<schema_class> (which in turn defaults to C<::DBI> if not set in current
f1613faa 332L<DBIx::Class>). Completely optional, and probably unnecessary for most
333people until other storage backends become available for L<DBIx::Class>.
ad91060a 334
ad91060a 335=head1 METHODS
336
c4fee9b8 337=head2 new
ad91060a 338
339Instantiates the Model based on the above-documented ->config parameters.
0f2fd2c0 340The only required parameter is C<schema_class>. C<connect_info> is
341required in the case that C<schema_class> does not already have connection
342information defined for it.
ad91060a 343
c4fee9b8 344=head2 schema
f1613faa 345
346Accessor which returns the connected schema being used by the this model.
347There are direct shortcuts on the model class itself for
348schema->resultset, schema->source, and schema->class.
349
c4fee9b8 350=head2 composed_schema
f1613faa 351
352Accessor which returns the composed schema, which has no connection info,
353which was used in constructing the C<schema> above. Useful for creating
354new connections based on the same schema/model. There are direct shortcuts
355from the model object for composed_schema->clone and composed_schema->connect
356
c4fee9b8 357=head2 clone
f1613faa 358
359Shortcut for ->composed_schema->clone
360
c4fee9b8 361=head2 connect
f1613faa 362
363Shortcut for ->composed_schema->connect
364
c4fee9b8 365=head2 source
c12b7310 366
f1613faa 367Shortcut for ->schema->source
368
c4fee9b8 369=head2 class
f1613faa 370
371Shortcut for ->schema->class
372
c4fee9b8 373=head2 resultset
f1613faa 374
375Shortcut for ->schema->resultset
376
c4fee9b8 377=head2 storage
f1613faa 378
379Provides an accessor for the connected schema's storage object.
380Used often for debugging and controlling transactions.
b8427e0b 381
ad91060a 382=cut
383
c34bcab6 384has schema => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 385
c34bcab6 386has schema_class => (
0fbbc8d5 387 is => 'ro',
388 isa => SchemaClass,
389 coerce => 1,
390 required => 1
391);
392
c34bcab6 393has storage_type => (is => 'rw', isa => Str);
0fbbc8d5 394
c34bcab6 395has connect_info => (is => 'ro', isa => ConnectInfo, coerce => 1);
0fbbc8d5 396
c34bcab6 397has model_name => (is => 'ro', isa => Str, default => sub {
0fbbc8d5 398 my $self = shift;
399
2201c2e4 400 my $class = ref $self;
0fbbc8d5 401 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
2201c2e4 402
0fbbc8d5 403 $model_name
404});
ad91060a 405
c34bcab6 406has traits => (is => 'ro', isa => ArrayRef|Str);
ad91060a 407
c34bcab6 408has _default_cursor_class => (
61ed82a5 409 is => 'ro',
410 isa => CursorClass,
411 default => 'DBIx::Class::Storage::DBI::Cursor',
412 coerce => 1
413);
414
0fbbc8d5 415sub BUILD {
416 my $self = shift;
417 my $class = ref $self;
2201c2e4 418 my $schema_class = $self->schema_class;
ad91060a 419
2201c2e4 420 if( !$self->connect_info ) {
f1613faa 421 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 422 $self->connect_info($schema_class->storage->connect_info);
f1613faa 423 }
424 else {
39f5f008 425 die "Either ->config->{connect_info} must be defined for $class"
460e3ac8 426 . " or $schema_class must have connect info defined on it."
427 . " Here's what we got:\n"
f1613faa 428 . Dumper($self);
429 }
7db6da78 430 }
431
0fbbc8d5 432 if (exists $self->connect_info->{cursor_class}) {
433 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
434 or croak "invalid connect_info: Cannot load your cursor_class"
435 . " ".$self->connect_info->{cursor_class}.": $@";
436 }
437
c34bcab6 438 $self->_plugin_ns('Trait');
0fbbc8d5 439
c34bcab6 440 $self->load_plugins($self->traits->flatten) if $self->traits;
0fbbc8d5 441
442 $self->setup;
443
f1613faa 444 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 445
f1613faa 446 $self->schema($self->composed_schema->clone);
447
2201c2e4 448 $self->schema->storage_type($self->storage_type)
449 if $self->storage_type;
7db6da78 450
2201c2e4 451 $self->schema->connection($self->connect_info);
452
453 $self->_install_rs_models;
c4fee9b8 454
455 $self->finalize;
2201c2e4 456}
457
458sub clone { shift->composed_schema->clone(@_); }
459
460sub connect { shift->composed_schema->connect(@_); }
461
462sub storage { shift->schema->storage(@_); }
463
5ec3e6cb 464sub resultset { shift->schema->resultset(@_); }
465
c4fee9b8 466=head2 setup
2201c2e4 467
c4fee9b8 468Called at C<<BUILD>> time before configuration.
2201c2e4 469
470=cut
471
0fbbc8d5 472sub setup { 1 }
2201c2e4 473
c4fee9b8 474=head2 finalize
475
476Called at the end of C<BUILD> after everything has been configured.
477
478=cut
479
480sub finalize { 1 }
481
482=head2 ACCEPT_CONTEXT
2201c2e4 483
0fbbc8d5 484Point of extension for doing things at C<<$c->model>> time, returns the model
485instance, see L<Catalyst::Manual::Intro> for more information.
2201c2e4 486
0fbbc8d5 487=cut
2201c2e4 488
0fbbc8d5 489sub ACCEPT_CONTEXT { shift }
2201c2e4 490
491sub _install_rs_models {
492 my $self = shift;
7b1fe8c2 493 my $class = $self->_original_class_name;
2201c2e4 494
ad91060a 495 no strict 'refs';
39f5f008 496
497 my @sources = $self->schema->sources;
498
499 die "No sources found (did you forget to define your tables?)"
500 unless @sources;
501
502 foreach my $moniker (@sources) {
0b2a7108 503 my $classname = "${class}::$moniker";
7db6da78 504 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 505 shift;
2201c2e4 506 shift->model($self->model_name)->resultset($moniker);
ad91060a 507 }
508 }
2201c2e4 509}
ad91060a 510
61ed82a5 511sub _reset_cursor_class {
512 my $self = shift;
513
514 if ($self->storage->can('cursor_class')) {
515 $self->storage->cursor_class($self->_default_cursor_class)
516 if $self->storage->cursor_class ne $self->_default_cursor_class;
517 }
518}
519
50f488ec 520{
521 my %COMPOSED_CACHE;
522
523 sub composed_schema {
524 my $self = shift;
525 my $class = $self->_original_class_name;
526 my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
527
528 $$store = shift if @_;
529
530 return $$store
531 }
532}
533
0fbbc8d5 534__PACKAGE__->meta->make_immutable;
2201c2e4 535
ad91060a 536=head1 SEE ALSO
537
7b39f3f0 538General Catalyst Stuff:
539
540L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
541L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
542
543Stuff related to DBIC and this Model style:
544
545L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 546L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
547L<MooseX::Object::Pluggable>
ad91060a 548
c34bcab6 549Traits:
c4fee9b8 550
c34bcab6 551L<Catalyst::Model::DBIC::Schema::Trait::Caching>,
552L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
c4fee9b8 553
ad91060a 554=head1 AUTHOR
555
556Brandon L Black, C<blblack@gmail.com>
557
2ff00e2b 558Contributors:
559
560Rafael Kitover, C<<rkitover at cpan.org>>
561
ad91060a 562=head1 COPYRIGHT
563
564This program is free software, you can redistribute it and/or modify it
565under the same terms as Perl itself.
566
567=cut
568
5691;