Merge 'trunk' into 'current'
[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
32f784fc 8our $VERSION = '0.03999_01';
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
124sub generate_code {
e8ad6491 125 my ($self, $local_moniker, $rels) = @_;
996be9ee 126
127 my $all_code = {};
128
e8ad6491 129 my $local_table = $self->{schema}->source($local_moniker)->from;
130 my $local_class = $self->{schema}->class($local_moniker);
996be9ee 131
e8ad6491 132 my %counters;
133 foreach my $rel (@$rels) {
134 next if !$rel->{remote_source};
135 $counters{$rel->{remote_source}}++;
136 }
137
138 foreach my $rel (@$rels) {
139 next if !$rel->{remote_source};
140 my $local_cols = $rel->{local_columns};
141 my $remote_cols = $rel->{remote_columns};
142 my $remote_moniker = $rel->{remote_source};
143 my $remote_obj = $self->{schema}->source($remote_moniker);
144 my $remote_class = $self->{schema}->class($remote_moniker);
145 my $remote_table = $remote_obj->from;
146 $remote_cols ||= [ $remote_obj->primary_columns ];
147
148 if($#$local_cols != $#$remote_cols) {
149 croak "Column count mismatch: $local_moniker (@$local_cols) "
150 . "$remote_moniker (@$remote_cols)";
996be9ee 151 }
152
e8ad6491 153 my %cond;
154 foreach my $i (0 .. $#$local_cols) {
155 $cond{$remote_cols->[$i]} = $local_cols->[$i];
156 }
996be9ee 157
e8ad6491 158 # If more than one rel between this pair of tables, use the
159 # local col name(s) as the relname in the foreign source, instead
160 # of the local table name.
161 my $local_relname;
162 if($counters{$remote_moniker} > 1) {
163 $local_relname = $self->_inflect_plural(
164 lc($local_table) . q{_} . join(q{_}, @$local_cols)
165 );
166 } else {
167 $local_relname = $self->_inflect_plural(lc $local_table);
168 }
996be9ee 169
e8ad6491 170 # for single-column case, set the relname to the column name,
171 # to make filter accessors work
172 my $remote_relname;
173 if(scalar keys %cond == 1) {
174 my ($col) = keys %cond;
175 $remote_relname = $self->_inflect_singular($cond{$col});
176 }
177 else {
178 $remote_relname = $self->_inflect_singular(lc $remote_table);
179 }
996be9ee 180
e8ad6491 181 my %rev_cond = reverse %cond;
996be9ee 182
e8ad6491 183 for (keys %rev_cond) {
184 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
185 delete $rev_cond{$_};
186 }
996be9ee 187
e8ad6491 188 push(@{$all_code->{$local_class}},
189 { method => 'belongs_to',
190 args => [ $remote_relname,
191 $remote_class,
192 \%cond,
193 ],
996be9ee 194 }
e8ad6491 195 );
196
197 push(@{$all_code->{$remote_class}},
198 { method => 'has_many',
199 args => [ $local_relname,
200 $local_class,
201 \%rev_cond,
202 ],
203 }
204 );
996be9ee 205 }
206
207 return $all_code;
208}
209
2101;