C::H::M::DBIC::Schema - convert to Moose
[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
0fbbc8d5 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
0fbbc8d5 17use Catalyst::Model::DBIC::Schema::Types qw/ConnectInfo SchemaClass/;
18
19use namespace::clean -except => 'meta';
f1613faa 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
187=over 4
188
189=item schema_class
190
191This is the classname of your L<DBIx::Class::Schema> Schema. It needs
aabc1d75 192to be findable in C<@INC>, but it does not need to be inside the
193C<Catalyst::Model::> namespace. This parameter is required.
ad91060a 194
195=item connect_info
196
197This is an arrayref of connection parameters, which are specific to your
b9a72351 198C<storage_type> (see your storage type documentation for more details).
199If you only need one parameter (e.g. the DSN), you can just pass a string
200instead of an arrayref.
ad91060a 201
0f2fd2c0 202This is not required if C<schema_class> already has connection information
d89e6c8a 203defined inside itself (which isn't highly recommended, but can be done)
0f2fd2c0 204
7db6da78 205For L<DBIx::Class::Storage::DBI>, which is the only supported
206C<storage_type> in L<DBIx::Class> at the time of this writing, the
207parameters are your dsn, username, password, and connect options hashref.
208
018eb0e2 209See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
210of the arguments supported.
7db6da78 211
212Examples:
213
2201c2e4 214 connect_info => {
215 dsn => 'dbi:Pg:dbname=mypgdb',
216 user => 'postgres',
217 password => ''
218 }
07edc53e 219
2201c2e4 220 connect_info => {
221 dsn => 'dbi:SQLite:dbname=foo.db',
222 on_connect_do => [
223 'PRAGMA synchronous = OFF',
224 ]
225 }
07edc53e 226
2201c2e4 227 connect_info => {
228 dsn => 'dbi:Pg:dbname=mypgdb',
229 user => 'postgres',
230 password => '',
231 pg_enable_utf8 => 1,
232 on_connect_do => [
233 'some SQL statement',
234 'another SQL statement',
235 ],
236 }
7db6da78 237
8281c933 238Or using L<Config::General>:
239
240 <Model::FilmDB>
241 schema_class MyApp::Schema::FilmDB
f090a149 242 roles Caching
8281c933 243 <connect_info>
2201c2e4 244 dsn dbi:Pg:dbname=mypgdb
245 user postgres
246 password ''
247 auto_savepoint 1
8281c933 248 on_connect_do some SQL statement
249 on_connect_do another SQL statement
250 </connect_info>
251 </Model::FilmDB>
252
253or
254
255 <Model::FilmDB>
256 schema_class MyApp::Schema::FilmDB
257 connect_info dbi:SQLite:dbname=foo.db
258 </Model::FilmDB>
259
2201c2e4 260Or using L<YAML>:
261
262 Model::MyDB:
263 schema_class: MyDB
264 connect_info:
265 dsn: dbi:Oracle:mydb
266 user: mtfnpy
267 password: mypass
268 LongReadLen: 1000000
269 LongTruncOk: 1
270 on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
271 cursor_class: 'DBIx::Class::Cursor::Cached'
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
0fbbc8d5 292=item roles
293
294Array of Roles to apply at BUILD time. Roles are relative to the
295C<<MyApp::Model::DB::Role::> then C<<Catalyst::Model::DBIC::Schema::Role::>>
296namespaces, unless prefixed with C<+> in which case they are taken to be a
297fully qualified name. E.g.:
2201c2e4 298
0fbbc8d5 299 roles Caching
300 roles +MyApp::DB::Role::Foo
2201c2e4 301
0fbbc8d5 302This is done using L<MooseX::Object::Pluggable>.
2201c2e4 303
0fbbc8d5 304A new instance is created at application time, so any consumed required
305attributes, coercions and modifiers will work.
2201c2e4 306
0fbbc8d5 307Roles are applied before setup, schema and connection are set, and have a chance
308to modify C<connect_info>.
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
f090a149 314L</ACCEPT_CONTEXT> 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
322=back
8281c933 323
ad91060a 324=item storage_type
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
331=back
332
333=head1 METHODS
334
335=over 4
336
337=item new
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
f1613faa 344=item schema
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
350=item composed_schema
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
357=item clone
358
359Shortcut for ->composed_schema->clone
360
361=item connect
362
363Shortcut for ->composed_schema->connect
364
365=item source
c12b7310 366
f1613faa 367Shortcut for ->schema->source
368
369=item class
370
371Shortcut for ->schema->class
372
373=item resultset
374
375Shortcut for ->schema->resultset
376
377=item storage
378
379Provides an accessor for the connected schema's storage object.
380Used often for debugging and controlling transactions.
b8427e0b 381
ad91060a 382=cut
383
2ff00e2b 384class_has 'composed_schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 385
2ff00e2b 386has 'schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
0fbbc8d5 387
388has 'schema_class' => (
389 is => 'ro',
390 isa => SchemaClass,
391 coerce => 1,
392 required => 1
393);
394
2ff00e2b 395has 'storage_type' => (is => 'ro', isa => 'Str');
0fbbc8d5 396
397has 'connect_info' => (is => 'ro', isa => ConnectInfo, coerce => 1);
398
399# ref $self changes to anon after roles are applied, and _original_class_name is
2ff00e2b 400# broken in MX::O::P 0.0009
0fbbc8d5 401has '_class_name' => (is => 'ro', isa => 'ClassName', default => sub {
402 ref shift
403});
404
2ff00e2b 405has 'model_name' => (is => 'ro', isa => 'Str', default => sub {
0fbbc8d5 406 my $self = shift;
407
2201c2e4 408 my $class = ref $self;
0fbbc8d5 409 (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
2201c2e4 410
0fbbc8d5 411 $model_name
412});
ad91060a 413
0fbbc8d5 414has 'roles' => (is => 'ro', isa => 'ArrayRef|Str');
ad91060a 415
0fbbc8d5 416sub BUILD {
417 my $self = shift;
418 my $class = ref $self;
2201c2e4 419 my $schema_class = $self->schema_class;
ad91060a 420
2201c2e4 421 if( !$self->connect_info ) {
f1613faa 422 if($schema_class->storage && $schema_class->storage->connect_info) {
2201c2e4 423 $self->connect_info($schema_class->storage->connect_info);
f1613faa 424 }
425 else {
426 croak "Either ->config->{connect_info} must be defined for $class"
460e3ac8 427 . " or $schema_class must have connect info defined on it."
428 . " Here's what we got:\n"
f1613faa 429 . Dumper($self);
430 }
7db6da78 431 }
432
0fbbc8d5 433 if (exists $self->connect_info->{cursor_class}) {
434 eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
435 or croak "invalid connect_info: Cannot load your cursor_class"
436 . " ".$self->connect_info->{cursor_class}.": $@";
437 }
438
439 $self->_plugin_ns('Role');
440
441 $self->load_plugins($self->roles->flatten) if $self->roles;
442
443 $self->setup;
444
f1613faa 445 $self->composed_schema($schema_class->compose_namespace($class));
2201c2e4 446
f1613faa 447 $self->schema($self->composed_schema->clone);
448
2201c2e4 449 $self->schema->storage_type($self->storage_type)
450 if $self->storage_type;
7db6da78 451
2201c2e4 452 $self->schema->connection($self->connect_info);
453
454 $self->_install_rs_models;
2201c2e4 455}
456
457sub clone { shift->composed_schema->clone(@_); }
458
459sub connect { shift->composed_schema->connect(@_); }
460
461sub storage { shift->schema->storage(@_); }
462
0fbbc8d5 463=item setup
2201c2e4 464
0fbbc8d5 465Called at C<<BUILD>> time, for modifying in roles/subclasses.
2201c2e4 466
467=cut
468
0fbbc8d5 469sub setup { 1 }
2201c2e4 470
0fbbc8d5 471=item ACCEPT_CONTEXT
2201c2e4 472
0fbbc8d5 473Point of extension for doing things at C<<$c->model>> time, returns the model
474instance, see L<Catalyst::Manual::Intro> for more information.
2201c2e4 475
0fbbc8d5 476=cut
2201c2e4 477
0fbbc8d5 478sub ACCEPT_CONTEXT { shift }
2201c2e4 479
480sub _install_rs_models {
481 my $self = shift;
0fbbc8d5 482 my $class = $self->_class_name;
2201c2e4 483
ad91060a 484 no strict 'refs';
485 foreach my $moniker ($self->schema->sources) {
0b2a7108 486 my $classname = "${class}::$moniker";
7db6da78 487 *{"${classname}::ACCEPT_CONTEXT"} = sub {
ad91060a 488 shift;
2201c2e4 489 shift->model($self->model_name)->resultset($moniker);
ad91060a 490 }
491 }
2201c2e4 492}
ad91060a 493
0fbbc8d5 494__PACKAGE__->meta->make_immutable;
2201c2e4 495
496=back
b8427e0b 497
ad91060a 498=head1 SEE ALSO
499
7b39f3f0 500General Catalyst Stuff:
501
502L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
503L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
504
505Stuff related to DBIC and this Model style:
506
507L<DBIx::Class>, L<DBIx::Class::Schema>,
f090a149 508L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
509L<MooseX::Object::Pluggable>
ad91060a 510
511=head1 AUTHOR
512
513Brandon L Black, C<blblack@gmail.com>
514
2ff00e2b 515Contributors:
516
517Rafael Kitover, C<<rkitover at cpan.org>>
518
ad91060a 519=head1 COPYRIGHT
520
521This program is free software, you can redistribute it and/or modify it
522under the same terms as Perl itself.
523
524=cut
525
5261;