41a8fd01272aac07fd331b6112d153034a37e72d
[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 'MooseX::Object::Pluggable';
7
8 our $VERSION = '0.24';
9
10 use Carp::Clan '^Catalyst::Model::DBIC::Schema';
11 use Data::Dumper;
12 use DBIx::Class ();
13 use Scalar::Util 'reftype';
14 use MooseX::ClassAttribute;
15 use Moose::Autobox;
16
17 use Catalyst::Model::DBIC::Schema::Types qw/ConnectInfo SchemaClass/;
18 use MooseX::Types::Moose qw/ArrayRef Str ClassName/;
19
20 use namespace::clean -except => 'meta';
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 Authentication::Store::DBIC in MyApp.pm:
95
96   package MyApp;
97
98   use Catalyst qw/... Authentication::Store::DBIC/;
99
100   ...
101
102   __PACKAGE__->config->{authentication}{dbic} = {
103       user_class      => 'FilmDB::Actor',
104       user_field      => 'name',
105       password_field  => 'password'
106   }
107
108 C<< $c->model('Schema::Source') >> returns a L<DBIx::Class::ResultSet> for 
109 the source name parameter passed. To find out more about which methods can 
110 be called on a ResultSet, or how to add your own methods to it, please see 
111 the ResultSet documentation in the L<DBIx::Class> distribution.
112
113 Some examples are given below:
114
115   # to access schema methods directly:
116   $c->model('FilmDB')->schema->source(...);
117
118   # to access the source object, resultset, and class:
119   $c->model('FilmDB')->source(...);
120   $c->model('FilmDB')->resultset(...);
121   $c->model('FilmDB')->class(...);
122
123   # For resultsets, there's an even quicker shortcut:
124   $c->model('FilmDB::Actor')
125   # is the same as $c->model('FilmDB')->resultset('Actor')
126
127   # To get the composed schema for making new connections:
128   my $newconn = $c->model('FilmDB')->composed_schema->connect(...);
129
130   # Or the same thing via a convenience shortcut:
131   my $newconn = $c->model('FilmDB')->connect(...);
132
133   # or, if your schema works on different storage drivers:
134   my $newconn = $c->model('FilmDB')->composed_schema->clone();
135   $newconn->storage_type('::LDAP');
136   $newconn->connection(...);
137
138   # and again, a convenience shortcut
139   my $newconn = $c->model('FilmDB')->clone();
140   $newconn->storage_type('::LDAP');
141   $newconn->connection(...);
142
143 =head1 DESCRIPTION
144
145 This is a Catalyst Model for L<DBIx::Class::Schema>-based Models.  See
146 the documentation for L<Catalyst::Helper::Model::DBIC::Schema> for
147 information on generating these Models via Helper scripts.
148
149 When your Catalyst app starts up, a thin Model layer is created as an 
150 interface to your DBIC Schema. It should be clearly noted that the model 
151 object returned by C<< $c->model('FilmDB') >> is NOT itself a DBIC schema or 
152 resultset object, but merely a wrapper proving L<methods|/METHODS> to access 
153 the underlying schema. 
154
155 In addition to this model class, a shortcut class is generated for each 
156 source in the schema, allowing easy and direct access to a resultset of the 
157 corresponding type. These generated classes are even thinner than the model 
158 class, providing no public methods but simply hooking into Catalyst's 
159 model() accessor via the 
160 L<ACCEPT_CONTEXT|Catalyst::Component/ACCEPT_CONTEXT> mechanism. The complete 
161 contents of each generated class is roughly equivalent to the following:
162
163   package MyApp::Model::FilmDB::Actor
164   sub ACCEPT_CONTEXT {
165       my ($self, $c) = @_;
166       $c->model('FilmDB')->resultset('Actor');
167   }
168
169 In short, there are three techniques available for obtaining a DBIC 
170 resultset object: 
171
172   # the long way
173   my $rs = $c->model('FilmDB')->schema->resultset('Actor');
174
175   # using the shortcut method on the model object
176   my $rs = $c->model('FilmDB')->resultset('Actor');
177
178   # using the generated class directly
179   my $rs = $c->model('FilmDB::Actor');
180
181 In order to add methods to a DBIC resultset, you cannot simply add them to 
182 the source (row, table) definition class; you must define a separate custom 
183 resultset class. See L<DBIx::Class::Manual::Cookbook/"Predefined searches"> 
184 for more info.
185
186 =head1 CONFIG PARAMETERS
187
188 =head2 schema_class
189
190 This is the classname of your L<DBIx::Class::Schema> Schema.  It needs
191 to be findable in C<@INC>, but it does not need to be inside the 
192 C<Catalyst::Model::> namespace.  This parameter is required.
193
194 =head2 connect_info
195
196 This is an arrayref of connection parameters, which are specific to your
197 C<storage_type> (see your storage type documentation for more details). 
198 If you only need one parameter (e.g. the DSN), you can just pass a string 
199 instead of an arrayref.
200
201 This is not required if C<schema_class> already has connection information
202 defined inside itself (which isn't highly recommended, but can be done)
203
204 For L<DBIx::Class::Storage::DBI>, which is the only supported
205 C<storage_type> in L<DBIx::Class> at the time of this writing, the
206 parameters are your dsn, username, password, and connect options hashref.
207
208 See L<DBIx::Class::Storage::DBI/connect_info> for a detailed explanation
209 of the arguments supported.
210
211 Examples:
212
213   connect_info => {
214     dsn => 'dbi:Pg:dbname=mypgdb',
215     user => 'postgres',
216     password => ''
217   }
218
219   connect_info => {
220     dsn => 'dbi:SQLite:dbname=foo.db',
221     on_connect_do => [
222       'PRAGMA synchronous = OFF',
223     ]
224   }
225
226   connect_info => {
227     dsn => 'dbi:Pg:dbname=mypgdb',
228     user => 'postgres',
229     password => '',
230     pg_enable_utf8 => 1,
231     on_connect_do => [
232       'some SQL statement',
233       'another SQL statement',
234     ],
235   }
236
237 Or using L<Config::General>:
238
239     <Model::FilmDB>
240         schema_class   MyApp::Schema::FilmDB
241         roles Caching
242         <connect_info>
243             dsn   dbi:Pg:dbname=mypgdb
244             user   postgres
245             password ''
246             auto_savepoint 1
247             on_connect_do   some SQL statement
248             on_connect_do   another SQL statement
249         </connect_info>
250     </Model::FilmDB>
251
252 or
253
254     <Model::FilmDB>
255         schema_class   MyApp::Schema::FilmDB
256         connect_info   dbi:SQLite:dbname=foo.db
257     </Model::FilmDB>
258
259 Or using L<YAML>:
260
261   Model::MyDB:
262       schema_class: MyDB
263       connect_info:
264           dsn: dbi:Oracle:mydb
265           user: mtfnpy
266           password: mypass
267           LongReadLen: 1000000
268           LongTruncOk: 1
269           on_connect_do: [ "alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS'" ]
270           cursor_class: 'DBIx::Class::Cursor::Cached'
271
272 The old arrayref style with hashrefs for L<DBI> then L<DBIx::Class> options is also
273 supported:
274
275   connect_info => [
276     'dbi:Pg:dbname=mypgdb',
277     'postgres',
278     '',
279     {
280       pg_enable_utf8 => 1,
281     },
282     {
283       auto_savepoint => 1,
284       on_connect_do => [
285         'some SQL statement',
286         'another SQL statement',
287       ],
288     }
289   ]
290
291 =head2 roles
292
293 Array of Roles to apply at BUILD time. Roles are relative to the
294 C<<MyApp::Model::DB::Role::> then C<<Catalyst::Model::DBIC::Schema::Role::>>
295 namespaces, unless prefixed with C<+> in which case they are taken to be a
296 fully qualified name. E.g.:
297
298     roles Caching
299     roles +MyApp::DB::Role::Foo
300
301 This is done using L<MooseX::Object::Pluggable>.
302
303 A new instance is created at application time, so any consumed required
304 attributes, coercions and modifiers will work.
305
306 Roles are applied before setup, schema and connection are set.
307
308 C<ref $self> will be an anon class if any roles are applied.
309
310 You cannot modify C<new> or C<BUILD>, modify C<setup> instead.
311
312 L</ACCEPT_CONTEXT> and L</finalize> can also be modified.
313
314 Roles that come with the distribution:
315
316 =over 4
317
318 =item L<Catalyst::Model::DBIC::Schema::Role::Caching>
319
320 =item L<Catalyst::Model::DBIC::Schema::Role::Replicated>
321
322 =back
323
324 =head2 storage_type
325
326 Allows the use of a different C<storage_type> than what is set in your
327 C<schema_class> (which in turn defaults to C<::DBI> if not set in current
328 L<DBIx::Class>).  Completely optional, and probably unnecessary for most
329 people until other storage backends become available for L<DBIx::Class>.
330
331 =head1 METHODS
332
333 =head2 new
334
335 Instantiates the Model based on the above-documented ->config parameters.
336 The only required parameter is C<schema_class>.  C<connect_info> is
337 required in the case that C<schema_class> does not already have connection
338 information defined for it.
339
340 =head2 schema
341
342 Accessor which returns the connected schema being used by the this model.
343 There are direct shortcuts on the model class itself for
344 schema->resultset, schema->source, and schema->class.
345
346 =head2 composed_schema
347
348 Accessor which returns the composed schema, which has no connection info,
349 which was used in constructing the C<schema> above.  Useful for creating
350 new connections based on the same schema/model.  There are direct shortcuts
351 from the model object for composed_schema->clone and composed_schema->connect
352
353 =head2 clone
354
355 Shortcut for ->composed_schema->clone
356
357 =head2 connect
358
359 Shortcut for ->composed_schema->connect
360
361 =head2 source
362
363 Shortcut for ->schema->source
364
365 =head2 class
366
367 Shortcut for ->schema->class
368
369 =head2 resultset
370
371 Shortcut for ->schema->resultset
372
373 =head2 storage
374
375 Provides an accessor for the connected schema's storage object.
376 Used often for debugging and controlling transactions.
377
378 =cut
379
380 class_has 'composed_schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
381
382 has 'schema' => (is => 'rw', isa => 'DBIx::Class::Schema');
383
384 has 'schema_class' => (
385     is => 'ro',
386     isa => SchemaClass,
387     coerce => 1,
388     required => 1
389 );
390
391 has 'storage_type' => (is => 'rw', isa => Str);
392
393 has 'connect_info' => (is => 'ro', isa => ConnectInfo, coerce => 1);
394
395 # ref $self changes to anon after roles are applied, and _original_class_name is
396 # broken in MX::O::P 0.0009
397 has '_class_name' => (is => 'ro', isa => ClassName, default => sub {
398     ref shift
399 });
400
401 has 'model_name' => (is => 'ro', isa => Str, default => sub {
402     my $self = shift;
403
404     my $class = ref $self;
405     (my $model_name = $class) =~ s/^[\w:]+::(?:Model|M):://;
406
407     $model_name
408 });
409
410 has 'roles' => (is => 'ro', isa => ArrayRef|Str);
411
412 sub BUILD {
413     my $self = shift;
414     my $class = ref $self;
415     my $schema_class = $self->schema_class;
416
417     if( !$self->connect_info ) {
418         if($schema_class->storage && $schema_class->storage->connect_info) {
419             $self->connect_info($schema_class->storage->connect_info);
420         }
421         else {
422             die "Either ->config->{connect_info} must be defined for $class"
423                   . " or $schema_class must have connect info defined on it."
424                   . " Here's what we got:\n"
425                   . Dumper($self);
426         }
427     }
428
429     if (exists $self->connect_info->{cursor_class}) {
430         eval { Class::MOP::load_class($self->connect_info->{cursor_class}) }
431             or croak "invalid connect_info: Cannot load your cursor_class"
432         . " ".$self->connect_info->{cursor_class}.": $@";
433     }
434
435     $self->_plugin_ns('Role');
436
437     $self->load_plugins($self->roles->flatten) if $self->roles;
438
439     $self->setup;
440
441     $self->composed_schema($schema_class->compose_namespace($class));
442
443     $self->schema($self->composed_schema->clone);
444
445     $self->schema->storage_type($self->storage_type)
446         if $self->storage_type;
447
448     $self->schema->connection($self->connect_info);
449
450     $self->_install_rs_models;
451
452     $self->finalize;
453 }
454
455 sub clone { shift->composed_schema->clone(@_); }
456
457 sub connect { shift->composed_schema->connect(@_); }
458
459 sub storage { shift->schema->storage(@_); }
460
461 =head2 setup
462
463 Called at C<<BUILD>> time before configuration.
464
465 =cut
466
467 sub setup { 1 }
468
469 =head2 finalize
470
471 Called at the end of C<BUILD> after everything has been configured.
472
473 =cut
474
475 sub finalize { 1 }
476
477 =head2 ACCEPT_CONTEXT
478
479 Point of extension for doing things at C<<$c->model>> time, returns the model
480 instance, see L<Catalyst::Manual::Intro> for more information.
481
482 =cut
483
484 sub ACCEPT_CONTEXT { shift }
485
486 sub _install_rs_models {
487     my $self  = shift;
488     my $class = $self->_class_name;
489
490     no strict 'refs';
491
492     my @sources = $self->schema->sources;
493
494     die "No sources found (did you forget to define your tables?)"
495         unless @sources;
496
497     foreach my $moniker (@sources) {
498         my $classname = "${class}::$moniker";
499         *{"${classname}::ACCEPT_CONTEXT"} = sub {
500             shift;
501             shift->model($self->model_name)->resultset($moniker);
502         }
503     }
504 }
505
506 __PACKAGE__->meta->make_immutable;
507
508 =head1 SEE ALSO
509
510 General Catalyst Stuff:
511
512 L<Catalyst::Manual>, L<Catalyst::Test>, L<Catalyst::Request>,
513 L<Catalyst::Response>, L<Catalyst::Helper>, L<Catalyst>,
514
515 Stuff related to DBIC and this Model style:
516
517 L<DBIx::Class>, L<DBIx::Class::Schema>,
518 L<DBIx::Class::Schema::Loader>, L<Catalyst::Helper::Model::DBIC::Schema>,
519 L<MooseX::Object::Pluggable>
520
521 Roles:
522
523 L<Catalyst::Model::DBIC::Schema::Role::Caching>,
524 L<Catalyst::Model::DBIC::Schema::Role::Replicated>
525
526 =head1 AUTHOR
527
528 Brandon L Black, C<blblack@gmail.com>
529
530 Contributors:
531
532 Rafael Kitover, C<<rkitover at cpan.org>>
533
534 =head1 COPYRIGHT
535
536 This program is free software, you can redistribute it and/or modify it
537 under the same terms as Perl itself.
538
539 =cut
540
541 1;