2e4ed99c988c9ee51cc9fbb0df6e2953b61c4193
[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
7 our $VERSION = '0.24';
8
9 use Carp::Clan '^Catalyst::Model::DBIC::Schema';
10 use Data::Dumper;
11 use DBIx::Class ();
12 use Scalar::Util 'reftype';
13 use MooseX::ClassAttribute;
14 use Moose::Autobox;
15 use Moose::Util ();
16
17 use Catalyst::Model::DBIC::Schema::Types
18     qw/ConnectInfo SchemaClass CursorClass/;
19
20 use MooseX::Types::Moose qw/ArrayRef Str ClassName Undef/;
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 to the instance. Traits are L<Moose::Role>s.
298
299 They are relative to the C<< MyApp::Model::DB::Trait:: >>, then the C<<
300 Catalyst::Model::DBIC::Schema::Trait:: >> namespaces, unless prefixed with C<+>
301 in which case they are taken to be a fully qualified name. E.g.:
302
303     traits Caching
304     traits +MyApp::DB::Trait::Foo
305
306 A new instance is created at application time, so any consumed required
307 attributes, coercions and modifiers will work.
308
309 Traits are applied before setup, schema and connection are set.
310
311 C<ref $self> will be an anon class if any traits are applied, C<<
312 $self->_original_class_name >> will be the original class.
313
314 You cannot modify C<BUILD> in a trait, as that is when traits are applied,
315 modify L</setup> instead.
316
317 Traits that come with the distribution:
318
319 =over 4
320
321 =item L<Catalyst::Model::DBIC::Schema::Trait::Caching>
322
323 =item L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
324
325 =back
326
327 =head2 storage_type
328
329 Allows the use of a different C<storage_type> than what is set in your
330 C<schema_class> (which in turn defaults to C<::DBI> if not set in current
331 L<DBIx::Class>).  Completely optional, and probably unnecessary for most
332 people until other storage backends become available for L<DBIx::Class>.
333
334 =head1 METHODS
335
336 =head2 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 =head2 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 =head2 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 =head2 clone
357
358 Shortcut for ->composed_schema->clone
359
360 =head2 connect
361
362 Shortcut for ->composed_schema->connect
363
364 =head2 source
365
366 Shortcut for ->schema->source
367
368 =head2 class
369
370 Shortcut for ->schema->class
371
372 =head2 resultset
373
374 Shortcut for ->schema->resultset
375
376 =head2 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 has schema => (is => 'rw', isa => 'DBIx::Class::Schema');
384
385 has schema_class => (
386     is => 'ro',
387     isa => SchemaClass,
388     coerce => 1,
389     required => 1
390 );
391
392 has storage_type => (is => 'rw', isa => Str);
393
394 has connect_info => (is => 'ro', isa => ConnectInfo, coerce => 1);
395
396 has model_name => (is => 'ro', isa => Str, default => sub {
397     my $self = shift;
398
399     my $class = ref $self;
400     (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
401
402     $model_name
403 });
404
405 has traits => (is => 'ro', isa => ArrayRef|Str);
406
407 has _trait_fqns => (is => 'ro', isa => ArrayRef|Undef, lazy_build => 1);
408
409 has _default_cursor_class => (
410     is => 'ro',
411     isa => CursorClass,
412     default => 'DBIx::Class::Storage::DBI::Cursor',
413     coerce => 1
414 );
415
416 has _original_class_name => (
417     is => 'ro',
418     required => 1,
419     isa => Str,
420     default => sub { blessed $_[0] },
421 );
422
423 sub BUILD {
424     my $self = shift;
425     my $class = ref $self;
426     my $schema_class = $self->schema_class;
427
428     if( !$self->connect_info ) {
429         if($schema_class->storage && $schema_class->storage->connect_info) {
430             $self->connect_info($schema_class->storage->connect_info);
431         }
432         else {
433             die "Either ->config->{connect_info} must be defined for $class"
434                   . " or $schema_class must have connect info defined on it."
435                   . " Here's what we got:\n"
436                   . Dumper($self);
437         }
438     }
439
440     if (exists $self->connect_info->{cursor_class}) {
441         eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
442             or croak "invalid connect_info: Cannot load your cursor_class"
443         . " ".$self->connect_info->{cursor_class}.": $@";
444     }
445
446     Moose::Util::apply_all_roles($self, $self->_trait_fqns->flatten)
447         if $self->_trait_fqns;
448
449     $self->setup;
450
451     $self->composed_schema($schema_class->compose_namespace($class));
452
453     $self->schema($self->composed_schema->clone);
454
455     $self->schema->storage_type($self->storage_type)
456         if $self->storage_type;
457
458     $self->schema->connection($self->connect_info);
459
460     $self->_install_rs_models;
461
462     $self->finalize;
463 }
464
465 sub clone { shift->composed_schema->clone(@_); }
466
467 sub connect { shift->composed_schema->connect(@_); }
468
469 sub storage { shift->schema->storage(@_); }
470
471 sub resultset { shift->schema->resultset(@_); }
472
473 =head2 setup
474
475 Called at C<BUILD>> time before configuration.
476
477 =cut
478
479 sub setup { 1 }
480
481 =head2 finalize
482
483 Called at the end of C<BUILD> after everything has been configured.
484
485 =cut
486
487 sub finalize { 1 }
488
489 =head2 ACCEPT_CONTEXT
490
491 Point of extension for doing things at C<< $c->model >> time, returns the model
492 instance, see L<Catalyst::Manual::Intro> for more information.
493
494 =cut
495
496 sub ACCEPT_CONTEXT { shift }
497
498 sub _install_rs_models {
499     my $self  = shift;
500     my $class = $self->_original_class_name;
501
502     no strict 'refs';
503
504     my @sources = $self->schema->sources;
505
506     die "No sources found (did you forget to define your tables?)"
507         unless @sources;
508
509     foreach my $moniker (@sources) {
510         my $classname = "${class}::$moniker";
511         *{"${classname}::ACCEPT_CONTEXT"} = sub {
512             shift;
513             shift->model($self->model_name)->resultset($moniker);
514         }
515     }
516 }
517
518 sub _reset_cursor_class {
519     my $self = shift;
520
521     if ($self->storage->can('cursor_class')) {
522         $self->storage->cursor_class($self->_default_cursor_class)
523             if $self->storage->cursor_class ne $self->_default_cursor_class;
524     }
525 }
526
527 {
528     my %COMPOSED_CACHE;
529
530     sub composed_schema {
531         my $self = shift;
532         my $class = $self->_original_class_name;
533         my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
534
535         $$store = shift if @_;
536
537         return $$store
538     }
539 }
540
541 sub _build__trait_fqns {
542     my $self  = shift;
543     my $class = $self->_original_class_name;
544     my $base  = 'Trait';
545
546     my @names = $self->traits->flatten if $self->traits;
547     return unless @names;
548
549     my @search_ns = grep !/^(?:Moose|Class::MOP)::/,
550         $class->meta->class_precedence_list;
551         
552     my @traits;
553
554     OUTER: for my $name (@names) {
555         if ($name =~ /^\+(.*)/) {
556             push @traits, $1;
557             next;
558         }
559         for my $ns (@search_ns) {
560             my $full = "${ns}::${base}::${name}";
561             if (eval { Class::MOP::load_class($full) }) {
562                 push @traits, $full;
563                 next OUTER;
564             }
565         }
566     }
567
568     return @traits ? \@traits : undef;
569 }
570
571 __PACKAGE__->meta->make_immutable;
572
573 =head1 SEE ALSO
574
575 General Catalyst Stuff:
576
577 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
578 L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
579
580 Stuff related to DBIC and this Model style:
581
582 L<DBIx::Class>, L<DBIx::Class::Schema>,
583 L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
584 L<MooseX::Object::Pluggable>
585
586 Traits:
587
588 L<Catalyst::Model::DBIC::Schema::Trait::Caching>,
589 L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
590
591 =head1 AUTHOR
592
593 Brandon L Black, C<blblack@gmail.com>
594
595 Contributors:
596
597 Rafael Kitover, C<rkitover at cpan.org>
598
599 =head1 COPYRIGHT
600
601 This program is free software, you can redistribute it and/or modify it
602 under the same terms as Perl itself.
603
604 =cut
605
606 1;
607 # vim:sts=4 sw=4: