1 package DBIx::Class::Schema::Loader::RelBuilder;
5 use Carp::Clan qw/^DBIx::Class/;
6 use Lingua::EN::Inflect::Number ();
8 our $VERSION = '0.04999_07';
12 DBIx::Class::Schema::Loader::RelBuilder - Builds relationships for DBIx::Class::Schema::Loader
16 See L<DBIx::Class::Schema::Loader>
20 This class builds relationships for L<DBIx::Class::Schema::Loader>. This
21 is module is not (yet) for external use.
27 Arguments: schema_class (scalar), inflect_plural, inflect_singular
29 C<$schema_class> should be a schema class name, where the source
30 classes have already been set up and registered. Column info, primary
31 key, and unique constraints will be drawn from this schema for all
32 of the existing source monikers.
34 Options inflect_plural and inflect_singular are optional, and are better documented
35 in L<DBIx::Class::Schema::Loader::Base>.
39 Arguments: local_moniker (scalar), fk_info (arrayref)
41 This generates the code for the relationships of a given table.
43 C<local_moniker> is the moniker name of the table which had the REFERENCES
44 statements. The fk_info arrayref's contents should take the form:
48 local_columns => [ 'col2', 'col3' ],
49 remote_columns => [ 'col5', 'col7' ],
50 remote_moniker => 'AnotherTableMoniker',
53 local_columns => [ 'col1', 'col4' ],
54 remote_columns => [ 'col1', 'col2' ],
55 remote_moniker => 'YetAnotherTableMoniker',
60 This method will return the generated relationships as a hashref keyed on the
61 class names. The values are arrayrefs of hashes containing method name and
65 'Some::Source::Class' => [
66 { method => 'belongs_to', arguments => [ 'col1', 'Another::Source::Class' ],
67 { method => 'has_many', arguments => [ 'anothers', 'Yet::Another::Source::Class', 'col15' ],
69 'Another::Source::Class' => [
78 my ( $class, $schema, $inflect_pl, $inflect_singular ) = @_;
82 inflect_plural => $inflect_pl,
83 inflect_singular => $inflect_singular,
86 bless $self => $class;
92 # pluralize a relationship name
94 my ($self, $relname) = @_;
96 if( ref $self->{inflect_plural} eq 'HASH' ) {
97 return $self->{inflect_plural}->{$relname}
98 if exists $self->{inflect_plural}->{$relname};
100 elsif( ref $self->{inflect_plural} eq 'CODE' ) {
101 my $inflected = $self->{inflect_plural}->($relname);
102 return $inflected if $inflected;
105 return Lingua::EN::Inflect::Number::to_PL($relname);
108 # Singularize a relationship name
109 sub _inflect_singular {
110 my ($self, $relname) = @_;
112 if( ref $self->{inflect_singular} eq 'HASH' ) {
113 return $self->{inflect_singular}->{$relname}
114 if exists $self->{inflect_singular}->{$relname};
116 elsif( ref $self->{inflect_singular} eq 'CODE' ) {
117 my $inflected = $self->{inflect_singular}->($relname);
118 return $inflected if $inflected;
121 return Lingua::EN::Inflect::Number::to_S($relname);
127 return unless @$a == @$b;
129 for (my $i = 0; $i < @$a; $i++) {
130 return unless $a->[$i] eq $b->[$i];
136 my ($self, $local_moniker, $local_relname, $local_cols, $uniqs) = @_;
138 my $remote_method = 'has_many';
140 # If the local columns have a UNIQUE constraint, this is a one-to-one rel
141 my $local_source = $self->{schema}->source($local_moniker);
142 if (_array_eq([ $local_source->primary_columns ], $local_cols) ||
143 grep { _array_eq($_->[1], $local_cols) } @$uniqs) {
144 $remote_method = 'might_have';
145 $local_relname = $self->_inflect_singular($local_relname);
148 return ($remote_method, $local_relname);
152 my ($self, $local_moniker, $local_cols) = @_;
154 # If the referring column is nullable, make 'belongs_to' an outer join:
155 my $nullable = grep { $self->{schema}->source($local_moniker)->column_info($_)->{is_nullable} }
158 return $nullable ? { join_type => 'LEFT' } : ();
161 sub _remote_relname {
162 my ($self, $remote_table, $cond) = @_;
165 # for single-column case, set the remote relname to the column
166 # name, to make filter accessors work, but strip trailing _id
167 if(scalar keys %{$cond} == 1) {
168 my ($col) = values %{$cond};
170 $remote_relname = $self->_inflect_singular($col);
173 $remote_relname = $self->_inflect_singular(lc $remote_table);
176 return $remote_relname;
180 my ($self, $local_moniker, $rels, $uniqs) = @_;
184 my $local_table = $self->{schema}->source($local_moniker)->from;
185 my $local_class = $self->{schema}->class($local_moniker);
188 foreach my $rel (@$rels) {
189 next if !$rel->{remote_source};
190 $counters{$rel->{remote_source}}++;
193 foreach my $rel (@$rels) {
194 next if !$rel->{remote_source};
195 my $local_cols = $rel->{local_columns};
196 my $remote_cols = $rel->{remote_columns};
197 my $remote_moniker = $rel->{remote_source};
198 my $remote_obj = $self->{schema}->source($remote_moniker);
199 my $remote_class = $self->{schema}->class($remote_moniker);
200 my $remote_table = $remote_obj->from;
201 $remote_cols ||= [ $remote_obj->primary_columns ];
203 if($#$local_cols != $#$remote_cols) {
204 croak "Column count mismatch: $local_moniker (@$local_cols) "
205 . "$remote_moniker (@$remote_cols)";
209 foreach my $i (0 .. $#$local_cols) {
210 $cond{$remote_cols->[$i]} = $local_cols->[$i];
214 my $remote_relname = $self->_remote_relname($remote_table, \%cond);
216 # If more than one rel between this pair of tables, use the local
217 # col names to distinguish
218 if($counters{$remote_moniker} > 1) {
219 my $colnames = q{_} . join(q{_}, @$local_cols);
220 $local_relname = $self->_inflect_plural(
221 lc($local_table) . $colnames
223 $remote_relname .= $colnames if keys %cond > 1;
225 $local_relname = $self->_inflect_plural(lc $local_table);
228 my %rev_cond = reverse %cond;
230 for (keys %rev_cond) {
231 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
232 delete $rev_cond{$_};
237 ($remote_method, $local_relname) = $self->_uniq_fk_rel($local_moniker, $local_relname, $local_cols, $uniqs);
239 push(@{$all_code->{$local_class}},
240 { method => 'belongs_to',
241 args => [ $remote_relname,
244 $self->_remote_attrs($local_moniker, $local_cols),
249 push(@{$all_code->{$remote_class}},
250 { method => $remote_method,
251 args => [ $local_relname,