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