cleanups, deps on mx::traits which is not released yet
[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 with 'MooseX::Traits';
6 extends 'Catalyst::Model';
7
8 our $VERSION = '0.24';
9
10 use namespace::autoclean;
11 use Carp::Clan '^Catalyst::Model::DBIC::Schema';
12 use Data::Dumper;
13 use DBIx::Class ();
14 use Moose::Autobox;
15
16 use Catalyst::Model::DBIC::Schema::Types
17     qw/ConnectInfo SchemaClass CursorClass/;
18
19 use MooseX::Types::Moose qw/ArrayRef Str ClassName Undef/;
20
21 =head1 NAME
22
23 Catalyst::Model::DBIC::Schema - DBIx::Class::Schema Model Class
24
25 =head1 SYNOPSIS
26
27 Manual creation of a DBIx::Class::Schema and a Catalyst::Model::DBIC::Schema:
28
29 =over
30
31 =item 1.
32
33 Create 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
42 Create some classes for the tables in the database, for example an 
43 Actor in MyApp/Schema/FilmDB/Actor.pm:
44
45   package MyApp::Schema::FilmDB::Actor;
46   use base qw/DBIx::Class/
47
48   __PACKAGE__->load_components(qw/Core/);
49   __PACKAGE__->table('actor');
50
51   ...
52
53 and a Role in MyApp/Schema/FilmDB/Role.pm:
54
55   package MyApp::Schema::FilmDB::Role;
56   use base qw/DBIx::Class/
57
58   __PACKAGE__->load_components(qw/Core/);
59   __PACKAGE__->table('role');
60
61   ...    
62
63 Notice that the schema is in MyApp::Schema, not in MyApp::Model. This way it's 
64 usable as a standalone module and you can test/run it without Catalyst. 
65
66 =item 3.
67
68 To expose it to Catalyst as a model, you should create a DBIC Model in
69 MyApp/Model/FilmDB.pm:
70
71   package MyApp::Model::FilmDB;
72   use base qw/Catalyst::Model::DBIC::Schema/;
73
74   __PACKAGE__->config(
75       schema_class => 'MyApp::Schema::FilmDB',
76       connect_info => {
77                         dsn => "DBI:...",
78                         user => "username",
79                         password => "password",
80                       }
81   );
82
83 See below for a full list of the possible config parameters.
84
85 =back
86
87 Now you have a working Model which accesses your separate DBIC Schema. This can
88 be used/accessed in the normal Catalyst manner, via $c->model():
89
90   my $actor = $c->model('FilmDB::Actor')->find(1);
91
92 You can also use it to set up DBIC authentication with 
93 Authentication::Store::DBIC in MyApp.pm:
94
95   package MyApp;
96
97   use Catalyst qw/... Authentication::Store::DBIC/;
98
99   ...
100
101   __PACKAGE__->config->{authentication}{dbic} = {
102       user_class      => 'FilmDB::Actor',
103       user_field      => 'name',
104       password_field  => 'password'
105   }
106
107 C<< $c->model('Schema::Source') >> returns a L<DBIx::Class::ResultSet> for 
108 the source name parameter passed. To find out more about which methods can 
109 be called on a ResultSet, or how to add your own methods to it, please see 
110 the ResultSet documentation in the L<DBIx::Class> distribution.
111
112 Some examples are given below:
113
114   # to access schema methods directly:
115   $c->model('FilmDB')->schema->source(...);
116
117   # to access the source object, resultset, and class:
118   $c->model('FilmDB')->source(...);
119   $c->model('FilmDB')->resultset(...);
120   $c->model('FilmDB')->class(...);
121
122   # For resultsets, there's an even quicker shortcut:
123   $c->model('FilmDB::Actor')
124   # is the same as $c->model('FilmDB')->resultset('Actor')
125
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
142 =head1 DESCRIPTION
143
144 This is a Catalyst Model for L<DBIx::Class::Schema>-based Models.  See
145 the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
146 information on generating these Models via Helper scripts.
147
148 When your Catalyst app starts up, a thin Model layer is created as an 
149 interface to your DBIC Schema. It should be clearly noted that the model 
150 object returned by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or 
151 resultset object, but merely a wrapper proving L<methods|/METHODS> to access 
152 the underlying schema. 
153
154 In addition to this model class, a shortcut class is generated for each 
155 source in the schema, allowing easy and direct access to a resultset of the 
156 corresponding type. These generated classes are even thinner than the model 
157 class, providing no public methods but simply hooking into Catalyst's 
158 model() accessor via the 
159 L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete 
160 contents 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
168 In short, there are three techniques available for obtaining a DBIC 
169 resultset 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
180 In order to add methods to a DBIC resultset, you cannot simply add them to 
181 the source (row, table) definition class; you must define a separate custom 
182 resultset class. See L<DBIx::Class::Manual::Cookbook/"Predefined searches"> 
183 for more info.
184
185 =head1 CONFIG PARAMETERS
186
187 =head2 schema_class
188
189 This is the classname of your L<DBIx::Class::Schema> Schema.  It needs
190 to be findable in C<@INC>, but it does not need to be inside the 
191 C<Catalyst::Model::> namespace.  This parameter is required.
192
193 =head2 connect_info
194
195 This is an arrayref of connection parameters, which are specific to your
196 C<storage_type> (see your storage type documentation for more details). 
197 If you only need one parameter (e.g. the DSN), you can just pass a string 
198 instead of an arrayref.
199
200 This is not required if C<schema_class> already has connection information
201 defined inside itself (which isn't highly recommended, but can be done)
202
203 For L<DBIx::Class::Storage::DBI>, which is the only supported
204 C<storage_type> in L<DBIx::Class> at the time of this writing, the
205 parameters are your dsn, username, password, and connect options hashref.
206
207 See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
208 of the arguments supported.
209
210 Examples:
211
212   connect_info => {
213     dsn => 'dbi:Pg:dbname=mypgdb',
214     user => 'postgres',
215     password => ''
216   }
217
218   connect_info => {
219     dsn => 'dbi:SQLite:dbname=foo.db',
220     on_connect_do => [
221       'PRAGMA synchronous = OFF',
222     ]
223   }
224
225   connect_info => {
226     dsn => 'dbi:Pg:dbname=mypgdb',
227     user => 'postgres',
228     password => '',
229     pg_enable_utf8 => 1,
230     on_connect_do => [
231       'some SQL statement',
232       'another SQL statement',
233     ],
234   }
235
236 Or using L<Config::General>:
237
238     <Model::FilmDB>
239         schema_class   MyApp::Schema::FilmDB
240         traits Caching
241         <connect_info>
242             dsn   dbi:Pg:dbname=mypgdb
243             user   postgres
244             password ""
245             auto_savepoint 1
246             quote_char """
247             on_connect_do   some SQL statement
248             on_connect_do   another SQL statement
249         </connect_info>
250     </Model::FilmDB>
251
252 or
253
254     <Model::FilmDB>
255         schema_class   MyApp::Schema::FilmDB
256         connect_info   dbi:SQLite:dbname=foo.db
257     </Model::FilmDB>
258
259 Or using L<YAML>:
260
261   Model::MyDB:
262       schema_class: MyDB
263       connect_info:
264           dsn: dbi:Oracle:mydb
265           user: mtfnpy
266           password: mypass
267           LongReadLen: 1000000
268           LongTruncOk: 1
269           on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
270           cursor_class: 'DBIx::Class::Cursor::Cached'
271           quote_char: '"'
272
273 The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
274 supported:
275
276   connect_info => [
277     'dbi:Pg:dbname=mypgdb',
278     'postgres',
279     '',
280     {
281       pg_enable_utf8 => 1,
282     },
283     {
284       auto_savepoint => 1,
285       on_connect_do => [
286         'some SQL statement',
287         'another SQL statement',
288       ],
289     }
290   ]
291
292 =head2 traits
293
294 Array of Traits to apply to the instance. Traits are L<Moose::Role>s.
295
296 They are relative to the C<< MyApp::Model::DB::Trait:: >>, then the C<<
297 Catalyst::Model::DBIC::Schema::Trait:: >> namespaces, unless prefixed with C<+>
298 in which case they are taken to be a fully qualified name. E.g.:
299
300     traits Caching
301     traits +MyApp::DB::Trait::Foo
302
303 A new instance is created at application time, so any consumed required
304 attributes, coercions and modifiers will work.
305
306 Traits are applied at L<Catalyst::Component/COMPONENT> time using L<MooseX::Traits>.
307
308 C<ref $self> will be an anon class if any traits are applied, C<<
309 $self->_original_class_name >> will be the original class.
310
311 When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
312 L</ACCEPT_CONTEXT>.
313
314 Traits that come with the distribution:
315
316 =over 4
317
318 =item L<Catalyst::Model::DBIC::Schema::Trait::Caching>
319
320 =item L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
321
322 =back
323
324 =head2 storage_type
325
326 Allows the use of a different C<storage_type> than what is set in your
327 C<schema_class> (which in turn defaults to C<::DBI> if not set in current
328 L<DBIx::Class>).  Completely optional, and probably unnecessary for most
329 people until other storage backends become available for L<DBIx::Class>.
330
331 =head1 ATTRIBUTES
332
333 The keys you pass in the model configuration are available as attributes.
334
335 Other attributes available:
336
337 =head2 connect_info
338
339 Your connect_info args normalized to hashref form (with dsn/user/password.) See
340 L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
341 L</connect_info>.
342
343 =head2 model_name
344
345 The model name L<Catalyst> uses to resolve this model, the part after
346 C<::Model::> or C<::M::> in your class name. E.g. if your class name is
347 C<MyApp::Model::DB> the L</model_name> will be C<DB>.
348
349 =head2 _original_class_name
350
351 The class name of your model before any L</traits> are applied. E.g.
352 C<MyApp::Model::DB>.
353
354 =head2 _default_cursor_class
355
356 What to rest your L<DBIx::Class::Storage::DBI/cursor_class> if a custom one
357 doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
358
359 =head2 _traits
360
361 Unresolved arrayref of traits passed at C<COMPONENT> time.
362
363 =head2 _resolved_traits
364
365 Traits you used resolved to full class names.
366
367 =head1 METHODS
368
369 =head2 new
370
371 Instantiates the Model based on the above-documented ->config parameters.
372 The only required parameter is C<schema_class>.  C<connect_info> is
373 required in the case that C<schema_class> does not already have connection
374 information defined for it.
375
376 =head2 schema
377
378 Accessor which returns the connected schema being used by the this model.
379 There are direct shortcuts on the model class itself for
380 schema->resultset, schema->source, and schema->class.
381
382 =head2 composed_schema
383
384 Accessor which returns the composed schema, which has no connection info,
385 which was used in constructing the C<schema> above.  Useful for creating
386 new connections based on the same schema/model.  There are direct shortcuts
387 from the model object for composed_schema->clone and composed_schema->connect
388
389 =head2 clone
390
391 Shortcut for ->composed_schema->clone
392
393 =head2 connect
394
395 Shortcut for ->composed_schema->connect
396
397 =head2 source
398
399 Shortcut for ->schema->source
400
401 =head2 class
402
403 Shortcut for ->schema->class
404
405 =head2 resultset
406
407 Shortcut for ->schema->resultset
408
409 =head2 storage
410
411 Provides an accessor for the connected schema's storage object.
412 Used often for debugging and controlling transactions.
413
414 =cut
415
416 has schema => (is => 'rw', isa => 'DBIx::Class::Schema');
417
418 has schema_class => (
419     is => 'ro',
420     isa => SchemaClass,
421     coerce => 1,
422     required => 1
423 );
424
425 has storage_type => (is => 'rw', isa => Str);
426
427 has connect_info => (is => 'ro', isa => ConnectInfo, coerce => 1);
428
429 has model_name => (
430     is => 'ro',
431     isa => Str,
432     required => 1,
433     lazy_build => 1,
434 );
435
436 has _traits => (is => 'ro', isa => ArrayRef);
437 has _resolved_traits => (is => 'ro', isa => ArrayRef);
438
439 has _default_cursor_class => (
440     is => 'ro',
441     isa => CursorClass,
442     default => 'DBIx::Class::Storage::DBI::Cursor',
443     coerce => 1
444 );
445
446 has _original_class_name => (
447     is => 'ro',
448     required => 1,
449     isa => Str,
450     default => sub { blessed $_[0] },
451 );
452
453 sub COMPONENT {
454     my ($class, $app, $args) = @_;
455
456     $args = $class->merge_config_hashes($class->config, $args);
457
458     if (my $traits = delete $args->{traits}) {
459         my @traits = $class->_resolve_traits($traits->flatten);
460         return $class->new_with_traits({
461             traits => \@traits,
462             _original_class_name => $class,
463             _traits => $traits,
464             _resolved_traits => \@traits,
465             %$args
466         });
467     }
468
469     return $class->new($args);
470 }
471
472 sub BUILD {
473     my $self = shift;
474     my $class = ref $self;
475     my $schema_class = $self->schema_class;
476
477     if( !$self->connect_info ) {
478         if($schema_class->storage && $schema_class->storage->connect_info) {
479             $self->connect_info($schema_class->storage->connect_info);
480         }
481         else {
482             die "Either ->config->{connect_info} must be defined for $class"
483                   . " or $schema_class must have connect info defined on it."
484                   . " Here's what we got:\n"
485                   . Dumper($self);
486         }
487     }
488
489     if (exists $self->connect_info->{cursor_class}) {
490         eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
491             or croak "invalid connect_info: Cannot load your cursor_class"
492         . " ".$self->connect_info->{cursor_class}.": $@";
493     }
494
495     $self->setup;
496
497     $self->composed_schema($schema_class->compose_namespace($class));
498
499     $self->schema($self->composed_schema->clone);
500
501     $self->schema->storage_type($self->storage_type)
502         if $self->storage_type;
503
504     $self->schema->connection($self->connect_info);
505
506     $self->_install_rs_models;
507 }
508
509 sub clone { shift->composed_schema->clone(@_); }
510
511 sub connect { shift->composed_schema->connect(@_); }
512
513 sub storage { shift->schema->storage(@_); }
514
515 sub resultset { shift->schema->resultset(@_); }
516
517 =head2 setup
518
519 Called at C<BUILD>> time before configuration, but after L</connect_info> is
520 set. To do something after configuuration use C<< after BUILD => >>.
521
522 =cut
523
524 sub setup { 1 }
525
526 =head2 ACCEPT_CONTEXT
527
528 Point of extension for doing things at C<< $c->model >> time with context,
529 returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
530 more information.
531
532 =cut
533
534 sub ACCEPT_CONTEXT { shift }
535
536 sub _install_rs_models {
537     my $self  = shift;
538     my $class = $self->_original_class_name;
539
540     no strict 'refs';
541
542     my @sources = $self->schema->sources;
543
544     die "No sources found (did you forget to define your tables?)"
545         unless @sources;
546
547     foreach my $moniker (@sources) {
548         my $classname = "${class}::$moniker";
549         *{"${classname}::ACCEPT_CONTEXT"} = sub {
550             shift;
551             shift->model($self->model_name)->resultset($moniker);
552         }
553     }
554 }
555
556 sub _reset_cursor_class {
557     my $self = shift;
558
559     if ($self->storage->can('cursor_class')) {
560         $self->storage->cursor_class($self->_default_cursor_class)
561             if $self->storage->cursor_class ne $self->_default_cursor_class;
562     }
563 }
564
565 {
566     my %COMPOSED_CACHE;
567
568     sub composed_schema {
569         my $self = shift;
570         my $class = $self->_original_class_name;
571         my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
572
573         $$store = shift if @_;
574
575         return $$store
576     }
577 }
578
579 sub _resolve_traits {
580     my ($class, @names) = @_;
581     my $base  = 'Trait';
582
583     my @search_ns = grep !/^(?:Moose|Class::MOP)::/,
584         $class->meta->class_precedence_list;
585         
586     my @traits;
587
588     OUTER: for my $name (@names) {
589         if ($name =~ /^\+(.*)/) {
590             push @traits, $1;
591             next;
592         }
593         for my $ns (@search_ns) {
594             my $full = "${ns}::${base}::${name}";
595             if (eval { Class::MOP::load_class($full) }) {
596                 push @traits, $full;
597                 next OUTER;
598             }
599         }
600     }
601
602     return @traits;
603 }
604
605 sub _build_model_name {
606     my $self  = shift;
607     my $class = $self->_original_class_name;
608     (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
609
610     return $model_name;
611 }
612
613 __PACKAGE__->meta->make_immutable;
614
615 =head1 SEE ALSO
616
617 General Catalyst Stuff:
618
619 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
620 L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
621
622 Stuff related to DBIC and this Model style:
623
624 L<DBIx::Class>, L<DBIx::Class::Schema>,
625 L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
626 L<MooseX::Object::Pluggable>
627
628 Traits:
629
630 L<Catalyst::Model::DBIC::Schema::Trait::Caching>,
631 L<Catalyst::Model::DBIC::Schema::Trait::Replicated>
632
633 =head1 AUTHOR
634
635 Brandon L Black, C<blblack at gmail.com>
636
637 Contributors:
638
639 Rafael Kitover, C<rkitover at cpan.org>
640
641 =head1 COPYRIGHT
642
643 This program is free software, you can redistribute it and/or modify it
644 under the same terms as Perl itself.
645
646 =cut
647
648 1;
649 # vim:sts=4 sw=4 et: