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