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