loader as a schema subclass basically working (needs some cleanup and docs and whatnot)
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / Generic.pm
CommitLineData
18fca96a 1package DBIx::Class::Schema::Loader::Generic;
a78e3fed 2
3use strict;
a4a19f3c 4use warnings;
5
6use base qw/DBIx::Class::Schema/;
7
a78e3fed 8use Carp;
9use Lingua::EN::Inflect;
a4a19f3c 10
a78e3fed 11require DBIx::Class::Core;
a4a19f3c 12
13__PACKAGE__->mk_classdata('loader_data');
14
15# XXX convert all usage of $class/$self->debug to ->debug_loader
a78e3fed 16
17=head1 NAME
18
18fca96a 19DBIx::Class::Schema::Loader::Generic - Generic DBIx::Class::Schema::Loader Implementation.
a78e3fed 20
21=head1 SYNOPSIS
22
18fca96a 23See L<DBIx::Class::Schema::Loader>
a78e3fed 24
25=head1 DESCRIPTION
26
27=head2 OPTIONS
28
29Available constructor options are:
30
31=head3 additional_base_classes
32
33List of additional base classes your table classes will use.
34
35=head3 left_base_classes
36
37List of additional base classes, that need to be leftmost.
38
39=head3 additional_classes
40
41List of additional classes which your table classes will use.
42
43=head3 constraint
44
45Only load tables matching regex.
46
47=head3 exclude
48
49Exclude tables matching regex.
50
51=head3 debug
52
53Enable debug messages.
54
55=head3 dsn
56
57DBI Data Source Name.
58
59=head3 namespace
60
61Namespace under which your table classes will be initialized.
62
63=head3 password
64
65Password.
66
67=head3 relationships
68
69Try to automatically detect/setup has_a and has_many relationships.
70
71=head3 inflect
72
73An hashref, which contains exceptions to Lingua::EN::Inflect::PL().
74Useful for foreign language column names.
75
76=head3 user
77
78Username.
79
80=head2 METHODS
81
82=cut
83
84=head3 new
85
86Not intended to be called directly. This is used internally by the
18fca96a 87C<new()> method in L<DBIx::Class::Schema::Loader>.
a78e3fed 88
89=cut
90
a4a19f3c 91sub _load_from_connection {
a78e3fed 92 my ( $class, %args ) = @_;
93 if ( $args{debug} ) {
94 no strict 'refs';
a4a19f3c 95 *{"$class\::debug_loader"} = sub { 1 };
a78e3fed 96 }
97 my $additional = $args{additional_classes} || [];
98 $additional = [$additional] unless ref $additional eq 'ARRAY';
99 my $additional_base = $args{additional_base_classes} || [];
100 $additional_base = [$additional_base]
101 unless ref $additional_base eq 'ARRAY';
102 my $left_base = $args{left_base_classes} || [];
103 $left_base = [$left_base] unless ref $left_base eq 'ARRAY';
a4a19f3c 104 $class->loader_data({
a78e3fed 105 _datasource =>
106 [ $args{dsn}, $args{user}, $args{password}, $args{options} ],
a4a19f3c 107 _namespace => $args{namespace} || $class,
a78e3fed 108 _additional => $additional,
109 _additional_base => $additional_base,
110 _left_base => $left_base,
111 _constraint => $args{constraint} || '.*',
112 _exclude => $args{exclude},
113 _relationships => $args{relationships},
114 _inflect => $args{inflect},
a4a19f3c 115 _db_schema => $args{db_schema},
116 _drop_db_schema => $args{drop_db_schema},
af6c2665 117 TABLE_CLASSES => {},
118 MONIKERS => {},
a4a19f3c 119 });
120
121 $class->connection(@{$class->loader_data->{_datasource}});
122 warn qq/\### START DBIx::Class::Schema::Loader dump ###\n/ if $class->debug;
123 $class->_load_classes;
124 $class->_relationships if $class->loader_data->{_relationships};
125 warn qq/\### END DBIx::Class::Schema::Loader dump ###\n/ if $class->debug;
126 $class->storage->dbh->disconnect; # XXX this should be ->storage->disconnect later?
127
128 1;
a78e3fed 129}
130
af6c2665 131# The original table class name during Loader,
132sub _find_table_class {
a4a19f3c 133 my ( $class, $table ) = @_;
134 return $class->loader_data->{TABLE_CLASSES}->{$table};
a78e3fed 135}
136
af6c2665 137# Returns the moniker for a given table name,
138# for use in $conn->resultset($moniker)
139sub moniker {
a4a19f3c 140 my ( $class, $table ) = @_;
141 return $class->loader_data->{MONIKERS}->{$table};
a78e3fed 142}
143
144=head3 debug
145
146Overload to enable debug messages.
147
148=cut
149
150sub debug { 0 }
151
152=head3 tables
153
154Returns a sorted list of tables.
155
156 my @tables = $loader->tables;
157
158=cut
159
160sub tables {
a4a19f3c 161 my $class = shift;
162 return sort keys %{ $class->loader_data->{MONIKERS} };
a78e3fed 163}
164
165# Overload in your driver class
166sub _db_classes { croak "ABSTRACT METHOD" }
167
168# Setup has_a and has_many relationships
169sub _belongs_to_many {
a4a19f3c 170 my ( $class, $table, $column, $other, $other_column ) = @_;
171 my $table_class = $class->_find_table_class($table);
172 my $other_class = $class->_find_table_class($other);
a78e3fed 173
a4a19f3c 174 warn qq/\# Belongs_to relationship\n/ if $class->debug;
a78e3fed 175
176 if($other_column) {
177 warn qq/$table_class->belongs_to( '$column' => '$other_class',/
178 . qq/ { "foreign.$other_column" => "self.$column" },/
179 . qq/ { accessor => 'filter' });\n\n/
a4a19f3c 180 if $class->debug;
a78e3fed 181 $table_class->belongs_to( $column => $other_class,
182 { "foreign.$other_column" => "self.$column" },
183 { accessor => 'filter' }
184 );
185 }
186 else {
187 warn qq/$table_class->belongs_to( '$column' => '$other_class' );\n\n/
a4a19f3c 188 if $class->debug;
a78e3fed 189 $table_class->belongs_to( $column => $other_class );
190 }
191
192 my ($table_class_base) = $table_class =~ /.*::(.+)/;
193 my $plural = Lingua::EN::Inflect::PL( lc $table_class_base );
a4a19f3c 194 $plural = $class->loader_data->{_inflect}->{ lc $table_class_base }
195 if $class->loader_data->{_inflect}
196 and exists $class->loader_data->{_inflect}->{ lc $table_class_base };
a78e3fed 197
a4a19f3c 198 warn qq/\# Has_many relationship\n/ if $class->debug;
a78e3fed 199
200 if($other_column) {
201 warn qq/$other_class->has_many( '$plural' => '$table_class',/
202 . qq/ { "foreign.$column" => "self.$other_column" } );\n\n/
a4a19f3c 203 if $class->debug;
a78e3fed 204 $other_class->has_many( $plural => $table_class,
205 { "foreign.$column" => "self.$other_column" }
206 );
207 }
208 else {
209 warn qq/$other_class->has_many( '$plural' => '$table_class',/
210 . qq/'$other_column' );\n\n/
a4a19f3c 211 if $class->debug;
a78e3fed 212 $other_class->has_many( $plural => $table_class, $column );
213 }
214}
215
216# Load and setup classes
217sub _load_classes {
a4a19f3c 218 my $class = shift;
af6c2665 219
a4a19f3c 220 my $namespace = $class->loader_data->{_namespace};
af6c2665 221
a4a19f3c 222 my @tables = $class->_tables();
223 my @db_classes = $class->_db_classes();
224 my $additional = join '', map "use $_;\n", @{ $class->loader_data->{_additional} };
a78e3fed 225 my $additional_base = join '', map "use base '$_';\n",
a4a19f3c 226 @{ $class->loader_data->{_additional_base} };
227 my $left_base = join '', map "use base '$_';\n", @{ $class->loader_data->{_left_base} };
228 my $constraint = $class->loader_data->{_constraint};
229 my $exclude = $class->loader_data->{_exclude};
a78e3fed 230
a78e3fed 231 foreach my $table (@tables) {
232 next unless $table =~ /$constraint/;
233 next if ( defined $exclude && $table =~ /$exclude/ );
af6c2665 234
235 my $table = lc $table;
236 my $table_name_db_schema = $table;
237 my $table_name_only = $table_name_db_schema;
238 my ($db_schema, $tbl) = split /\./, $table;
a78e3fed 239 if($tbl) {
a4a19f3c 240 $table_name_db_schema = $tbl if $class->loader_data->{_drop_db_schema};
af6c2665 241 $table_name_only = $tbl;
a78e3fed 242 }
af6c2665 243 else {
244 undef $db_schema;
245 }
246
a4a19f3c 247 my $table_subclass = $class->_table2subclass($db_schema, $table_name_only);
248 my $table_class = $namespace . '::' . $table_subclass;
af6c2665 249
a4a19f3c 250 $class->inject_base( $table_class, 'DBIx::Class::Core' );
a78e3fed 251 $_->require for @db_classes;
a4a19f3c 252 $class->inject_base( $table_class, $_ ) for @db_classes;
253 warn qq/\# Initializing table "$table_name_db_schema" as "$table_class"\n/ if $class->debug;
254 $table_class->table(lc $table_name_db_schema);
af6c2665 255
a4a19f3c 256 my ( $cols, $pks ) = $class->_table_info($table_name_db_schema);
a78e3fed 257 carp("$table has no primary key") unless @$pks;
a4a19f3c 258 $table_class->add_columns(@$cols);
259 $table_class->set_primary_key(@$pks) if @$pks;
af6c2665 260
a4a19f3c 261 my $code = "package $table_class;\n$additional_base$additional$left_base";
262 warn qq/$code/ if $class->debug;
263 warn qq/$table_class->table('$table_name_db_schema');\n/ if $class->debug;
a78e3fed 264 my $columns = join "', '", @$cols;
a4a19f3c 265 warn qq/$table_class->add_columns('$columns')\n/ if $class->debug;
a78e3fed 266 my $primaries = join "', '", @$pks;
a4a19f3c 267 warn qq/$table_class->set_primary_key('$primaries')\n/ if $class->debug && @$pks;
a78e3fed 268 eval $code;
269 croak qq/Couldn't load additional classes "$@"/ if $@;
a4a19f3c 270 unshift @{"$table_class\::ISA"}, $_ foreach ( @{ $class->loader_data->{_left_base} } );
af6c2665 271
a4a19f3c 272 $class->register_class($table_subclass, $table_class);
273 $class->loader_data->{TABLE_CLASSES}->{$table_name_db_schema} = $table_class;
274 $class->loader_data->{MONIKERS}->{$table_name_db_schema} = $table_subclass;
a78e3fed 275 }
276}
277
278# Find and setup relationships
279sub _relationships {
a4a19f3c 280 my $class = shift;
281 my $dbh = $class->storage->dbh;
282 foreach my $table ( $class->tables ) {
a78e3fed 283 my $quoter = $dbh->get_info(29) || q{"};
284 if ( my $sth = $dbh->foreign_key_info( '', '', '', '', '', $table ) ) {
285 for my $res ( @{ $sth->fetchall_arrayref( {} ) } ) {
286 my $column = $res->{FK_COLUMN_NAME};
287 my $other = $res->{UK_TABLE_NAME};
288 my $other_column = $res->{UK_COLUMN_NAME};
289 $column =~ s/$quoter//g;
290 $other =~ s/$quoter//g;
291 $other_column =~ s/$quoter//g;
a4a19f3c 292 eval { $class->_belongs_to_many( $table, $column, $other,
a78e3fed 293 $other_column ) };
294 warn qq/\# belongs_to_many failed "$@"\n\n/
a4a19f3c 295 if $@ && $class->debug;
a78e3fed 296 }
297 }
298 }
299}
300
af6c2665 301# Make a subclass (dbix moniker) from a table
302sub _table2subclass {
a4a19f3c 303 my ( $class, $db_schema, $table ) = @_;
af6c2665 304
a4a19f3c 305 my $table_subclass = join '', map ucfirst, split /[\W_]+/, $table;
af6c2665 306
a4a19f3c 307 if($db_schema && !$class->loader_data->{_drop_db_schema}) {
308 $table_subclass = (ucfirst lc $db_schema) . '-' . $table_subclass;
a78e3fed 309 }
af6c2665 310
a4a19f3c 311 $table_subclass;
a78e3fed 312}
313
314# Overload in driver class
315sub _tables { croak "ABSTRACT METHOD" }
316
317sub _table_info { croak "ABSTRACT METHOD" }
318
319=head1 SEE ALSO
320
18fca96a 321L<DBIx::Class::Schema::Loader>
a78e3fed 322
323=cut
324
3251;