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 ();
7use Lingua::EN::Inflect::Number ();
8
32f784fc 9our $VERSION = '0.03999_01';
10
996be9ee 11=head1 NAME
12
13DBIx::Class::Schema::Loader::RelBuilder - Builds relationships for DBIx::Class::Schema::Loader
14
15=head1 SYNOPSIS
16
17See L<DBIx::Class::Schema::Loader>
18
19=head1 DESCRIPTION
20
21This class builds relationships for L<DBIx::Class::Schema::Loader>. This
22is module is not (yet) for external use.
23
24=head1 METHODS
25
26=head2 new
27
28Arguments: schema_class (scalar), fk_info (hashref), inflect_plural, inflect_singular
29
30C<$schema_class> should be a schema class name, where the source
31classes have already been set up and registered. Column info, primary
32key, and unique constraints will be drawn from this schema for all
33of the existing source monikers.
34
35The fk_info hashref's contents should take the form:
36
37 {
38 TableMoniker => [
39 {
40 local_columns => [ 'col2', 'col3' ],
41 remote_columns => [ 'col5', 'col7' ],
42 remote_moniker => 'AnotherTableMoniker',
43 },
44 # ...
45 ],
46 AnotherTableMoniker => [
47 # ...
48 ],
49 # ...
50 }
51
52Options inflect_plural and inflect_singular are optional, and are better documented
53in L<DBIx::Class::Schema::Loader::Base>.
54
55=head2 generate_code
56
57This method will return the generated relationships as a hashref per table moniker,
58containing an arrayref of code strings which can be "eval"-ed in the context of
59the source class, like:
60
61 {
62 'Some::Source::Class' => [
63 "belongs_to( col1 => 'AnotherTableMoniker' )",
64 "has_many( anothers => 'AnotherTableMoniker', 'col15' )",
65 ],
66 'Another::Source::Class' => [
67 # ...
68 ],
69 # ...
70 }
8f9d7ce5 71
996be9ee 72You might want to use this in building an on-disk source class file, by
73adding each string to the appropriate source class file,
74prefixed by C<__PACKAGE__-E<gt>>.
75
76=cut
77
78sub new {
79 my ( $class, $schema, $fk_info, $inflect_pl, $inflect_singular ) = @_;
80
81 my $self = {
82 schema => $schema,
83 fk_info => $fk_info,
84 inflect_plural => $inflect_pl,
85 inflect_singular => $inflect_singular,
86 };
87
88 bless $self => $class;
89
90 $self;
91}
92
93
94# pluralize a relationship name
95sub _inflect_plural {
96 my ($self, $relname) = @_;
97
98 if( ref $self->{inflect_plural} eq 'HASH' ) {
99 return $self->{inflect_plural}->{$relname}
100 if exists $self->{inflect_plural}->{$relname};
101 }
102 elsif( ref $self->{inflect_plural} eq 'CODE' ) {
103 my $inflected = $self->{inflect_plural}->($relname);
104 return $inflected if $inflected;
105 }
106
107 return $self->{legacy_default_inflections}
108 ? Lingua::EN::Inflect::PL($relname)
109 : Lingua::EN::Inflect::Number::to_PL($relname);
110}
111
112# Singularize a relationship name
113sub _inflect_singular {
114 my ($self, $relname) = @_;
115
116 if( ref $self->{inflect_singular} eq 'HASH' ) {
117 return $self->{inflect_singular}->{$relname}
118 if exists $self->{inflect_singular}->{$relname};
119 }
120 elsif( ref $self->{inflect_singular} eq 'CODE' ) {
121 my $inflected = $self->{inflect_singular}->($relname);
122 return $inflected if $inflected;
123 }
124
125 return $self->{legacy_default_inflections}
126 ? $relname
127 : Lingua::EN::Inflect::Number::to_S($relname);
128}
129
130sub generate_code {
131 my $self = shift;
132
133 my $all_code = {};
134
135 foreach my $local_moniker (keys %{$self->{fk_info}}) {
136 my $local_table = $self->{schema}->source($local_moniker)->from;
137 my $local_class = $self->{schema}->class($local_moniker);
138 my $rels = $self->{fk_info}->{$local_moniker};
139
140 my %counters;
141 foreach my $rel (@$rels) {
142 next if !$rel->{remote_source};
143 $counters{$rel->{remote_source}}++;
144 }
145
146 foreach my $rel (@$rels) {
147 next if !$rel->{remote_source};
148 my $local_cols = $rel->{local_columns};
149 my $remote_cols = $rel->{remote_columns};
150 my $remote_moniker = $rel->{remote_source};
151 my $remote_obj = $self->{schema}->source($remote_moniker);
152 my $remote_class = $self->{schema}->class($remote_moniker);
153 my $remote_table = $remote_obj->from;
154 $remote_cols ||= [ $remote_obj->primary_columns ];
155
156 if($#$local_cols != $#$remote_cols) {
157 croak "Column count mismatch: $local_moniker (@$local_cols) "
158 . "$remote_moniker (@$remote_cols)";
159 }
160
161 my %cond;
162 foreach my $i (0 .. $#$local_cols) {
163 $cond{$remote_cols->[$i]} = $local_cols->[$i];
164 }
165
166 # If more than one rel between this pair of tables, use the
167 # local col name(s) as the relname in the foreign source, instead
168 # of the local table name.
169 my $local_relname;
170 if($counters{$remote_moniker} > 1) {
171 $local_relname = $self->_inflect_plural(
172 lc($local_table) . q{_} . join(q{_}, @$local_cols)
173 );
174 } else {
175 $local_relname = $self->_inflect_plural(lc $local_table);
176 }
177
178 # for single-column case, set the relname to the column name,
179 # to make filter accessors work
180 my $remote_relname;
181 if(scalar keys %cond == 1) {
182 my ($col) = keys %cond;
183 $remote_relname = $self->_inflect_singular($cond{$col});
184 }
185 else {
186 $remote_relname = $self->_inflect_singular(lc $remote_table);
187 }
188
189 my %rev_cond = reverse %cond;
190
191 for (keys %rev_cond) {
192 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
193 delete $rev_cond{$_};
194 }
195
196 push(@{$all_code->{$local_class}},
197 { method => 'belongs_to',
198 args => [ $remote_relname,
199 $remote_moniker,
200 \%cond,
201 ],
202 }
203 );
204
205 push(@{$all_code->{$remote_class}},
206 { method => 'has_many',
207 args => [ $local_relname,
208 $local_moniker,
209 \%rev_cond,
210 ],
211 }
212 );
213 }
214 }
215
216 return $all_code;
217}
218
2191;