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