Schema::Loader converted to better inheritance model, no longer pollutes user schema...
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Generic.pm
1 package DBIx::Class::Schema::Loader::Generic;
2
3 use strict;
4 use warnings;
5
6 use Carp;
7 use Lingua::EN::Inflect;
8 use base qw/Class::Accessor::Fast/;
9
10 require DBIx::Class::Core;
11
12 # The first group are all arguments which are may be defaulted within,
13 # The last two (classes, monikers) are generated locally:
14
15 __PACKAGE__->mk_ro_accessors(qw/
16                                 schema
17                                 dsn
18                                 user
19                                 password
20                                 options
21                                 exclude
22                                 constraint
23                                 additional_classes
24                                 additional_base_classes
25                                 left_base_classes
26                                 relationships
27                                 inflect
28                                 db_schema
29                                 drop_db_schema
30                                 debug
31
32                                 classes
33                                 monikers
34                              /);
35
36 =head1 NAME
37
38 DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
39
40 =head1 SYNOPSIS
41
42 See L<DBIx::Class::Schema::Loader>
43
44 =head1 DESCRIPTION
45
46 =head2 OPTIONS
47
48 Available constructor options are:
49
50 =head3 additional_base_classes
51
52 List of additional base classes your table classes will use.
53
54 =head3 left_base_classes
55
56 List of additional base classes, that need to be leftmost.
57
58 =head3 additional_classes
59
60 List of additional classes which your table classes will use.
61
62 =head3 constraint
63
64 Only load tables matching regex.
65
66 =head3 exclude
67
68 Exclude tables matching regex.
69
70 =head3 debug
71
72 Enable debug messages.
73
74 =head3 dsn
75
76 DBI Data Source Name.
77
78 =head3 password
79
80 Password.
81
82 =head3 relationships
83
84 Try to automatically detect/setup has_a and has_many relationships.
85
86 =head3 inflect
87
88 An hashref, which contains exceptions to Lingua::EN::Inflect::PL().
89 Useful for foreign language column names.
90
91 =head3 user
92
93 Username.
94
95 =head2 METHODS
96
97 =cut
98
99 =head3 new
100
101 Constructor for L<DBIx::Class::Schema::Loader::Generic>, used internally
102 by L<DBIx::Class::Schema::Loader>.
103
104 =cut
105
106 # ensure that a peice of object data is a valid arrayref, creating
107 # an empty one or encapsulating whatever's there.
108 sub _ensure_arrayref {
109     my $self = shift;
110
111     foreach (@_) {
112         $self->{$_} ||= [];
113         $self->{$_} = [ $self->{$_} ]
114             unless ref $self->{$_} eq 'ARRAY';
115     }
116 }
117
118 sub new {
119     my ( $class, %args ) = @_;
120
121     my $self = { %args };
122
123     bless $self => $class;
124
125     $self->{db_schema}  ||= '';
126     $self->{constraint} ||= '.*';
127     $self->{inflect}    ||= {};
128     $self->_ensure_arrayref(qw/additional_classes
129                                additional_base_classes
130                                left_base_classes/);
131
132     $self->{monikers} = {};
133     $self->{classes} = {};
134
135     $self->schema->connection($self->dsn, $self->user,
136                               $self->password, $self->options);
137
138     warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/
139         if $self->debug;
140
141     $self->_load_classes;
142     $self->_load_relationships if $self->relationships;
143
144     warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/
145         if $self->debug;
146     $self->schema->storage->dbh->disconnect; # XXX this should be ->storage->disconnect later?
147
148     $self;
149 }
150
151 # Overload in your driver class
152 sub _db_classes { croak "ABSTRACT METHOD" }
153
154 # Inflect a relationship name
155 #   XXX (should pluralize, but currently also tends to de-pluralize plurals)
156 sub _inflect_relname {
157     my ($self, $relname) = @_;
158
159     return $self->inflect->{$relname} if exists $self->inflect->{$relname};
160     return Lingua::EN::Inflect::PL($relname);
161 }
162
163 # Set up a simple relation with just a local col and foreign table
164 sub _make_simple_rel {
165     my ($self, $table, $other, $col) = @_;
166
167     my $table_class = $self->classes->{$table};
168     my $other_class = $self->classes->{$other};
169     my $table_relname = $self->_inflect_relname(lc $table);
170
171     warn qq/\# Belongs_to relationship\n/ if $self->debug;
172     warn qq/$table_class->belongs_to( '$col' => '$other_class' );\n\n/
173       if $self->debug;
174     $table_class->belongs_to( $col => $other_class );
175
176     warn qq/\# Has_many relationship\n/ if $self->debug;
177     warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
178       .  qq/$col);\n\n/
179       if $self->debug;
180
181     $other_class->has_many( $table_relname => $table_class, $col);
182 }
183
184 # not a class method, just a helper for cond_rel XXX
185 sub _stringify_hash {
186     my $href = shift;
187
188     return '{ ' .
189            join(q{, }, map("$_ => $href->{$_}", keys %$href))
190            . ' }';
191 }
192
193 # Set up a complex relation based on a hashref condition
194 sub _make_cond_rel {
195     my ( $self, $table, $other, $cond ) = @_;
196
197     my $table_class = $self->classes->{$table};
198     my $other_class = $self->classes->{$other};
199     my $table_relname = $self->_inflect_relname(lc $table);
200     my $other_relname = lc $other;
201
202     # for single-column case, set the relname to the column name,
203     # to make filter accessors work
204     if(scalar keys %$cond == 1) {
205         my ($col) = keys %$cond;
206         $other_relname = $cond->{$col};
207     }
208
209     my $rev_cond = { reverse %$cond };
210
211     my $cond_printable = _stringify_hash($cond)
212         if $self->debug;
213     my $rev_cond_printable = _stringify_hash($rev_cond)
214         if $self->debug;
215
216     warn qq/\# Belongs_to relationship\n/ if $self->debug;
217
218     warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/
219       .  qq/$cond_printable);\n\n/
220       if $self->debug;
221
222     $table_class->belongs_to( $other_relname => $other_class, $cond);
223
224     warn qq/\# Has_many relationship\n/ if $self->debug;
225
226     warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
227       .  qq/$rev_cond_printable);\n\n/
228       .  qq/);\n\n/
229       if $self->debug;
230
231     $other_class->has_many( $table_relname => $table_class, $rev_cond);
232 }
233
234 # Load and setup classes
235 sub _load_classes {
236     my $self = shift;
237
238     my @tables     = $self->_tables();
239     my @db_classes = $self->_db_classes();
240     my $schema     = $self->schema;
241
242     foreach my $table (@tables) {
243         my $constraint = $self->constraint;
244         my $exclude = $self->exclude;
245
246         next unless $table =~ /$constraint/;
247         next if defined $exclude && $table =~ /$exclude/;
248
249         my ($db_schema, $tbl) = split /\./, $table;
250         my $tablename = lc $table;
251         if($tbl) {
252             $tablename = $self->drop_db_schema ? $tbl : lc $table;
253         }
254         my $lc_tblname = lc $tablename;
255
256         my $table_moniker = $self->_table2moniker($db_schema, $tbl);
257         my $table_class = $schema . q{::} . $table_moniker;
258
259         # XXX all of this needs require/eval error checking
260         $schema->inject_base( $table_class, 'DBIx::Class::Core' );
261         $_->require for @db_classes;
262         $schema->inject_base( $table_class, $_ ) for @db_classes;
263         $schema->inject_base( $table_class, $_ )
264             for @{$self->additional_base_classes};
265         eval "package $table_class; use $_;"
266             for @{$self->additional_classes};
267         $schema->inject_base( $table_class, $_ )
268             for @{$self->left_base_classes};
269
270         warn qq/\# Initializing table "$tablename" as "$table_class"\n/
271             if $self->debug;
272         $table_class->table($lc_tblname);
273
274         my ( $cols, $pks ) = $self->_table_info($table);
275         carp("$table has no primary key") unless @$pks;
276         $table_class->add_columns(@$cols);
277         $table_class->set_primary_key(@$pks) if @$pks;
278
279         warn qq/$table_class->table('$tablename');\n/ if $self->debug;
280         my $columns = join "', '", @$cols;
281         warn qq/$table_class->add_columns('$columns')\n/ if $self->debug;
282         my $primaries = join "', '", @$pks;
283         warn qq/$table_class->set_primary_key('$primaries')\n/
284             if $self->debug && @$pks;
285
286         $schema->register_class($table_moniker, $table_class);
287         $self->classes->{$lc_tblname} = $table_class;
288         $self->monikers->{$lc_tblname} = $table_moniker;
289     }
290 }
291
292 =head3 tables
293
294 Returns a sorted list of tables.
295
296     my @tables = $loader->tables;
297
298 =cut
299
300 sub tables {
301     my $self = shift;
302
303     return sort keys %{ $self->monikers };
304 }
305
306 # Find and setup relationships
307 sub _load_relationships {
308     my $self = shift;
309
310     my $dbh = $self->schema->storage->dbh;
311     my $quoter = $dbh->get_info(29) || q{"};
312     foreach my $table ( $self->tables ) {
313         my $rels = {};
314         my $sth = $dbh->foreign_key_info( '',
315             $self->db_schema, '', '', '', $table );
316         next if !$sth;
317         while(my $raw_rel = $sth->fetchrow_hashref) {
318             my $uk_tbl  = lc $raw_rel->{UK_TABLE_NAME};
319             my $uk_col  = lc $raw_rel->{UK_COLUMN_NAME};
320             my $fk_col  = lc $raw_rel->{FK_COLUMN_NAME};
321             my $relid   = lc $raw_rel->{UK_NAME};
322             $uk_tbl =~ s/$quoter//g;
323             $uk_col =~ s/$quoter//g;
324             $fk_col =~ s/$quoter//g;
325             $relid  =~ s/$quoter//g;
326             $rels->{$relid}->{tbl} = $uk_tbl;
327             $rels->{$relid}->{cols}->{$uk_col} = $fk_col;
328         }
329
330         foreach my $relid (keys %$rels) {
331             my $reltbl = $rels->{$relid}->{tbl};
332             my $cond   = $rels->{$relid}->{cols};
333             eval { $self->_make_cond_rel( $table, $reltbl, $cond ) };
334               warn qq/\# belongs_to_many failed "$@"\n\n/
335                 if $@ && $self->debug;
336         }
337     }
338 }
339
340 # Make a moniker from a table
341 sub _table2moniker {
342     my ( $self, $db_schema, $table ) = @_;
343
344     my $db_schema_ns;
345
346     if($table) {
347         $db_schema = ucfirst lc $db_schema;
348         $db_schema_ns = $db_schema if(!$self->drop_db_schema);
349     } else {
350         $table = $db_schema;
351     }
352
353     my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table;
354     $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
355
356     return $moniker;
357 }
358
359 # Overload in driver class
360 sub _tables { croak "ABSTRACT METHOD" }
361
362 sub _table_info { croak "ABSTRACT METHOD" }
363
364 =head1 SEE ALSO
365
366 L<DBIx::Class::Schema::Loader>
367
368 =cut
369
370 1;