Replace UNIVERSAL::require with Class::C3::Componentised
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / RelBuilder.pm
CommitLineData
996be9ee 1package DBIx::Class::Schema::Loader::RelBuilder;
2
3use strict;
4use warnings;
fa994d3c 5use Carp::Clan qw/^DBIx::Class/;
996be9ee 6use Lingua::EN::Inflect::Number ();
7
457c3335 8our $VERSION = '0.04999_07';
32f784fc 9
996be9ee 10=head1 NAME
11
12DBIx::Class::Schema::Loader::RelBuilder - Builds relationships for DBIx::Class::Schema::Loader
13
14=head1 SYNOPSIS
15
16See L<DBIx::Class::Schema::Loader>
17
18=head1 DESCRIPTION
19
20This class builds relationships for L<DBIx::Class::Schema::Loader>. This
21is module is not (yet) for external use.
22
23=head1 METHODS
24
25=head2 new
26
e8ad6491 27Arguments: schema_class (scalar), inflect_plural, inflect_singular
996be9ee 28
29C<$schema_class> should be a schema class name, where the source
30classes have already been set up and registered. Column info, primary
31key, and unique constraints will be drawn from this schema for all
32of the existing source monikers.
33
996be9ee 34Options inflect_plural and inflect_singular are optional, and are better documented
35in L<DBIx::Class::Schema::Loader::Base>.
36
37=head2 generate_code
38
e8ad6491 39Arguments: local_moniker (scalar), fk_info (arrayref)
40
41This generates the code for the relationships of a given table.
42
43C<local_moniker> is the moniker name of the table which had the REFERENCES
44statements. The fk_info arrayref's contents should take the form:
45
46 [
47 {
48 local_columns => [ 'col2', 'col3' ],
49 remote_columns => [ 'col5', 'col7' ],
50 remote_moniker => 'AnotherTableMoniker',
51 },
52 {
53 local_columns => [ 'col1', 'col4' ],
54 remote_columns => [ 'col1', 'col2' ],
55 remote_moniker => 'YetAnotherTableMoniker',
56 },
57 # ...
58 ],
59
60This method will return the generated relationships as a hashref keyed on the
61class names. The values are arrayrefs of hashes containing method name and
62arguments, like so:
996be9ee 63
64 {
65 'Some::Source::Class' => [
b97c2c1e 66 { method => 'belongs_to', arguments => [ 'col1', 'Another::Source::Class' ],
67 { method => 'has_many', arguments => [ 'anothers', 'Yet::Another::Source::Class', 'col15' ],
996be9ee 68 ],
69 'Another::Source::Class' => [
70 # ...
71 ],
72 # ...
73 }
8f9d7ce5 74
996be9ee 75=cut
76
77sub new {
e8ad6491 78 my ( $class, $schema, $inflect_pl, $inflect_singular ) = @_;
996be9ee 79
80 my $self = {
81 schema => $schema,
996be9ee 82 inflect_plural => $inflect_pl,
83 inflect_singular => $inflect_singular,
84 };
85
86 bless $self => $class;
87
88 $self;
89}
90
91
92# pluralize a relationship name
93sub _inflect_plural {
94 my ($self, $relname) = @_;
95
96 if( ref $self->{inflect_plural} eq 'HASH' ) {
97 return $self->{inflect_plural}->{$relname}
98 if exists $self->{inflect_plural}->{$relname};
99 }
100 elsif( ref $self->{inflect_plural} eq 'CODE' ) {
101 my $inflected = $self->{inflect_plural}->($relname);
102 return $inflected if $inflected;
103 }
104
d3af7a07 105 return Lingua::EN::Inflect::Number::to_PL($relname);
996be9ee 106}
107
108# Singularize a relationship name
109sub _inflect_singular {
110 my ($self, $relname) = @_;
111
112 if( ref $self->{inflect_singular} eq 'HASH' ) {
113 return $self->{inflect_singular}->{$relname}
114 if exists $self->{inflect_singular}->{$relname};
115 }
116 elsif( ref $self->{inflect_singular} eq 'CODE' ) {
117 my $inflected = $self->{inflect_singular}->($relname);
118 return $inflected if $inflected;
119 }
120
d3af7a07 121 return Lingua::EN::Inflect::Number::to_S($relname);
996be9ee 122}
123
26f1c8c9 124sub _array_eq {
125 my ($a, $b) = @_;
126
127 return unless @$a == @$b;
128
129 for (my $i = 0; $i < @$a; $i++) {
130 return unless $a->[$i] eq $b->[$i];
131 }
132 return 1;
133}
134
996be9ee 135sub generate_code {
26f1c8c9 136 my ($self, $local_moniker, $rels, $uniqs) = @_;
996be9ee 137
138 my $all_code = {};
139
e8ad6491 140 my $local_table = $self->{schema}->source($local_moniker)->from;
141 my $local_class = $self->{schema}->class($local_moniker);
996be9ee 142
e8ad6491 143 my %counters;
144 foreach my $rel (@$rels) {
145 next if !$rel->{remote_source};
146 $counters{$rel->{remote_source}}++;
147 }
148
149 foreach my $rel (@$rels) {
150 next if !$rel->{remote_source};
151 my $local_cols = $rel->{local_columns};
152 my $remote_cols = $rel->{remote_columns};
153 my $remote_moniker = $rel->{remote_source};
154 my $remote_obj = $self->{schema}->source($remote_moniker);
155 my $remote_class = $self->{schema}->class($remote_moniker);
156 my $remote_table = $remote_obj->from;
157 $remote_cols ||= [ $remote_obj->primary_columns ];
158
159 if($#$local_cols != $#$remote_cols) {
160 croak "Column count mismatch: $local_moniker (@$local_cols) "
161 . "$remote_moniker (@$remote_cols)";
996be9ee 162 }
163
e8ad6491 164 my %cond;
165 foreach my $i (0 .. $#$local_cols) {
166 $cond{$remote_cols->[$i]} = $local_cols->[$i];
167 }
996be9ee 168
e8ad6491 169 my $local_relname;
e8ad6491 170 my $remote_relname;
996be9ee 171
54700b71 172 # for single-column case, set the remote relname to the column
e7886624 173 # name, to make filter accessors work, but strip trailing _id
e8ad6491 174 if(scalar keys %cond == 1) {
e7886624 175 my ($col) = values %cond;
176 $col =~ s/_id$//;
177 $remote_relname = $self->_inflect_singular($col);
e8ad6491 178 }
179 else {
180 $remote_relname = $self->_inflect_singular(lc $remote_table);
181 }
996be9ee 182
54700b71 183 # If more than one rel between this pair of tables, use the local
184 # col names to distinguish
185 if($counters{$remote_moniker} > 1) {
186 my $colnames = q{_} . join(q{_}, @$local_cols);
187 $local_relname = $self->_inflect_plural(
188 lc($local_table) . $colnames
189 );
190 $remote_relname .= $colnames if keys %cond > 1;
191 } else {
192 $local_relname = $self->_inflect_plural(lc $local_table);
193 }
996be9ee 194
e8ad6491 195 my %rev_cond = reverse %cond;
996be9ee 196
e8ad6491 197 for (keys %rev_cond) {
198 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
199 delete $rev_cond{$_};
200 }
996be9ee 201
26f1c8c9 202 my $remote_method = 'has_many';
203
204 # If the local columns have a UNIQUE constraint, this is a one-to-one rel
97ec773a 205 my $local_source = $self->{schema}->source($local_moniker);
206 if (_array_eq([ $local_source->primary_columns ], $local_cols) ||
26f1c8c9 207 grep { _array_eq($_->[1], $local_cols) } @$uniqs) {
208 $remote_method = 'might_have';
209 $local_relname = $self->_inflect_singular($local_relname);
210 }
211
7dba7c70 212 # If the referring column is nullable, make 'belongs_to' an outer join:
7dba7c70 213 my $nullable = grep { $local_source->column_info($_)->{is_nullable} }
214 @$local_cols;
215
e8ad6491 216 push(@{$all_code->{$local_class}},
217 { method => 'belongs_to',
218 args => [ $remote_relname,
219 $remote_class,
220 \%cond,
53751ffe 221 $nullable ? { join_type => 'LEFT' } : ()
e8ad6491 222 ],
996be9ee 223 }
e8ad6491 224 );
225
226 push(@{$all_code->{$remote_class}},
26f1c8c9 227 { method => $remote_method,
e8ad6491 228 args => [ $local_relname,
229 $local_class,
230 \%rev_cond,
231 ],
232 }
233 );
996be9ee 234 }
235
236 return $all_code;
237}
238
2391;