more cleanliness improvements to schema-loader
[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 base qw/DBIx::Class::Schema/;
7
8 use Carp;
9 use Lingua::EN::Inflect;
10
11 require DBIx::Class::Core;
12
13 __PACKAGE__->mk_classaccessor('_loader_inflect');
14 __PACKAGE__->mk_classaccessor('_loader_db_schema');
15 __PACKAGE__->mk_classaccessor('_loader_drop_db_schema');
16 __PACKAGE__->mk_classaccessor('_loader_classes' => {} );
17 __PACKAGE__->mk_classaccessor('_loader_monikers' => {} );
18 __PACKAGE__->mk_classaccessor('_loader_debug' => 0);
19
20 =head1 NAME
21
22 DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
23
24 =head1 SYNOPSIS
25
26 See L<DBIx::Class::Schema::Loader>
27
28 =head1 DESCRIPTION
29
30 =head2 OPTIONS
31
32 Available constructor options are:
33
34 =head3 additional_base_classes
35
36 List of additional base classes your table classes will use.
37
38 =head3 left_base_classes
39
40 List of additional base classes, that need to be leftmost.
41
42 =head3 additional_classes
43
44 List of additional classes which your table classes will use.
45
46 =head3 constraint
47
48 Only load tables matching regex.
49
50 =head3 exclude
51
52 Exclude tables matching regex.
53
54 =head3 debug
55
56 Enable debug messages.
57
58 =head3 dsn
59
60 DBI Data Source Name.
61
62 =head3 password
63
64 Password.
65
66 =head3 relationships
67
68 Try to automatically detect/setup has_a and has_many relationships.
69
70 =head3 inflect
71
72 An hashref, which contains exceptions to Lingua::EN::Inflect::PL().
73 Useful for foreign language column names.
74
75 =head3 user
76
77 Username.
78
79 =head2 METHODS
80
81 =cut
82
83 =head3 new
84
85 Not intended to be called directly.  This is used internally by the
86 C<new()> method in L<DBIx::Class::Schema::Loader>.
87
88 =cut
89
90 sub _load_from_connection {
91     my ( $class, %args ) = @_;
92
93     $class->_loader_debug(1) if $args{debug};
94     $class->_loader_inflect($args{inflect});
95     $class->_loader_db_schema($args{db_schema} || '');
96     $class->_loader_drop_db_schema($args{drop_db_schema});
97
98     my $additional = $args{additional_classes} || [];
99     $additional = [$additional] unless ref $additional eq 'ARRAY';
100
101     my $additional_base = $args{additional_base_classes} || [];
102     $additional_base = [$additional_base]
103       unless ref $additional_base eq 'ARRAY';
104
105     my $left_base = $args{left_base_classes} || [];
106     $left_base = [$left_base] unless ref $left_base eq 'ARRAY';
107
108     my %load_classes_args = (
109         additional      => $additional,
110         additional_base => $additional_base,
111         left_base       => $left_base,
112         constraint      => $args{constraint} || '.*',
113         exclude         => $args{exclude},
114     );
115
116     $class->connection($args{dsn}, $args{user},
117                        $args{password}, $args{options});
118
119     warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/
120         if $class->_loader_debug;
121
122     $class->_loader_load_classes(%load_classes_args);
123     $class->_loader_relationships if $args{relationships};
124
125     warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/
126         if $class->_loader_debug;
127     $class->storage->dbh->disconnect; # XXX this should be ->storage->disconnect later?
128
129     1;
130 }
131
132 # The original table class name during Loader,
133 sub _loader_find_table_class {
134     my ( $class, $table ) = @_;
135     return $class->_loader_classes->{$table};
136 }
137
138 # Returns the moniker for a given table name,
139 # for use in $conn->resultset($moniker)
140
141 =head3 moniker
142
143 Returns the moniker for a given literal table name.  Used
144 as $schema->resultset($moniker), etc.
145
146 =cut
147 sub moniker {
148     my ( $class, $table ) = @_;
149     return $class->_loader_monikers->{$table};
150 }
151
152 =head3 tables
153
154 Returns a sorted list of tables.
155
156     my @tables = $loader->tables;
157
158 =cut
159
160 sub tables {
161     my $class = shift;
162     return sort keys %{ $class->_loader_monikers };
163 }
164
165 # Overload in your driver class
166 sub _loader_db_classes { croak "ABSTRACT METHOD" }
167
168 # not a class method.
169 sub _loader_stringify_hash {
170     my $href = shift;
171
172     return '{ ' .
173            join(q{, }, map("$_ => $href->{$_}", keys %$href))
174            . ' }';
175 }
176
177 # Setup has_a and has_many relationships
178 sub _loader_make_relations {
179
180     my ( $class, $table, $other, $cond ) = @_;
181     my $table_class = $class->_loader_find_table_class($table);
182     my $other_class = $class->_loader_find_table_class($other);
183
184     my $table_relname = lc $table;
185     my $other_relname = lc $other;
186
187     if(my $inflections = $class->_loader_inflect) {
188         $table_relname = $inflections->{$table_relname}
189           if exists $inflections->{$table_relname};
190     }
191     else {
192         $table_relname = Lingua::EN::Inflect::PL($table_relname);
193     }
194
195     # for single-column case, set the relname to the column name,
196     # to make filter accessors work
197     if(scalar keys %$cond == 1) {
198         my ($col) = keys %$cond;
199         $other_relname = $cond->{$col};
200     }
201
202     my $rev_cond = { reverse %$cond };
203
204     my $cond_printable = _loader_stringify_hash($cond)
205         if $class->_loader_debug;
206     my $rev_cond_printable = _loader_stringify_hash($rev_cond)
207         if $class->_loader_debug;
208
209     warn qq/\# Belongs_to relationship\n/ if $class->_loader_debug;
210
211     warn qq/$table_class->belongs_to( '$other_relname' => '$other_class',/
212       .  qq/$cond_printable);\n\n/
213       if $class->_loader_debug;
214
215     $table_class->belongs_to( $other_relname => $other_class, $cond);
216
217     warn qq/\# Has_many relationship\n/ if $class->_loader_debug;
218
219     warn qq/$other_class->has_many( '$table_relname' => '$table_class',/
220       .  qq/$rev_cond_printable);\n\n/
221       .  qq/);\n\n/
222       if $class->_loader_debug;
223
224     $other_class->has_many( $table_relname => $table_class, $rev_cond);
225 }
226
227 # Load and setup classes
228 sub _loader_load_classes {
229     my ($class, %args)  = @_;
230
231     my $additional      = join '',
232                           map "use $_;\n", @{$args{additional}};
233
234     my @tables          = $class->_loader_tables();
235     my @db_classes      = $class->_loader_db_classes();
236
237     foreach my $table (@tables) {
238         next unless $table =~ /$args{constraint}/;
239         next if defined $args{exclude} && $table =~ /$args{exclude}/;
240
241         my ($db_schema, $tbl) = split /\./, $table;
242         my $tablename = lc $table;
243         if($tbl) {
244             $tablename = $class->_loader_drop_db_schema ? $tbl : lc $table;
245         }
246         my $lc_tblname = lc $tablename;
247
248         my $table_moniker = $class->_loader_table2moniker($db_schema, $tbl);
249         my $table_class = "$class\::$table_moniker";
250
251         # XXX all of this needs require/eval error checking
252         $class->inject_base( $table_class, 'DBIx::Class::Core' );
253         $_->require for @db_classes;
254         $class->inject_base( $table_class, $_ ) for @db_classes;
255         $class->inject_base( $table_class, $_ ) for @{$args{additional_base}};
256         eval "package $table_class;$_;"         for @{$args{additional}};
257         $class->inject_base( $table_class, $_ ) for @{$args{left_base}};
258
259         warn qq/\# Initializing table "$tablename" as "$table_class"\n/ if $class->_loader_debug;
260         $table_class->table($lc_tblname);
261
262         my ( $cols, $pks ) = $class->_loader_table_info($table);
263         carp("$table has no primary key") unless @$pks;
264         $table_class->add_columns(@$cols);
265         $table_class->set_primary_key(@$pks) if @$pks;
266
267         warn qq/$table_class->table('$tablename');\n/ if $class->_loader_debug;
268         my $columns = join "', '", @$cols;
269         warn qq/$table_class->add_columns('$columns')\n/ if $class->_loader_debug;
270         my $primaries = join "', '", @$pks;
271         warn qq/$table_class->set_primary_key('$primaries')\n/ if $class->_loader_debug && @$pks;
272
273         $class->register_class($table_moniker, $table_class);
274         $class->_loader_classes->{$lc_tblname} = $table_class;
275         $class->_loader_monikers->{$lc_tblname} = $table_moniker;
276     }
277 }
278
279 # Find and setup relationships
280 sub _loader_relationships {
281     my $class = shift;
282     my $dbh = $class->storage->dbh;
283     my $quoter = $dbh->get_info(29) || q{"};
284     foreach my $table ( $class->tables ) {
285         my $rels = {};
286         my $sth = $dbh->foreign_key_info( '',
287             $class->_loader_db_schema, '', '', '', $table );
288         next if !$sth;
289         while(my $raw_rel = $sth->fetchrow_hashref) {
290             my $uk_tbl  = lc $raw_rel->{UK_TABLE_NAME};
291             my $uk_col  = lc $raw_rel->{UK_COLUMN_NAME};
292             my $fk_col  = lc $raw_rel->{FK_COLUMN_NAME};
293             $uk_tbl =~ s/$quoter//g;
294             $uk_col =~ s/$quoter//g;
295             $fk_col =~ s/$quoter//g;
296             $rels->{$uk_tbl}->{$uk_col} = $fk_col;
297         }
298
299         foreach my $reltbl (keys %$rels) {
300             my $cond = $rels->{$reltbl};
301             eval { $class->_loader_make_relations( $table, $reltbl, $cond ) };
302               warn qq/\# belongs_to_many failed "$@"\n\n/
303                 if $@ && $class->_loader_debug;
304         }
305     }
306 }
307
308 # Make a moniker from a table
309 sub _loader_table2moniker {
310     my ( $class, $db_schema, $table ) = @_;
311
312     my $db_schema_ns;
313
314     if($table) {
315         $db_schema = ucfirst lc $db_schema;
316         $db_schema_ns = $db_schema if(!$class->_loader_drop_db_schema);
317     } else {
318         $table = $db_schema;
319     }
320
321     my $moniker = join '', map ucfirst, split /[\W_]+/, lc $table;
322     $moniker = $db_schema_ns ? $db_schema_ns . $moniker : $moniker;
323
324     return $moniker;
325 }
326
327 # Overload in driver class
328 sub _loader_tables { croak "ABSTRACT METHOD" }
329
330 sub _loader_table_info { croak "ABSTRACT METHOD" }
331
332 =head1 SEE ALSO
333
334 L<DBIx::Class::Schema::Loader>
335
336 =cut
337
338 1;