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' } : ();
162 my ($self, $local_moniker, $rels, $uniqs) = @_;
166 my $local_table = $self->{schema}->source($local_moniker)->from;
167 my $local_class = $self->{schema}->class($local_moniker);
170 foreach my $rel (@$rels) {
171 next if !$rel->{remote_source};
172 $counters{$rel->{remote_source}}++;
175 foreach my $rel (@$rels) {
176 next if !$rel->{remote_source};
177 my $local_cols = $rel->{local_columns};
178 my $remote_cols = $rel->{remote_columns};
179 my $remote_moniker = $rel->{remote_source};
180 my $remote_obj = $self->{schema}->source($remote_moniker);
181 my $remote_class = $self->{schema}->class($remote_moniker);
182 my $remote_table = $remote_obj->from;
183 $remote_cols ||= [ $remote_obj->primary_columns ];
185 if($#$local_cols != $#$remote_cols) {
186 croak "Column count mismatch: $local_moniker (@$local_cols) "
187 . "$remote_moniker (@$remote_cols)";
191 foreach my $i (0 .. $#$local_cols) {
192 $cond{$remote_cols->[$i]} = $local_cols->[$i];
198 # for single-column case, set the remote relname to the column
199 # name, to make filter accessors work, but strip trailing _id
200 if(scalar keys %cond == 1) {
201 my ($col) = values %cond;
203 $remote_relname = $self->_inflect_singular($col);
206 $remote_relname = $self->_inflect_singular(lc $remote_table);
209 # If more than one rel between this pair of tables, use the local
210 # col names to distinguish
211 if($counters{$remote_moniker} > 1) {
212 my $colnames = q{_} . join(q{_}, @$local_cols);
213 $local_relname = $self->_inflect_plural(
214 lc($local_table) . $colnames
216 $remote_relname .= $colnames if keys %cond > 1;
218 $local_relname = $self->_inflect_plural(lc $local_table);
221 my %rev_cond = reverse %cond;
223 for (keys %rev_cond) {
224 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
225 delete $rev_cond{$_};
230 ($remote_method, $local_relname) = $self->_uniq_fk_rel($local_moniker, $local_relname, $local_cols, $uniqs);
232 push(@{$all_code->{$local_class}},
233 { method => 'belongs_to',
234 args => [ $remote_relname,
237 $self->_remote_attrs($local_moniker, $local_cols),
242 push(@{$all_code->{$remote_class}},
243 { method => $remote_method,
244 args => [ $local_relname,