1 package DBIx::Class::Schema::Loader::RelBuilder;
6 use Carp::Clan qw/^DBIx::Class/;
7 use Lingua::EN::Inflect::Number ();
9 our $VERSION = '0.04999_07';
13 DBIx::Class::Schema::Loader::RelBuilder - Builds relationships for DBIx::Class::Schema::Loader
17 See L<DBIx::Class::Schema::Loader>
21 This class builds relationships for L<DBIx::Class::Schema::Loader>. This
22 is module is not (yet) for external use.
28 Arguments: schema_class (scalar), inflect_plural, inflect_singular
30 C<$schema_class> should be a schema class name, where the source
31 classes have already been set up and registered. Column info, primary
32 key, and unique constraints will be drawn from this schema for all
33 of the existing source monikers.
35 Options inflect_plural and inflect_singular are optional, and are better documented
36 in L<DBIx::Class::Schema::Loader::Base>.
40 Arguments: local_moniker (scalar), fk_info (arrayref)
42 This generates the code for the relationships of a given table.
44 C<local_moniker> is the moniker name of the table which had the REFERENCES
45 statements. The fk_info arrayref's contents should take the form:
49 local_columns => [ 'col2', 'col3' ],
50 remote_columns => [ 'col5', 'col7' ],
51 remote_moniker => 'AnotherTableMoniker',
54 local_columns => [ 'col1', 'col4' ],
55 remote_columns => [ 'col1', 'col2' ],
56 remote_moniker => 'YetAnotherTableMoniker',
61 This method will return the generated relationships as a hashref keyed on the
62 class names. The values are arrayrefs of hashes containing method name and
66 'Some::Source::Class' => [
67 { method => 'belongs_to', arguments => [ 'col1', 'Another::Source::Class' ],
68 { method => 'has_many', arguments => [ 'anothers', 'Yet::Another::Source::Class', 'col15' ],
70 'Another::Source::Class' => [
79 my ( $class, $schema, $inflect_pl, $inflect_singular ) = @_;
83 inflect_plural => $inflect_pl,
84 inflect_singular => $inflect_singular,
87 bless $self => $class;
93 # pluralize a relationship name
95 my ($self, $relname) = @_;
97 if( ref $self->{inflect_plural} eq 'HASH' ) {
98 return $self->{inflect_plural}->{$relname}
99 if exists $self->{inflect_plural}->{$relname};
101 elsif( ref $self->{inflect_plural} eq 'CODE' ) {
102 my $inflected = $self->{inflect_plural}->($relname);
103 return $inflected if $inflected;
106 return Lingua::EN::Inflect::Number::to_PL($relname);
109 # Singularize a relationship name
110 sub _inflect_singular {
111 my ($self, $relname) = @_;
113 if( ref $self->{inflect_singular} eq 'HASH' ) {
114 return $self->{inflect_singular}->{$relname}
115 if exists $self->{inflect_singular}->{$relname};
117 elsif( ref $self->{inflect_singular} eq 'CODE' ) {
118 my $inflected = $self->{inflect_singular}->($relname);
119 return $inflected if $inflected;
122 return Lingua::EN::Inflect::Number::to_S($relname);
128 return unless @$a == @$b;
130 for (my $i = 0; $i < @$a; $i++) {
131 return unless $a->[$i] eq $b->[$i];
137 my ($self, $local_moniker, $local_relname, $local_cols, $uniqs) = @_;
139 my $remote_method = 'has_many';
141 # If the local columns have a UNIQUE constraint, this is a one-to-one rel
142 my $local_source = $self->{schema}->source($local_moniker);
143 if (_array_eq([ $local_source->primary_columns ], $local_cols) ||
144 grep { _array_eq($_->[1], $local_cols) } @$uniqs) {
145 $remote_method = 'might_have';
146 $local_relname = $self->_inflect_singular($local_relname);
149 return ($remote_method, $local_relname);
153 my ($self, $local_moniker, $local_cols) = @_;
155 # If the referring column is nullable, make 'belongs_to' an outer join:
156 my $nullable = grep { $self->{schema}->source($local_moniker)->column_info($_)->{is_nullable} }
159 return $nullable ? { join_type => 'LEFT' } : ();
162 sub _remote_relname {
163 my ($self, $remote_table, $cond) = @_;
166 # for single-column case, set the remote relname to the column
167 # name, to make filter accessors work, but strip trailing _id
168 if(scalar keys %{$cond} == 1) {
169 my ($col) = values %{$cond};
171 $remote_relname = $self->_inflect_singular($col);
174 $remote_relname = $self->_inflect_singular(lc $remote_table);
177 return $remote_relname;
181 my ($self, $local_moniker, $rels, $uniqs) = @_;
185 my $local_table = $self->{schema}->source($local_moniker)->from;
186 my $local_class = $self->{schema}->class($local_moniker);
189 foreach my $rel (@$rels) {
190 next if !$rel->{remote_source};
191 $counters{$rel->{remote_source}}++;
194 foreach my $rel (@$rels) {
195 next if !$rel->{remote_source};
196 my $local_cols = $rel->{local_columns};
197 my $remote_cols = $rel->{remote_columns};
198 my $remote_moniker = $rel->{remote_source};
199 my $remote_obj = $self->{schema}->source($remote_moniker);
200 my $remote_class = $self->{schema}->class($remote_moniker);
201 my $remote_table = $remote_obj->from;
202 $remote_cols ||= [ $remote_obj->primary_columns ];
204 if($#$local_cols != $#$remote_cols) {
205 croak "Column count mismatch: $local_moniker (@$local_cols) "
206 . "$remote_moniker (@$remote_cols)";
210 foreach my $i (0 .. $#$local_cols) {
211 $cond{$remote_cols->[$i]} = $local_cols->[$i];
215 my $remote_relname = $self->_remote_relname($remote_table, \%cond);
217 # If more than one rel between this pair of tables, use the local
218 # col names to distinguish
219 if($counters{$remote_moniker} > 1) {
220 my $colnames = q{_} . join(q{_}, @$local_cols);
221 $local_relname = $self->_inflect_plural(
222 lc($local_table) . $colnames
224 $remote_relname .= $colnames if keys %cond > 1;
226 $local_relname = $self->_inflect_plural(lc $local_table);
229 my %rev_cond = reverse %cond;
231 for (keys %rev_cond) {
232 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
233 delete $rev_cond{$_};
238 ($remote_method, $local_relname) = $self->_uniq_fk_rel($local_moniker, $local_relname, $local_cols, $uniqs);
240 push(@{$all_code->{$local_class}},
241 { method => 'belongs_to',
242 args => [ $remote_relname,
245 $self->_remote_attrs($local_moniker, $local_cols),
250 push(@{$all_code->{$remote_class}},
251 { method => $remote_method,
252 args => [ $local_relname,