1 package DBIx::Class::Schema::Loader::RelBuilder;
6 use Carp::Clan qw/^DBIx::Class/;
7 use Lingua::EN::Inflect::Number ();
9 our $VERSION = '0.05002';
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' => [
80 my ( $class, $schema, $inflect_pl, $inflect_singular, $rel_attrs ) = @_;
84 inflect_plural => $inflect_pl,
85 inflect_singular => $inflect_singular,
86 relationship_attrs => $rel_attrs,
89 # validate the relationship_attrs arg
90 if( defined $self->{relationship_attrs} ) {
91 ref($self->{relationship_attrs}) eq 'HASH'
92 or croak "relationship_attrs must be a hashref";
95 return bless $self => $class;
99 # pluralize a relationship name
100 sub _inflect_plural {
101 my ($self, $relname) = @_;
103 if( ref $self->{inflect_plural} eq 'HASH' ) {
104 return $self->{inflect_plural}->{$relname}
105 if exists $self->{inflect_plural}->{$relname};
107 elsif( ref $self->{inflect_plural} eq 'CODE' ) {
108 my $inflected = $self->{inflect_plural}->($relname);
109 return $inflected if $inflected;
112 return Lingua::EN::Inflect::Number::to_PL($relname);
115 # Singularize a relationship name
116 sub _inflect_singular {
117 my ($self, $relname) = @_;
119 if( ref $self->{inflect_singular} eq 'HASH' ) {
120 return $self->{inflect_singular}->{$relname}
121 if exists $self->{inflect_singular}->{$relname};
123 elsif( ref $self->{inflect_singular} eq 'CODE' ) {
124 my $inflected = $self->{inflect_singular}->($relname);
125 return $inflected if $inflected;
128 return Lingua::EN::Inflect::Number::to_S($relname);
131 # accessor for options to be passed to each generated relationship
132 # type. take single argument, the relationship type name, and returns
133 # either a hashref (if some options are set), or nothing
134 sub _relationship_attrs {
135 my ( $self, $reltype ) = @_;
136 my $r = $self->{relationship_attrs};
137 return unless $r && ( $r->{all} || $r->{$reltype} );
139 my %composite = %{ $r->{all} || {} };
140 if( my $specific = $r->{$reltype} ) {
141 while( my ($k,$v) = each %$specific ) {
151 return unless @$a == @$b;
153 for (my $i = 0; $i < @$a; $i++) {
154 return unless $a->[$i] eq $b->[$i];
160 my ($self, $local_moniker, $local_cols) = @_;
162 # get our base set of attrs from _relationship_attrs, if present
163 my $attrs = $self->_relationship_attrs('belongs_to') || {};
165 # If the referring column is nullable, make 'belongs_to' an
166 # outer join, unless explicitly set by relationship_attrs
167 my $nullable = grep { $self->{schema}->source($local_moniker)->column_info($_)->{is_nullable} }
169 $attrs->{join_type} = 'LEFT'
170 if $nullable && !defined $attrs->{join_type};
175 sub _remote_relname {
176 my ($self, $remote_table, $cond) = @_;
179 # for single-column case, set the remote relname to the column
180 # name, to make filter accessors work, but strip trailing _id
181 if(scalar keys %{$cond} == 1) {
182 my ($col) = values %{$cond};
184 $remote_relname = $self->_inflect_singular($col);
187 $remote_relname = $self->_inflect_singular(lc $remote_table);
190 return $remote_relname;
194 my ($self, $local_moniker, $rels, $uniqs) = @_;
198 my $local_class = $self->{schema}->class($local_moniker);
201 foreach my $rel (@$rels) {
202 next if !$rel->{remote_source};
203 $counters{$rel->{remote_source}}++;
206 foreach my $rel (@$rels) {
207 my $remote_moniker = $rel->{remote_source}
210 my $remote_class = $self->{schema}->class($remote_moniker);
211 my $remote_obj = $self->{schema}->source($remote_moniker);
212 my $remote_cols = $rel->{remote_columns} || [ $remote_obj->primary_columns ];
214 my $local_cols = $rel->{local_columns};
216 if($#$local_cols != $#$remote_cols) {
217 croak "Column count mismatch: $local_moniker (@$local_cols) "
218 . "$remote_moniker (@$remote_cols)";
222 foreach my $i (0 .. $#$local_cols) {
223 $cond{$remote_cols->[$i]} = $local_cols->[$i];
226 my ( $local_relname, $remote_relname, $remote_method ) =
227 $self->_relnames_and_methods( $local_moniker, $rel, \%cond, $uniqs, \%counters );
229 push(@{$all_code->{$local_class}},
230 { method => 'belongs_to',
231 args => [ $remote_relname,
234 $self->_remote_attrs($local_moniker, $local_cols),
239 my %rev_cond = reverse %cond;
240 for (keys %rev_cond) {
241 $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
242 delete $rev_cond{$_};
245 push(@{$all_code->{$remote_class}},
246 { method => $remote_method,
247 args => [ $local_relname,
250 $self->_relationship_attrs($remote_method),
259 sub _relnames_and_methods {
260 my ( $self, $local_moniker, $rel, $cond, $uniqs, $counters ) = @_;
262 my $remote_moniker = $rel->{remote_source};
263 my $remote_obj = $self->{schema}->source( $remote_moniker );
264 my $remote_class = $self->{schema}->class( $remote_moniker );
265 my $remote_relname = $self->_remote_relname( $remote_obj->from, $cond);
267 my $local_cols = $rel->{local_columns};
268 my $local_table = $self->{schema}->source($local_moniker)->from;
270 # If more than one rel between this pair of tables, use the local
271 # col names to distinguish
273 if ( $counters->{$remote_moniker} > 1) {
274 my $colnames = q{_} . join(q{_}, @$local_cols);
275 $remote_relname .= $colnames if keys %$cond > 1;
277 my $old_relname = #< TODO: remove me after 0.05003 release
278 my $local_relname = lc($local_table) . $colnames;
279 my $stripped_id = $local_relname =~ s/_id$//; #< strip off any trailing _id
280 $local_relname = $self->_inflect_plural( $local_relname );
282 # TODO: remove me after 0.05003 release
283 $old_relname = $self->_inflect_plural( $old_relname );
284 warn __PACKAGE__." $VERSION: warning, stripping trailing _id from ${remote_class} relation '$old_relname', renaming to '$local_relname'. This behavior is new as of 0.05003.\n"
288 $local_relname = $self->_inflect_plural(lc $local_table);
291 my $remote_method = 'has_many';
293 # If the local columns have a UNIQUE constraint, this is a one-to-one rel
294 my $local_source = $self->{schema}->source($local_moniker);
295 if (_array_eq([ $local_source->primary_columns ], $local_cols) ||
296 grep { _array_eq($_->[1], $local_cols) } @$uniqs) {
297 $remote_method = 'might_have';
298 $local_relname = $self->_inflect_singular($local_relname);
301 return ( $local_relname, $remote_relname, $remote_method );
306 See L<DBIx::Class::Schema::Loader/AUTHOR> and L<DBIx::Class::Schema::Loader/CONTRIBUTORS>.
310 This library is free software; you can redistribute it and/or modify it under
311 the same terms as Perl itself.