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