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