reshuffling the division of labor between Storage and Storage::DBI (not complete)
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema.pm
1 package DBIx::Class::Schema;
2
3 use strict;
4 use warnings;
5
6 use Carp::Clan qw/^DBIx::Class/;
7 use Scalar::Util qw/weaken/;
8
9 use base qw/DBIx::Class/;
10
11 __PACKAGE__->mk_classdata('class_mappings' => {});
12 __PACKAGE__->mk_classdata('source_registrations' => {});
13 __PACKAGE__->mk_classdata('storage_type' => '::DBI');
14 __PACKAGE__->mk_classdata('storage');
15 __PACKAGE__->mk_classdata('exception_action');
16
17 =head1 NAME
18
19 DBIx::Class::Schema - composable schemas
20
21 =head1 SYNOPSIS
22
23   package Library::Schema;
24   use base qw/DBIx::Class::Schema/;
25
26   # load Library::Schema::CD, Library::Schema::Book, Library::Schema::DVD
27   __PACKAGE__->load_classes(qw/CD Book DVD/);
28
29   package Library::Schema::CD;
30   use base qw/DBIx::Class/;
31   __PACKAGE__->load_components(qw/PK::Auto Core/); # for example
32   __PACKAGE__->table('cd');
33
34   # Elsewhere in your code:
35   my $schema1 = Library::Schema->connect(
36     $dsn,
37     $user,
38     $password,
39     { AutoCommit => 0 },
40   );
41
42   my $schema2 = Library::Schema->connect($coderef_returning_dbh);
43
44   # fetch objects using Library::Schema::DVD
45   my $resultset = $schema1->resultset('DVD')->search( ... );
46   my @dvd_objects = $schema2->resultset('DVD')->search( ... );
47
48 =head1 DESCRIPTION
49
50 Creates database classes based on a schema. This is the recommended way to
51 use L<DBIx::Class> and allows you to use more than one concurrent connection
52 with your classes.
53
54 NB: If you're used to L<Class::DBI> it's worth reading the L</SYNOPSIS>
55 carefully, as DBIx::Class does things a little differently. Note in
56 particular which module inherits off which.
57
58 =head1 METHODS
59
60 =head2 register_class
61
62 =over 4
63
64 =item Arguments: $moniker, $component_class
65
66 =back
67
68 Registers a class which isa DBIx::Class::ResultSourceProxy. Equivalent to
69 calling:
70
71   $schema->register_source($moniker, $component_class->result_source_instance);
72
73 =cut
74
75 sub register_class {
76   my ($self, $moniker, $to_register) = @_;
77   $self->register_source($moniker => $to_register->result_source_instance);
78 }
79
80 =head2 register_source
81
82 =over 4
83
84 =item Arguments: $moniker, $result_source
85
86 =back
87
88 Registers the L<DBIx::Class::ResultSource> in the schema with the given
89 moniker.
90
91 =cut
92
93 sub register_source {
94   my ($self, $moniker, $source) = @_;
95   my %reg = %{$self->source_registrations};
96   $reg{$moniker} = $source;
97   $self->source_registrations(\%reg);
98   $source->schema($self);
99   weaken($source->{schema}) if ref($self);
100   if ($source->result_class) {
101     my %map = %{$self->class_mappings};
102     $map{$source->result_class} = $moniker;
103     $self->class_mappings(\%map);
104   }
105 }
106
107 =head2 class
108
109 =over 4
110
111 =item Arguments: $moniker
112
113 =item Return Value: $classname
114
115 =back
116
117 Retrieves the result class name for the given moniker. For example:
118
119   my $class = $schema->class('CD');
120
121 =cut
122
123 sub class {
124   my ($self, $moniker) = @_;
125   return $self->source($moniker)->result_class;
126 }
127
128 =head2 source
129
130 =over 4
131
132 =item Arguments: $moniker
133
134 =item Return Value: $result_source
135
136 =back
137
138   my $source = $schema->source('Book');
139
140 Returns the L<DBIx::Class::ResultSource> object for the registered moniker.
141
142 =cut
143
144 sub source {
145   my ($self, $moniker) = @_;
146   my $sreg = $self->source_registrations;
147   return $sreg->{$moniker} if exists $sreg->{$moniker};
148
149   # if we got here, they probably passed a full class name
150   my $mapped = $self->class_mappings->{$moniker};
151   $self->throw_exception("Can't find source for ${moniker}")
152     unless $mapped && exists $sreg->{$mapped};
153   return $sreg->{$mapped};
154 }
155
156 =head2 sources
157
158 =over 4
159
160 =item Return Value: @source_monikers
161
162 =back
163
164 Returns the source monikers of all source registrations on this schema.
165 For example:
166
167   my @source_monikers = $schema->sources;
168
169 =cut
170
171 sub sources { return keys %{shift->source_registrations}; }
172
173 =head2 resultset
174
175 =over 4
176
177 =item Arguments: $moniker
178
179 =item Return Value: $result_set
180
181 =back
182
183   my $rs = $schema->resultset('DVD');
184
185 Returns the L<DBIx::Class::ResultSet> object for the registered moniker.
186
187 =cut
188
189 sub resultset {
190   my ($self, $moniker) = @_;
191   return $self->source($moniker)->resultset;
192 }
193
194 =head2 load_classes
195
196 =over 4
197
198 =item Arguments: @classes?, { $namespace => [ @classes ] }+
199
200 =back
201
202 With no arguments, this method uses L<Module::Find> to find all classes under
203 the schema's namespace. Otherwise, this method loads the classes you specify
204 (using L<use>), and registers them (using L</"register_class">).
205
206 It is possible to comment out classes with a leading C<#>, but note that perl
207 will think it's a mistake (trying to use a comment in a qw list), so you'll
208 need to add C<no warnings 'qw';> before your load_classes call.
209
210 Example:
211
212   My::Schema->load_classes(); # loads My::Schema::CD, My::Schema::Artist,
213                               # etc. (anything under the My::Schema namespace)
214
215   # loads My::Schema::CD, My::Schema::Artist, Other::Namespace::Producer but
216   # not Other::Namespace::LinerNotes nor My::Schema::Track
217   My::Schema->load_classes(qw/ CD Artist #Track /, {
218     Other::Namespace => [qw/ Producer #LinerNotes /],
219   });
220
221 =cut
222
223 sub load_classes {
224   my ($class, @params) = @_;
225
226   my %comps_for;
227
228   if (@params) {
229     foreach my $param (@params) {
230       if (ref $param eq 'ARRAY') {
231         # filter out commented entries
232         my @modules = grep { $_ !~ /^#/ } @$param;
233
234         push (@{$comps_for{$class}}, @modules);
235       }
236       elsif (ref $param eq 'HASH') {
237         # more than one namespace possible
238         for my $comp ( keys %$param ) {
239           # filter out commented entries
240           my @modules = grep { $_ !~ /^#/ } @{$param->{$comp}};
241
242           push (@{$comps_for{$comp}}, @modules);
243         }
244       }
245       else {
246         # filter out commented entries
247         push (@{$comps_for{$class}}, $param) if $param !~ /^#/;
248       }
249     }
250   } else {
251     eval "require Module::Find;";
252     $class->throw_exception(
253       "No arguments to load_classes and couldn't load Module::Find ($@)"
254     ) if $@;
255     my @comp = map { substr $_, length "${class}::"  }
256                  Module::Find::findallmod($class);
257     $comps_for{$class} = \@comp;
258   }
259
260   my @to_register;
261   {
262     no warnings qw/redefine/;
263     local *Class::C3::reinitialize = sub { };
264     foreach my $prefix (keys %comps_for) {
265       foreach my $comp (@{$comps_for{$prefix}||[]}) {
266         my $comp_class = "${prefix}::${comp}";
267         $class->ensure_class_loaded($comp_class);
268         $comp_class->source_name($comp) unless $comp_class->source_name;
269
270         push(@to_register, [ $comp_class->source_name, $comp_class ]);
271       }
272     }
273   }
274   Class::C3->reinitialize;
275
276   foreach my $to (@to_register) {
277     $class->register_class(@$to);
278     #  if $class->can('result_source_instance');
279   }
280 }
281
282 =head2 compose_connection
283
284 =over 4
285
286 =item Arguments: $target_namespace, @db_info
287
288 =item Return Value: $new_schema
289
290 =back
291
292 Calls L<DBIx::Class::Schema/"compose_namespace"> to the target namespace,
293 calls L<DBIx::Class::Schema/connection> with @db_info on the new schema,
294 then injects the L<DBix::Class::ResultSetProxy> component and a
295 resultset_instance classdata entry on all the new classes, in order to support
296 $target_namespaces::$class->search(...) method calls.
297
298 This is primarily useful when you have a specific need for class method access
299 to a connection. In normal usage it is preferred to call
300 L<DBIx::Class::Schema/connect> and use the resulting schema object to operate
301 on L<DBIx::Class::ResultSet> objects with L<DBIx::Class::Schema/resultset> for
302 more information.
303
304 =cut
305
306 sub compose_connection {
307   my ($self, $target, @info) = @_;
308   my $base = 'DBIx::Class::ResultSetProxy';
309   eval "require ${base};";
310   $self->throw_exception
311     ("No arguments to load_classes and couldn't load ${base} ($@)")
312       if $@;
313
314   if ($self eq $target) {
315     # Pathological case, largely caused by the docs on early C::M::DBIC::Plain
316     foreach my $moniker ($self->sources) {
317       my $source = $self->source($moniker);
318       my $class = $source->result_class;
319       $self->inject_base($class, $base);
320       $class->mk_classdata(resultset_instance => $source->resultset);
321       $class->mk_classdata(class_resolver => $self);
322     }
323     $self->connection(@info);
324     return $self;
325   }
326
327   my $schema = $self->compose_namespace($target, $base);
328   {
329     no strict 'refs';
330     *{"${target}::schema"} = sub { $schema };
331   }
332
333   $schema->connection(@info);
334   foreach my $moniker ($schema->sources) {
335     my $source = $schema->source($moniker);
336     my $class = $source->result_class;
337     #warn "$moniker $class $source ".$source->storage;
338     $class->mk_classdata(result_source_instance => $source);
339     $class->mk_classdata(resultset_instance => $source->resultset);
340     $class->mk_classdata(class_resolver => $schema);
341   }
342   return $schema;
343 }
344
345 =head2 compose_namespace
346
347 =over 4
348
349 =item Arguments: $target_namespace, $additional_base_class?
350
351 =item Return Value: $new_schema
352
353 =back
354
355 For each L<DBIx::Class::ResultSource> in the schema, this method creates a
356 class in the target namespace (e.g. $target_namespace::CD,
357 $target_namespace::Artist) that inherits from the corresponding classes
358 attached to the current schema.
359
360 It also attaches a corresponding L<DBIx::Class::ResultSource> object to the
361 new $schema object. If C<$additional_base_class> is given, the new composed
362 classes will inherit from first the corresponding classe from the current
363 schema then the base class.
364
365 For example, for a schema with My::Schema::CD and My::Schema::Artist classes,
366
367   $schema->compose_namespace('My::DB', 'Base::Class');
368   print join (', ', @My::DB::CD::ISA) . "\n";
369   print join (', ', @My::DB::Artist::ISA) ."\n";
370
371 will produce the output
372
373   My::Schema::CD, Base::Class
374   My::Schema::Artist, Base::Class
375
376 =cut
377
378 sub compose_namespace {
379   my ($self, $target, $base) = @_;
380   my %reg = %{ $self->source_registrations };
381   my %target;
382   my %map;
383   my $schema = $self->clone;
384   {
385     no warnings qw/redefine/;
386     local *Class::C3::reinitialize = sub { };
387     foreach my $moniker ($schema->sources) {
388       my $source = $schema->source($moniker);
389       my $target_class = "${target}::${moniker}";
390       $self->inject_base(
391         $target_class => $source->result_class, ($base ? $base : ())
392       );
393       $source->result_class($target_class);
394       $target_class->result_source_instance($source)
395         if $target_class->can('result_source_instance');
396     }
397   }
398   Class::C3->reinitialize();
399   {
400     no strict 'refs';
401     foreach my $meth (qw/class source resultset/) {
402       *{"${target}::${meth}"} =
403         sub { shift->schema->$meth(@_) };
404     }
405   }
406   return $schema;
407 }
408
409 =head2 setup_connection_class
410
411 =over 4
412
413 =item Arguments: $target, @info
414
415 =back
416
417 Sets up a database connection class to inject between the schema and the
418 subclasses that the schema creates.
419
420 =cut
421
422 sub setup_connection_class {
423   my ($class, $target, @info) = @_;
424   $class->inject_base($target => 'DBIx::Class::DB');
425   #$target->load_components('DB');
426   $target->connection(@info);
427 }
428
429 =head2 connection
430
431 =over 4
432
433 =item Arguments: @args
434
435 =item Return Value: $new_schema
436
437 =back
438
439 Instantiates a new Storage object of type
440 L<DBIx::Class::Schema/"storage_type"> and passes the arguments to
441 $storage->connect_info. Sets the connection in-place on the schema. See
442 L<DBIx::Class::Storage::DBI/"connect_info"> for more information.
443
444 =cut
445
446 sub connection {
447   my ($self, @info) = @_;
448   return $self if !@info && $self->storage;
449   my $storage_class = $self->storage_type;
450   $storage_class = 'DBIx::Class::Storage'.$storage_class
451     if $storage_class =~ m/^::/;
452   eval "require ${storage_class};";
453   $self->throw_exception(
454     "No arguments to load_classes and couldn't load ${storage_class} ($@)"
455   ) if $@;
456   my $storage = $storage_class->new($self);
457   $storage->connect_info(\@info);
458   $self->storage($storage);
459   return $self;
460 }
461
462 =head2 connect
463
464 =over 4
465
466 =item Arguments: @info
467
468 =item Return Value: $new_schema
469
470 =back
471
472 This is a convenience method. It is equivalent to calling
473 $schema->clone->connection(@info). See L</connection> and L</clone> for more
474 information.
475
476 =cut
477
478 sub connect { shift->clone->connection(@_) }
479
480 =head2 txn_do
481
482 =over 4
483
484 =item Arguments: C<$coderef>, @coderef_args?
485
486 =item Return Value: The return value of $coderef
487
488 =back
489
490 Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
491 returning its result (if any). Equivalent to calling $schema->storage->txn_do.
492 See L<DBIx::Class::Storage/"txn_do"> for more information.
493
494 This interface is preferred over using the individual methods L</txn_begin>,
495 L</txn_commit>, and L</txn_rollback> below.
496
497 =cut
498
499 sub txn_do {
500   my $self = shift;
501
502   $self->storage or $self->throw_exception
503     ('txn_do called on $schema without storage');
504
505   $self->storage->txn_do(@_);
506 }
507
508 =head2 txn_begin
509
510 Begins a transaction (does nothing if AutoCommit is off). Equivalent to
511 calling $schema->storage->txn_begin. See
512 L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
513
514 =cut
515
516 sub txn_begin {
517   my $self = shift;
518
519   $self->storage or $self->throw_exception
520     ('txn_begin called on $schema without storage');
521
522   $self->storage->txn_begin;
523 }
524
525 =head2 txn_commit
526
527 Commits the current transaction. Equivalent to calling
528 $schema->storage->txn_commit. See L<DBIx::Class::Storage::DBI/"txn_commit">
529 for more information.
530
531 =cut
532
533 sub txn_commit {
534   my $self = shift;
535
536   $self->storage or $self->throw_exception
537     ('txn_commit called on $schema without storage');
538
539   $self->storage->txn_commit;
540 }
541
542 =head2 txn_rollback
543
544 Rolls back the current transaction. Equivalent to calling
545 $schema->storage->txn_rollback. See
546 L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
547
548 =cut
549
550 sub txn_rollback {
551   my $self = shift;
552
553   $self->storage or $self->throw_exception
554     ('txn_rollback called on $schema without storage');
555
556   $self->storage->txn_rollback;
557 }
558
559 =head2 clone
560
561 =over 4
562
563 =item Return Value: $new_schema
564
565 =back
566
567 Clones the schema and its associated result_source objects and returns the
568 copy.
569
570 =cut
571
572 sub clone {
573   my ($self) = @_;
574   my $clone = { (ref $self ? %$self : ()) };
575   bless $clone, (ref $self || $self);
576
577   foreach my $moniker ($self->sources) {
578     my $source = $self->source($moniker);
579     my $new = $source->new($source);
580     $clone->register_source($moniker => $new);
581   }
582   $clone->storage->set_schema($clone) if $clone->storage;
583   return $clone;
584 }
585
586 =head2 populate
587
588 =over 4
589
590 =item Arguments: $moniker, \@data;
591
592 =back
593
594 Populates the source registered with the given moniker with the supplied data.
595 @data should be a list of listrefs -- the first containing column names, the
596 second matching values.
597
598 i.e.,
599
600   $schema->populate('Artist', [
601     [ qw/artistid name/ ],
602     [ 1, 'Popular Band' ],
603     [ 2, 'Indie Band' ],
604     ...
605   ]);
606
607 =cut
608
609 sub populate {
610   my ($self, $name, $data) = @_;
611   my $rs = $self->resultset($name);
612   my @names = @{shift(@$data)};
613   my @created;
614   foreach my $item (@$data) {
615     my %create;
616     @create{@names} = @$item;
617     push(@created, $rs->create(\%create));
618   }
619   return @created;
620 }
621
622 =head2 exception_action
623
624 =over 4
625
626 =item Arguments: $code_reference
627
628 =back
629
630 If C<exception_action> is set for this class/object, L</throw_exception>
631 will prefer to call this code reference with the exception as an argument,
632 rather than its normal <croak> action.
633
634 Your subroutine should probably just wrap the error in the exception
635 object/class of your choosing and rethrow.  If, against all sage advice,
636 you'd like your C<exception_action> to suppress a particular exception
637 completely, simply have it return true.
638
639 Example:
640
641    package My::Schema;
642    use base qw/DBIx::Class::Schema/;
643    use My::ExceptionClass;
644    __PACKAGE__->exception_action(sub { My::ExceptionClass->throw(@_) });
645    __PACKAGE__->load_classes;
646
647    # or:
648    my $schema_obj = My::Schema->connect( .... );
649    $schema_obj->exception_action(sub { My::ExceptionClass->throw(@_) });
650
651    # suppress all exceptions, like a moron:
652    $schema_obj->exception_action(sub { 1 });
653
654 =head2 throw_exception
655
656 =over 4
657
658 =item Arguments: $message
659
660 =back
661
662 Throws an exception. Defaults to using L<Carp::Clan> to report errors from
663 user's perspective.  See L</exception_action> for details on overriding
664 this method's behavior.
665
666 =cut
667
668 sub throw_exception {
669   my $self = shift;
670   croak @_ if !$self->exception_action || !$self->exception_action->(@_);
671 }
672
673 =head2 deploy (EXPERIMENTAL)
674
675 =over 4
676
677 =item Arguments: $sqlt_args
678
679 =back
680
681 Attempts to deploy the schema to the current storage using L<SQL::Translator>.
682
683 Note that this feature is currently EXPERIMENTAL and may not work correctly
684 across all databases, or fully handle complex relationships.
685
686 See L<SQL::Translator/METHODS> for a list of values for C<$sqlt_args>. The most
687 common value for this would be C<< { add_drop_table => 1, } >> to have the SQL
688 produced include a DROP TABLE statement for each table created.
689
690 =cut
691
692 sub deploy {
693   my ($self, $sqltargs) = @_;
694   $self->throw_exception("Can't deploy without storage") unless $self->storage;
695   $self->storage->deploy($self, undef, $sqltargs);
696 }
697
698 =head2 create_ddl_dir (EXPERIMENTAL)
699
700 =over 4
701
702 =item Arguments: \@databases, $version, $directory, $sqlt_args
703
704 =back
705
706 Creates an SQL file based on the Schema, for each of the specified
707 database types, in the given directory.
708
709 Note that this feature is currently EXPERIMENTAL and may not work correctly
710 across all databases, or fully handle complex relationships.
711
712 =cut
713
714 sub create_ddl_dir
715 {
716   my $self = shift;
717
718   $self->throw_exception("Can't create_ddl_dir without storage") unless $self->storage;
719   $self->storage->create_ddl_dir($self, @_);
720 }
721
722 =head2 ddl_filename (EXPERIMENTAL)
723
724   my $filename = $table->ddl_filename($type, $dir, $version)
725
726 Creates a filename for a SQL file based on the table class name.  Not
727 intended for direct end user use.
728
729 =cut
730
731 sub ddl_filename
732 {
733     my ($self, $type, $dir, $version) = @_;
734
735     my $filename = ref($self);
736     $filename =~ s/::/-/;
737     $filename = "$dir$filename-$version-$type.sql";
738
739     return $filename;
740 }
741
742 1;
743
744 =head1 AUTHORS
745
746 Matt S. Trout <mst@shadowcatsystems.co.uk>
747
748 =head1 LICENSE
749
750 You may distribute this code under the same terms as Perl itself.
751
752 =cut
753