resolve_condition moved to ResultSource
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Relationship / Base.pm
1 package DBIx::Class::Relationship::Base;
2
3 use strict;
4 use warnings;
5
6 use base qw/DBIx::Class/;
7
8 __PACKAGE__->mk_classdata('_relationships', { } );
9
10 =head1 NAME 
11
12 DBIx::Class::Relationship::Base - Inter-table relationships
13
14 =head1 SYNOPSIS
15
16 =head1 DESCRIPTION
17
18 This class handles relationships between the tables in your database
19 model. It allows your to set up relationships, and to perform joins
20 on searches.
21
22 =head1 METHODS
23
24 =head2 add_relationship
25
26   __PACKAGE__->add_relationship('relname', 'Foreign::Class', $cond, $attrs);
27
28 The condition needs to be an SQL::Abstract-style representation of the
29 join between the tables. For example, if you're creating a rel from Foo to Bar,
30
31   { 'foreign.foo_id' => 'self.id' }
32
33 will result in the JOIN clause
34
35   foo me JOIN bar bar ON bar.foo_id = me.id
36
37 You can specify as many foreign => self mappings as necessary.
38
39 Valid attributes are as follows:
40
41 =over 4
42
43 =item join_type
44
45 Explicitly specifies the type of join to use in the relationship. Any SQL
46 join type is valid, e.g. C<LEFT> or C<RIGHT>. It will be placed in the SQL
47 command immediately before C<JOIN>.
48
49 =item proxy
50
51 An arrayref containing a list of accessors in the foreign class to proxy in
52 the main class. If, for example, you do the following:
53   
54   __PACKAGE__->might_have(bar => 'Bar', undef, { proxy => qw[/ margle /] });
55   
56 Then, assuming Bar has an accessor named margle, you can do:
57
58   my $obj = Foo->find(1);
59   $obj->margle(10); # set margle; Bar object is created if it doesn't exist
60   
61 =item accessor
62
63 Specifies the type of accessor that should be created for the relationship.
64 Valid values are C<single> (for when there is only a single related object),
65 C<multi> (when there can be many), and C<filter> (for when there is a single
66 related object, but you also want the relationship accessor to double as
67 a column accessor). For C<multi> accessors, an add_to_* method is also
68 created, which calls C<create_related> for the relationship.
69
70 =back
71
72 =cut
73
74 sub add_relationship {
75   shift->result_source->add_relationship(@_);
76 }
77
78 sub relationships {
79   shift->result_source->relationships(@_);
80 }
81
82 sub relationship_info {
83   shift->result_source->relationship_info(@_);
84 }
85
86 =head2 search_related
87
88   My::Table->search_related('relname', $cond, $attrs);
89
90 =cut
91
92 sub search_related {
93   my $self = shift;
94   die "Can't call *_related as class methods" unless ref $self;
95   my $rel = shift;
96   my $attrs = { };
97   if (@_ > 1 && ref $_[$#_] eq 'HASH') {
98     $attrs = { %{ pop(@_) } };
99   }
100   my $rel_obj = $self->relationship_info($rel);
101   $self->throw( "No such relationship ${rel}" ) unless $rel_obj;
102   $attrs = { %{$rel_obj->{attrs} || {}}, %{$attrs || {}} };
103
104   $self->throw( "Invalid query: @_" ) if (@_ > 1 && (@_ % 2 == 1));
105   my $query = ((@_ > 1) ? {@_} : shift);
106
107   my ($cond) = $self->result_source->resolve_condition($rel_obj->{cond}, $rel, $self);
108   $query = ($query ? { '-and' => [ $cond, $query ] } : $cond);
109   #use Data::Dumper; warn Dumper($cond);
110   #warn $rel_obj->{class}." $meth $cond ".join(', ', @{$attrs->{bind}||[]});
111   return $self->result_source->related_source($rel
112            )->resultset->search($query, $attrs);
113 }
114
115 =head2 count_related
116
117   My::Table->count_related('relname', $cond, $attrs);
118
119 =cut
120
121 sub count_related {
122   my $self = shift;
123   return $self->search_related(@_)->count;
124 }
125
126 =head2 create_related
127
128   My::Table->create_related('relname', \%col_data);
129
130 =cut
131
132 sub create_related {
133   my $self = shift;
134   my $rel = shift;
135   return $self->search_related($rel)->create(@_);
136 }
137
138 =head2 new_related
139
140   My::Table->new_related('relname', \%col_data);
141
142 =cut
143
144 sub new_related {
145   my ($self, $rel, $values, $attrs) = @_;
146   return $self->search_related($rel)->new($values, $attrs);
147 }
148
149 =head2 find_related
150
151   My::Table->find_related('relname', @pri_vals | \%pri_vals);
152
153 =cut
154
155 sub find_related {
156   my $self = shift;
157   my $rel = shift;
158   return $self->search_related($rel)->find(@_);
159 }
160
161 =head2 find_or_create_related
162
163   My::Table->find_or_create_related('relname', \%col_data);
164
165 =cut
166
167 sub find_or_create_related {
168   my $self = shift;
169   return $self->find_related(@_) || $self->create_related(@_);
170 }
171
172 =head2 set_from_related
173
174   My::Table->set_from_related('relname', $rel_obj);
175
176 =cut
177
178 sub set_from_related {
179   my ($self, $rel, $f_obj) = @_;
180   my $rel_obj = $self->relationship_info($rel);
181   $self->throw( "No such relationship ${rel}" ) unless $rel_obj;
182   my $cond = $rel_obj->{cond};
183   $self->throw( "set_from_related can only handle a hash condition; the "
184     ."condition for $rel is of type ".(ref $cond ? ref $cond : 'plain scalar'))
185       unless ref $cond eq 'HASH';
186   my $f_class = $self->result_source->schema->class($rel_obj->{class});
187   $self->throw( "Object $f_obj isn't a ".$f_class )
188     unless $f_obj->isa($f_class);
189   foreach my $key (keys %$cond) {
190     next if ref $cond->{$key}; # Skip literals and complex conditions
191     $self->throw("set_from_related can't handle $key as key")
192       unless $key =~ m/^foreign\.([^\.]+)$/;
193     my $val = $f_obj->get_column($1);
194     $self->throw("set_from_related can't handle ".$cond->{$key}." as value")
195       unless $cond->{$key} =~ m/^self\.([^\.]+)$/;
196     $self->set_column($1 => $val);
197   }
198   return 1;
199 }
200
201 =head2 update_from_related
202
203   My::Table->update_from_related('relname', $rel_obj);
204
205 =cut
206
207 sub update_from_related {
208   my $self = shift;
209   $self->set_from_related(@_);
210   $self->update;
211 }
212
213 =head2 delete_related
214
215   My::Table->delete_related('relname', $cond, $attrs);
216
217 =cut
218
219 sub delete_related {
220   my $self = shift;
221   return $self->search_related(@_)->delete;
222 }
223
224 1;
225
226 =head1 AUTHORS
227
228 Matt S. Trout <mst@shadowcatsystems.co.uk>
229
230 =head1 LICENSE
231
232 You may distribute this code under the same terms as Perl itself.
233
234 =cut
235