0.04001, dump_overwrite -> really_erase_my_files
[dbsrgits/DBIx-Class-Schema-Loader.git] / lib / DBIx / Class / Schema / Loader / RelBuilder.pm
1 package DBIx::Class::Schema::Loader::RelBuilder;
2
3 use strict;
4 use warnings;
5 use Carp::Clan qw/^DBIx::Class/;
6 use Lingua::EN::Inflect::Number ();
7
8 our $VERSION = '0.04001';
9
10 =head1 NAME
11
12 DBIx::Class::Schema::Loader::RelBuilder - Builds relationships for DBIx::Class::Schema::Loader
13
14 =head1 SYNOPSIS
15
16 See L<DBIx::Class::Schema::Loader>
17
18 =head1 DESCRIPTION
19
20 This class builds relationships for L<DBIx::Class::Schema::Loader>.  This
21 is module is not (yet) for external use.
22
23 =head1 METHODS
24
25 =head2 new
26
27 Arguments: schema_class (scalar), inflect_plural, inflect_singular
28
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.
33
34 Options inflect_plural and inflect_singular are optional, and are better documented
35 in L<DBIx::Class::Schema::Loader::Base>.
36
37 =head2 generate_code
38
39 Arguments: local_moniker (scalar), fk_info (arrayref)
40
41 This generates the code for the relationships of a given table.
42
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:
45
46     [
47         {
48             local_columns => [ 'col2', 'col3' ],
49             remote_columns => [ 'col5', 'col7' ],
50             remote_moniker => 'AnotherTableMoniker',
51         },
52         {
53             local_columns => [ 'col1', 'col4' ],
54             remote_columns => [ 'col1', 'col2' ],
55             remote_moniker => 'YetAnotherTableMoniker',
56         },
57         # ...
58     ],
59
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
62 arguments, like so:
63
64   {
65       'Some::Source::Class' => [
66           { method => 'belongs_to', arguments => [ 'col1', 'Another::Source::Class' ],
67           { method => 'has_many', arguments => [ 'anothers', 'Yet::Another::Source::Class', 'col15' ],
68       ],
69       'Another::Source::Class' => [
70           # ...
71       ],
72       # ...
73   }
74
75 =cut
76
77 sub new {
78     my ( $class, $schema, $inflect_pl, $inflect_singular ) = @_;
79
80     my $self = {
81         schema => $schema,
82         inflect_plural => $inflect_pl,
83         inflect_singular => $inflect_singular,
84     };
85
86     bless $self => $class;
87
88     $self;
89 }
90
91
92 # pluralize a relationship name
93 sub _inflect_plural {
94     my ($self, $relname) = @_;
95
96     if( ref $self->{inflect_plural} eq 'HASH' ) {
97         return $self->{inflect_plural}->{$relname}
98             if exists $self->{inflect_plural}->{$relname};
99     }
100     elsif( ref $self->{inflect_plural} eq 'CODE' ) {
101         my $inflected = $self->{inflect_plural}->($relname);
102         return $inflected if $inflected;
103     }
104
105     return Lingua::EN::Inflect::Number::to_PL($relname);
106 }
107
108 # Singularize a relationship name
109 sub _inflect_singular {
110     my ($self, $relname) = @_;
111
112     if( ref $self->{inflect_singular} eq 'HASH' ) {
113         return $self->{inflect_singular}->{$relname}
114             if exists $self->{inflect_singular}->{$relname};
115     }
116     elsif( ref $self->{inflect_singular} eq 'CODE' ) {
117         my $inflected = $self->{inflect_singular}->($relname);
118         return $inflected if $inflected;
119     }
120
121     return Lingua::EN::Inflect::Number::to_S($relname);
122 }
123
124 sub generate_code {
125     my ($self, $local_moniker, $rels) = @_;
126
127     my $all_code = {};
128
129     my $local_table = $self->{schema}->source($local_moniker)->from;
130     my $local_class = $self->{schema}->class($local_moniker);
131         
132     my %counters;
133     foreach my $rel (@$rels) {
134         next if !$rel->{remote_source};
135         $counters{$rel->{remote_source}}++;
136     }
137
138     foreach my $rel (@$rels) {
139         next if !$rel->{remote_source};
140         my $local_cols = $rel->{local_columns};
141         my $remote_cols = $rel->{remote_columns};
142         my $remote_moniker = $rel->{remote_source};
143         my $remote_obj = $self->{schema}->source($remote_moniker);
144         my $remote_class = $self->{schema}->class($remote_moniker);
145         my $remote_table = $remote_obj->from;
146         $remote_cols ||= [ $remote_obj->primary_columns ];
147
148         if($#$local_cols != $#$remote_cols) {
149             croak "Column count mismatch: $local_moniker (@$local_cols) "
150                 . "$remote_moniker (@$remote_cols)";
151         }
152
153         my %cond;
154         foreach my $i (0 .. $#$local_cols) {
155             $cond{$remote_cols->[$i]} = $local_cols->[$i];
156         }
157
158         my $local_relname;
159         my $remote_relname;
160
161         # for single-column case, set the remote relname to the column
162         # name, to make filter accessors work
163         if(scalar keys %cond == 1) {
164             my ($col) = keys %cond;
165             $remote_relname = $self->_inflect_singular($cond{$col});
166         }
167         else {
168             $remote_relname = $self->_inflect_singular(lc $remote_table);
169         }
170
171         # If more than one rel between this pair of tables, use the local
172         # col names to distinguish
173         if($counters{$remote_moniker} > 1) {
174             my $colnames = q{_} . join(q{_}, @$local_cols);
175             $local_relname = $self->_inflect_plural(
176                 lc($local_table) . $colnames
177             );
178             $remote_relname .= $colnames if keys %cond > 1;
179         } else {
180             $local_relname = $self->_inflect_plural(lc $local_table);
181         }
182
183         my %rev_cond = reverse %cond;
184
185         for (keys %rev_cond) {
186             $rev_cond{"foreign.$_"} = "self.".$rev_cond{$_};
187             delete $rev_cond{$_};
188         }
189
190         push(@{$all_code->{$local_class}},
191             { method => 'belongs_to',
192               args => [ $remote_relname,
193                         $remote_class,
194                         \%cond,
195               ],
196             }
197         );
198
199         push(@{$all_code->{$remote_class}},
200             { method => 'has_many',
201               args => [ $local_relname,
202                         $local_class,
203                         \%rev_cond,
204               ],
205             }
206         );
207     }
208
209     return $all_code;
210 }
211
212 1;