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