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_06';
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, $rels, $uniqs) = @_;
140 my $local_table = $self->{schema}->source($local_moniker)->from;
141 my $local_class = $self->{schema}->class($local_moniker);
144 foreach my $rel (@$rels) {
145 next if !$rel->{remote_source};
146 $counters{$rel->{remote_source}}++;
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 ];
159 if($#$local_cols != $#$remote_cols) {
160 croak "Column count mismatch: $local_moniker (@$local_cols) "
161 . "$remote_moniker (@$remote_cols)";
165 foreach my $i (0 .. $#$local_cols) {
166 $cond{$remote_cols->[$i]} = $local_cols->[$i];
172 # for single-column case, set the remote relname to the column
173 # name, to make filter accessors work, but strip trailing _id
174 if(scalar keys %cond == 1) {
175 my ($col) = values %cond;
177 $remote_relname = $self->_inflect_singular($col);
180 $remote_relname = $self->_inflect_singular(lc $remote_table);
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
190 $remote_relname .= $colnames if keys %cond > 1;
192 $local_relname = $self->_inflect_plural(lc $local_table);
195 my %rev_cond = reverse %cond;
197 for (keys %rev_cond) {
198 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
199 delete $rev_cond{$_};
202 my $remote_method = 'has_many';
204 # If the local columns have a UNIQUE constraint, this is a one-to-one rel
205 my $local_source = $self->{schema}->source($local_moniker);
206 if (_array_eq([ $local_source->primary_columns ], $local_cols) ||
207 grep { _array_eq($_->[1], $local_cols) } @$uniqs) {
208 $remote_method = 'might_have';
209 $local_relname = $self->_inflect_singular($local_relname);
212 # If the referring column is nullable, make 'belongs_to' an outer join:
213 my $nullable = grep { $local_source->column_info($_)->{is_nullable} }
216 push(@{$all_code->{$local_class}},
217 { method => 'belongs_to',
218 args => [ $remote_relname,
221 $nullable ? { join_type => 'LEFT OUTER' } : ()
226 push(@{$all_code->{$remote_class}},
227 { method => $remote_method,
228 args => [ $local_relname,