658305dccac58b106178e42333131765c22768ba
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / CDBICompat / Relationships.pm
1 package # hide from PAUSE
2     DBIx::Class::CDBICompat::Relationships;
3
4 use strict;
5 use warnings;
6 use base 'DBIx::Class';
7
8 use Clone;
9 use DBIx::Class::CDBICompat::Relationship;
10 use Scalar::Util 'blessed';
11 use DBIx::Class::_Util qw(quote_sub perlstring);
12 use namespace::clean;
13
14 __PACKAGE__->mk_classdata('__meta_info' => {});
15
16
17 =head1 NAME
18
19 DBIx::Class::CDBICompat::Relationships - Emulate has_a(), has_many(), might_have() and meta_info()
20
21 =head1 DESCRIPTION
22
23 Emulate C<has_a>, C<has_many>, C<might_have> and C<meta_info>.
24
25 =cut
26
27 sub has_a {
28     my($self, $col, @rest) = @_;
29
30     $self->_declare_has_a($col, @rest);
31     $self->_mk_inflated_column_accessor($col);
32
33     return 1;
34 }
35
36
37 sub _declare_has_a {
38   my ($self, $col, $f_class, %args) = @_;
39   $self->throw_exception( "No such column ${col}" )
40    unless $self->has_column($col);
41   $self->ensure_class_loaded($f_class);
42
43   my $rel_info;
44
45   # Class::DBI allows Non database has_a with implicit deflate and inflate
46   # Hopefully the following will catch Non-database tables.
47   if( !$f_class->isa('DBIx::Class::Row') and !$f_class->isa('Class::DBI::Row') ) {
48     $args{'inflate'} ||= sub { $f_class->new(shift) }; # implicit inflate by calling new
49     $args{'deflate'} ||= sub { shift() . '' }; # implicit deflate by stringification
50   }
51
52   if ($args{'inflate'} || $args{'deflate'}) { # Non-database has_a
53     if (!ref $args{'inflate'}) {
54       my $meth = $args{'inflate'};
55       $args{'inflate'} = sub { $f_class->$meth(shift); };
56     }
57     if (!ref $args{'deflate'}) {
58       my $meth = $args{'deflate'};
59       $args{'deflate'} = sub { shift->$meth; };
60     }
61     $self->inflate_column($col, \%args);
62
63     $rel_info = {
64         class => $f_class
65     };
66   }
67   else {
68     $self->belongs_to($col, $f_class);
69     $rel_info = $self->result_source_instance->relationship_info($col);
70   }
71
72   $rel_info->{args} = \%args;
73
74   $self->_extend_meta(
75     has_a => $col,
76     $rel_info
77   );
78
79   return 1;
80 }
81
82 sub _mk_inflated_column_accessor {
83     my($class, $col) = @_;
84
85     return $class->mk_group_accessors('inflated_column' => $col);
86 }
87
88 sub has_many {
89   my ($class, $rel, $f_class, $f_key, $args) = @_;
90
91   my @f_method;
92
93   if (ref $f_class eq 'ARRAY') {
94     ($f_class, @f_method) = @$f_class;
95   }
96
97   if (ref $f_key eq 'HASH' && !$args) { $args = $f_key; undef $f_key; };
98
99   $args ||= {};
100   my $cascade = delete $args->{cascade} || '';
101   if (delete $args->{no_cascade_delete} || $cascade eq 'None') {
102     $args->{cascade_delete} = 0;
103   }
104   elsif( $cascade eq 'Delete' ) {
105     $args->{cascade_delete} = 1;
106   }
107   elsif( length $cascade ) {
108     warn "Unemulated cascade option '$cascade' in $class->has_many($rel => $f_class)";
109   }
110
111   if( !$f_key and !@f_method ) {
112       $class->ensure_class_loaded($f_class);
113       my $f_source = $f_class->result_source_instance;
114       ($f_key) = grep { $f_source->relationship_info($_)->{class} eq $class }
115                       $f_source->relationships;
116   }
117
118   $class->next::method($rel, $f_class, $f_key, $args);
119
120   my $rel_info = $class->result_source_instance->relationship_info($rel);
121   $args->{mapping}      = \@f_method;
122   $args->{foreign_key}  = $f_key;
123   $rel_info->{args} = $args;
124
125   $class->_extend_meta(
126     has_many => $rel,
127     $rel_info
128   );
129
130   if (@f_method) {
131     quote_sub "${class}::${rel}", sprintf( <<'EOC', perlstring $rel), { '$rf' => \sub { my $o = shift; $o = $o->$_ for @f_method; $o } };
132       my $rs = shift->search_related( %s => @_);
133       $rs->{attrs}{record_filter} = $rf;
134       return (wantarray ? $rs->all : $rs);
135 EOC
136
137     return 1;
138   }
139 }
140
141
142 sub might_have {
143   my ($class, $rel, $f_class, @columns) = @_;
144
145   my $ret;
146   if (ref $columns[0] || !defined $columns[0]) {
147     $ret = $class->next::method($rel, $f_class, @columns);
148   } else {
149     $ret = $class->next::method($rel, $f_class, undef,
150                                 { proxy => \@columns });
151   }
152
153   my $rel_info = $class->result_source_instance->relationship_info($rel);
154   $rel_info->{args}{import} = \@columns;
155
156   $class->_extend_meta(
157     might_have => $rel,
158     $rel_info
159   );
160
161   return $ret;
162 }
163
164
165 sub _extend_meta {
166     my ($class, $type, $rel, $val) = @_;
167
168 ### Explicitly not using the deep cloner as Clone exhibits specific behavior
169 ### wrt CODE references - it simply passes them as-is to the new structure
170 ### (without deparse/eval cycles). There likely is code that relies on this
171 ### so we just let sleeping dogs lie.
172     my $hash = Clone::clone($class->__meta_info || {});
173
174     $val->{self_class} = $class;
175     $val->{type}       = $type;
176     $val->{accessor}   = $rel;
177
178     $hash->{$type}{$rel} = DBIx::Class::CDBICompat::Relationship->new($val);
179     $class->__meta_info($hash);
180 }
181
182
183 sub meta_info {
184     my ($class, $type, $rel) = @_;
185     my $meta = $class->__meta_info;
186     return $meta unless $type;
187
188     my $type_meta = $meta->{$type};
189     return $type_meta unless $rel;
190     return $type_meta->{$rel};
191 }
192
193
194 sub search {
195   my $self = shift;
196   my $attrs = {};
197   if (@_ > 1 && ref $_[$#_] eq 'HASH') {
198     $attrs = { %{ pop(@_) } };
199   }
200   my $where = (@_ ? ((@_ == 1) ? ((ref $_[0] eq "HASH") ? { %{+shift} } : shift)
201                                : {@_})
202                   : undef());
203   if (ref $where eq 'HASH') {
204     foreach my $key (keys %$where) { # has_a deflation hack
205       $where->{$key} = ''.$where->{$key} if (
206         defined blessed $where->{$key}
207           and
208         $where->{$key}->isa('DBIx::Class')
209       );
210     }
211   }
212   $self->next::method($where, $attrs);
213 }
214
215 sub new_related {
216   return shift->search_related(shift)->new_result(shift);
217 }
218
219 =head1 FURTHER QUESTIONS?
220
221 Check the list of L<additional DBIC resources|DBIx::Class/GETTING HELP/SUPPORT>.
222
223 =head1 COPYRIGHT AND LICENSE
224
225 This module is free software L<copyright|DBIx::Class/COPYRIGHT AND LICENSE>
226 by the L<DBIx::Class (DBIC) authors|DBIx::Class/AUTHORS>. You can
227 redistribute it and/or modify it under the same terms as the
228 L<DBIx::Class library|DBIx::Class/COPYRIGHT AND LICENSE>.
229
230 =cut
231
232 1;