pod cleanup, release
[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.32';
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/;
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 the C<<
282 Catalyst::TraitFor::Model::DBIC::Schema:: >> namespaces, unless prefixed with C<+>
283 in which case they are taken to be a fully qualified name. E.g.:
284
285     traits Caching
286     traits +MyApp::TraitFor::Model::Foo
287
288 A new instance is created at application time, so any consumed required
289 attributes, coercions and modifiers will work.
290
291 Traits are applied at L<Catalyst::Component/COMPONENT> time using
292 L<CatalystX::Component::Traits>.
293
294 C<ref $self> will be an anon class if any traits are applied, C<<
295 $self->_original_class_name >> will be the original class.
296
297 When writing a Trait, interesting points to modify are C<BUILD>, L</setup> and
298 L</ACCEPT_CONTEXT>.
299
300 Traits that come with the distribution:
301
302 =over 4
303
304 =item L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>
305
306 =item L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>
307
308 =back
309
310 =head2 storage_type
311
312 Allows the use of a different C<storage_type> than what is set in your
313 C<schema_class> (which in turn defaults to C<::DBI> if not set in current
314 L<DBIx::Class>).  Completely optional, and probably unnecessary for most
315 people until other storage backends become available for L<DBIx::Class>.
316
317 =head1 ATTRIBUTES
318
319 The keys you pass in the model configuration are available as attributes.
320
321 Other attributes available:
322
323 =head2 connect_info
324
325 Your connect_info args normalized to hashref form (with dsn/user/password.) See
326 L<DBIx::Class::Storage::DBI/connect_info> for more info on the hashref form of
327 L</connect_info>.
328
329 =head2 model_name
330
331 The model name L<Catalyst> uses to resolve this model, the part after
332 C<::Model::> or C<::M::> in your class name. E.g. if your class name is
333 C<MyApp::Model::DB> the L</model_name> will be C<DB>.
334
335 =head2 _default_cursor_class
336
337 What to reset your L<DBIx::Class::Storage::DBI/cursor_class> to if a custom one
338 doesn't work out. Defaults to L<DBIx::Class::Storage::DBI::Cursor>.
339
340 =head1 ATTRIBUTES FROM L<MooseX::Traits::Pluggable>
341
342 =head2 _original_class_name
343
344 The class name of your model before any L</traits> are applied. E.g.
345 C<MyApp::Model::DB>.
346
347 =head2 _traits
348
349 Unresolved arrayref of traits passed in the config.
350
351 =head2 _resolved_traits
352
353 Traits you used resolved to full class names.
354
355 =head1 METHODS
356
357 Methods not listed here are delegated to the connected schema used by the model
358 instance, so the following are equivalent:
359
360     $c->model('DB')->schema->my_accessor('foo');
361     # or
362     $c->model('DB')->my_accessor('foo');
363
364 Methods on the model take precedence over schema methods.
365
366 =head2 new
367
368 Instantiates the Model based on the above-documented ->config parameters.
369 The only required parameter is C<schema_class>.  C<connect_info> is
370 required in the case that C<schema_class> does not already have connection
371 information defined for it.
372
373 =head2 schema
374
375 Accessor which returns the connected schema being used by the this model.
376 There are direct shortcuts on the model class itself for
377 schema->resultset, schema->source, and schema->class.
378
379 =head2 composed_schema
380
381 Accessor which returns the composed schema, which has no connection info,
382 which was used in constructing the C<schema> above.  Useful for creating
383 new connections based on the same schema/model.  There are direct shortcuts
384 from the model object for composed_schema->clone and composed_schema->connect
385
386 =head2 clone
387
388 Shortcut for ->composed_schema->clone
389
390 =head2 connect
391
392 Shortcut for ->composed_schema->connect
393
394 =head2 source
395
396 Shortcut for ->schema->source
397
398 =head2 class
399
400 Shortcut for ->schema->class
401
402 =head2 resultset
403
404 Shortcut for ->schema->resultset
405
406 =head2 storage
407
408 Provides an accessor for the connected schema's storage object.
409 Used often for debugging and controlling transactions.
410
411 =cut
412
413 has schema_class => (
414     is => 'ro',
415     isa => SchemaClass,
416     coerce => 1,
417     required => 1
418 );
419
420 has storage_type => (is => 'rw', isa => Str);
421
422 has connect_info => (is => 'rw', isa => ConnectInfo, coerce => 1);
423
424 has model_name => (
425     is => 'ro',
426     isa => Str,
427     required => 1,
428     lazy_build => 1,
429 );
430
431 has _default_cursor_class => (
432     is => 'ro',
433     isa => LoadedClass,
434     default => 'DBIx::Class::Storage::DBI::Cursor',
435     coerce => 1
436 );
437
438 sub BUILD {
439     my ($self, $args) = @_;
440     my $class = $self->_original_class_name;
441     my $schema_class = $self->schema_class;
442
443     if( !$self->connect_info ) {
444         if($schema_class->storage && $schema_class->storage->connect_info) {
445             $self->connect_info($schema_class->storage->connect_info);
446         }
447         else {
448             die "Either ->config->{connect_info} must be defined for $class"
449                   . " or $schema_class must have connect info defined on it."
450                   . " Here's what we got:\n"
451                   . Dumper($self);
452         }
453     }
454
455     if (exists $self->connect_info->{cursor_class}) {
456         eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
457             or croak "invalid connect_info: Cannot load your cursor_class"
458         . " ".$self->connect_info->{cursor_class}.": $@";
459     }
460
461     $self->setup;
462
463     $self->composed_schema($schema_class->compose_namespace($class));
464
465     my $was_mutable = $self->meta->is_mutable;
466
467     $self->meta->make_mutable;
468     $self->meta->add_attribute('schema',
469         is => 'rw',
470         isa => 'DBIx::Class::Schema',
471         handles => $self->_delegates
472     );
473     $self->meta->make_immutable unless $was_mutable;
474
475     $self->schema($self->composed_schema->clone);
476
477     $self->_pass_options_to_schema($args);
478
479     $self->schema->storage_type($self->storage_type)
480         if $self->storage_type;
481
482     $self->schema->connection($self->connect_info);
483
484     $self->_install_rs_models;
485 }
486
487 sub clone { shift->composed_schema->clone(@_); }
488
489 sub connect { shift->composed_schema->connect(@_); }
490
491 =head2 setup
492
493 Called at C<BUILD> time before configuration, but after L</connect_info> is
494 set. To do something after configuuration use C<< after BUILD => >>.
495
496 =cut
497
498 sub setup { 1 }
499
500 =head2 ACCEPT_CONTEXT
501
502 Point of extension for doing things at C<< $c->model >> time with context,
503 returns the model instance, see L<Catalyst::Manual::Intro/ACCEPT_CONTEXT> for
504 more information.
505
506 =cut
507
508 sub ACCEPT_CONTEXT { shift }
509
510 sub _install_rs_models {
511     my $self  = shift;
512     my $class = $self->_original_class_name;
513
514     no strict 'refs';
515
516     my @sources = $self->schema->sources;
517
518     unless (@sources) {
519         warn <<'EOF' unless $ENV{CMDS_NO_SOURCES};
520 ******************************* WARNING ***************************************
521 * No sources found (did you forget to define your tables?)                    *
522 *                                                                             *
523 * To turn off this warning, set the CMDS_NO_SOURCES environment variable.     *
524 *******************************************************************************
525 EOF
526     }
527
528     foreach my $moniker (@sources) {
529         my $classname = "${class}::$moniker";
530         *{"${classname}::ACCEPT_CONTEXT"} = sub {
531             shift;
532             shift->model($self->model_name)->resultset($moniker);
533         }
534     }
535 }
536
537 sub _reset_cursor_class {
538     my $self = shift;
539
540     if ($self->storage->can('cursor_class')) {
541         $self->storage->cursor_class($self->_default_cursor_class)
542             if $self->storage->cursor_class ne $self->_default_cursor_class;
543     }
544 }
545
546 {
547     my %COMPOSED_CACHE;
548
549     sub composed_schema {
550         my $self = shift;
551         my $class = $self->_original_class_name;
552         my $store = \$COMPOSED_CACHE{$class}{$self->schema_class};
553
554         $$store = shift if @_;
555
556         return $$store
557     }
558 }
559
560 sub _build_model_name {
561     my $self  = shift;
562     my $class = $self->_original_class_name;
563     (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
564
565     return $model_name;
566 }
567
568 sub _delegates {
569     my $self = shift;
570
571     my $schema_meta = Class::MOP::Class->initialize($self->schema_class);
572     my @schema_methods = $schema_meta->get_all_method_names;
573
574 # combine with any already added by other schemas
575     my @handles = eval {
576         @{ $self->meta->find_attribute_by_name('schema')->handles }
577     };
578
579 # now kill the attribute, otherwise add_attribute in BUILD will not do the right
580 # thing (it clears the handles for some reason.) May be a Moose bug.
581     eval { $self->meta->remove_attribute('schema') };
582
583     my %schema_methods;
584     @schema_methods{ @schema_methods, @handles } = ();
585     @schema_methods = keys %schema_methods;
586
587     my @my_methods = $self->meta->get_all_method_names;
588     my %my_methods;
589     @my_methods{@my_methods} = ();
590
591     my @delegates;
592     for my $method (@schema_methods) {
593         push @delegates, $method unless exists $my_methods{$method};
594     }
595
596     return \@delegates;
597 }
598
599 sub _pass_options_to_schema {
600     my ($self, $args) = @_;
601
602     my @attributes = map {
603         $_->init_arg || ()
604     } $self->meta->get_all_attributes;
605
606     my %attributes;
607     @attributes{@attributes} = ();
608
609     for my $opt (keys %$args) {
610         if (not exists $attributes{$opt}) {
611             next unless $self->schema->can($opt);
612             $self->schema->$opt($self->{$opt});
613         }
614     }
615 }
616
617 __PACKAGE__->meta->make_immutable;
618
619 =head1 ENVIRONMENT
620
621 =over 4
622
623 =item CMDS_NO_SOURCES
624
625 Set this variable if you will be using schemas with no sources (Result classes)
626 to disable the warning. The warning is there because having no Result classes
627 is usually a mistake.
628
629 =back
630
631 =head1 Setting up DBIC authentication
632
633 You can set this up with 
634 L<Catalyst::Authentication::Store::DBIx::Class> in MyApp.pm:
635
636   package MyApp;
637
638   use Catalyst qw/... Authentication .../;
639
640   ...
641
642   __PACKAGE__->config('Plugin::Authentication' =>
643                 {
644                     default_realm => 'members',
645                     members => {
646                         credential => {
647                             class => 'Password',
648                             password_field => 'password',
649                             password_type => 'hashed'
650                             password_hash_type => 'SHA-256'
651                         },
652                         store => {
653                             class => 'DBIx::Class',
654                             user_model => 'DB::User',
655                             role_relation => 'roles',
656                             role_field => 'rolename',
657                         }
658                     }
659                 });
660
661 =head1 SEE ALSO
662
663 General Catalyst Stuff:
664
665 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
666 L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
667
668 Stuff related to DBIC and this Model style:
669
670 L<DBIx::Class>, L<DBIx::Class::Schema>,
671 L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
672 L<CatalystX::Component::Traits>, L<MooseX::Traits::Pluggable>
673
674 Traits:
675
676 L<Catalyst::TraitFor::Model::DBIC::Schema::Caching>,
677 L<Catalyst::TraitFor::Model::DBIC::Schema::Replicated>,
678 L<Catalyst::TraitFor::Model::DBIC::Schema::QueryLog>
679
680 =head1 AUTHOR
681
682 Brandon L Black C<blblack at gmail.com>
683
684 =head1 CONTRIBUTORS
685
686 caelum: Rafael Kitover C<rkitover at cpan.org>
687
688 dandv: Dan Dascalescu C<dandv at cpan.org>
689
690 bluefeet: Aran Deltac C<bluefeet@cpan.org>
691
692 t0m: Tomas Doran C<bobtfish@bobtfish.net>
693
694 osfameron: C<osfameron@cpan.org>
695
696 ozum: Ozum Eldogan C<ozum@ozum.net>
697
698 =head1 COPYRIGHT
699
700 Copyright (c) 2006 - 2009
701 the Catalyst::Model::DBIC::Schema L</AUTHOR> and L</CONTRIBUTORS>
702 as listed above.
703
704 =head1 LICENSE
705
706 This program is free software. You can redistribute it and/or modify it
707 under the same terms as Perl itself.
708
709 =cut
710
711 1;
712 # vim:sts=4 sw=4 et: