Commit | Line | Data |
a9c8094b |
1 | package # hide from PAUSE |
2 | DBIx::Class::CDBICompat::Relationships; |
3 | |
4 | use strict; |
5 | use warnings; |
5e0eea35 |
6 | use base 'DBIx::Class'; |
a9c8094b |
7 | |
8 | use Clone; |
9 | use DBIx::Class::CDBICompat::Relationship; |
dff89602 |
10 | use Scalar::Util 'blessed'; |
8d73fcd4 |
11 | use DBIx::Class::_Util qw(quote_sub perlstring); |
51ec0382 |
12 | use namespace::clean; |
a9c8094b |
13 | |
14 | __PACKAGE__->mk_classdata('__meta_info' => {}); |
15 | |
16 | |
17 | =head1 NAME |
18 | |
b24d86a1 |
19 | DBIx::Class::CDBICompat::Relationships - Emulate has_a(), has_many(), might_have() and meta_info() |
a9c8094b |
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 { |
c79bd6e9 |
28 | my($self, $col, @rest) = @_; |
d4daee7b |
29 | |
c79bd6e9 |
30 | $self->_declare_has_a($col, @rest); |
31 | $self->_mk_inflated_column_accessor($col); |
d4daee7b |
32 | |
c79bd6e9 |
33 | return 1; |
34 | } |
35 | |
36 | |
37 | sub _declare_has_a { |
a9c8094b |
38 | my ($self, $col, $f_class, %args) = @_; |
c79bd6e9 |
39 | $self->throw_exception( "No such column ${col}" ) |
40 | unless $self->has_column($col); |
a9c8094b |
41 | $self->ensure_class_loaded($f_class); |
d4daee7b |
42 | |
1097f5e4 |
43 | my $rel_info; |
a9c8094b |
44 | |
2040ad73 |
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 | |
a9c8094b |
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); |
d4daee7b |
62 | |
1097f5e4 |
63 | $rel_info = { |
a9c8094b |
64 | class => $f_class |
65 | }; |
66 | } |
67 | else { |
68 | $self->belongs_to($col, $f_class); |
1097f5e4 |
69 | $rel_info = $self->result_source_instance->relationship_info($col); |
a9c8094b |
70 | } |
d4daee7b |
71 | |
1097f5e4 |
72 | $rel_info->{args} = \%args; |
d4daee7b |
73 | |
a9c8094b |
74 | $self->_extend_meta( |
75 | has_a => $col, |
1097f5e4 |
76 | $rel_info |
a9c8094b |
77 | ); |
c79bd6e9 |
78 | |
a9c8094b |
79 | return 1; |
80 | } |
81 | |
c79bd6e9 |
82 | sub _mk_inflated_column_accessor { |
83 | my($class, $col) = @_; |
d4daee7b |
84 | |
c79bd6e9 |
85 | return $class->mk_group_accessors('inflated_column' => $col); |
86 | } |
a9c8094b |
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 ||= {}; |
deff792e |
100 | my $cascade = delete $args->{cascade} || ''; |
101 | if (delete $args->{no_cascade_delete} || $cascade eq 'None') { |
a9c8094b |
102 | $args->{cascade_delete} = 0; |
103 | } |
deff792e |
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 | } |
a9c8094b |
110 | |
111 | if( !$f_key and !@f_method ) { |
4656f62f |
112 | $class->ensure_class_loaded($f_class); |
a9c8094b |
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 | |
1097f5e4 |
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 | |
a9c8094b |
125 | $class->_extend_meta( |
126 | has_many => $rel, |
1097f5e4 |
127 | $rel_info |
a9c8094b |
128 | ); |
129 | |
130 | if (@f_method) { |
8d73fcd4 |
131 | quote_sub "${class}::${rel}", sprintf( <<'EOC', perlstring $rel), { '$rf' => \sub { my $o = shift; $o = $o->$_ for @f_method; $o } }; |
e5053694 |
132 | my $rs = shift->related_resultset(%s)->search_rs( @_); |
8d73fcd4 |
133 | $rs->{attrs}{record_filter} = $rf; |
134 | return (wantarray ? $rs->all : $rs); |
135 | EOC |
136 | |
a9c8094b |
137 | return 1; |
138 | } |
a9c8094b |
139 | } |
140 | |
141 | |
142 | sub might_have { |
143 | my ($class, $rel, $f_class, @columns) = @_; |
d4daee7b |
144 | |
a9c8094b |
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 | } |
1097f5e4 |
152 | |
153 | my $rel_info = $class->result_source_instance->relationship_info($rel); |
154 | $rel_info->{args}{import} = \@columns; |
155 | |
a9c8094b |
156 | $class->_extend_meta( |
157 | might_have => $rel, |
1097f5e4 |
158 | $rel_info |
a9c8094b |
159 | ); |
d4daee7b |
160 | |
a9c8094b |
161 | return $ret; |
162 | } |
163 | |
164 | |
165 | sub _extend_meta { |
166 | my ($class, $type, $rel, $val) = @_; |
1c30a2e4 |
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 || {}); |
a9c8094b |
173 | |
174 | $val->{self_class} = $class; |
175 | $val->{type} = $type; |
176 | $val->{accessor} = $rel; |
177 | |
1c30a2e4 |
178 | $hash->{$type}{$rel} = DBIx::Class::CDBICompat::Relationship->new($val); |
179 | $class->__meta_info($hash); |
a9c8094b |
180 | } |
181 | |
aa11d765 |
182 | |
a9c8094b |
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 |
dff89602 |
205 | $where->{$key} = ''.$where->{$key} if ( |
206 | defined blessed $where->{$key} |
207 | and |
208 | $where->{$key}->isa('DBIx::Class') |
209 | ); |
a9c8094b |
210 | } |
211 | } |
212 | $self->next::method($where, $attrs); |
213 | } |
214 | |
ee333775 |
215 | sub new_related { |
e5053694 |
216 | return shift->search_related(shift)->new_result(@_); |
ee333775 |
217 | } |
218 | |
a2bd3796 |
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 | |
a9c8094b |
232 | 1; |