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