File Coverage

blib/lib/DBR/Record/Maker.pm
Criterion Covered Total %
statement 154 158 97.4
branch 40 68 58.8
condition 7 12 58.3
subroutine 18 18 100.0
pod 0 2 0.0
total 219 258 84.8


line stmt bran cond sub pod time code
1             package DBR::Record::Maker;
2              
3 18     18   105 use strict;
  18         1299  
  18         1316  
4 18     18   97 use base 'DBR::Common';
  18         8193  
  18         1379  
5 18     18   101 use Carp;
  18         36  
  18         1311  
6 18     18   18081 use Symbol qw(qualify_to_ref delete_package);
  18         28217  
  18         4351  
7 18     18   9139 use DBR::Record::Helper;
  18         68  
  18         683  
8 18     18   13164 use DBR::Record::Base;
  18         50  
  18         521  
9 18     18   134 use DBR::Query::Part;
  18         44  
  18         35392  
10              
11             #IDPOOL is a revolving door of package ids... we do this to guard against memory leaks... juuust in case
12             my @IDPOOL = (1..200);
13             my $classidx = 200; #overflow
14              
15             my $BASECLASS = 'DBR::_R';
16              
17             sub new {
18 27     27 0 76 my( $package ) = shift;
19 27         254 my %params = @_;
20 27         132 my $self = {
21             session => $params{session},
22             };
23              
24 27         117 bless( $self, $package ); # BS object
25              
26 27 50       518 $self->{session} or croak 'session is required';
27 27 50       971 my $query = $params{query} or croak 'query is required';
28              
29 27   33     140 $self->{classidx} = (shift @IDPOOL) || ++$classidx;
30              
31 27 50       151 $self->_prep($query) or return $self->_error('prep failed');
32              
33 27         628 return $self;
34             }
35              
36 39     39 0 358 sub class { $_[0]->{recordclass} }
37              
38             sub _prep{
39 27     27   55 my $self = shift;
40 27         57 my $query = shift;
41              
42 27         123 my $class = $BASECLASS . $self->{classidx};
43 27         91 $self->{recordclass} = $class;
44              
45 27 50       108 my @fields = $query->fields or confess 'Failed to get query fields';
46              
47 27         67 my @table_ids;
48             # It's important that we preserve the specific field objects from the query. They have payloads that new ones do not.
49 27         79 foreach my $field (@fields){
50 43 50       166 my $field_id = $field->field_id or next; # Anon fields have no field_id
51 43         249 my $table_id = $field->table_id;
52 43         213 $self->{fieldmap}->{ $field_id } = $field;
53              
54 43         169 push @table_ids, $table_id;
55             }
56              
57 27         63 my %tablemap;
58             my %pkmap;
59 0         0 my %flookup;
60 0         0 my @allrelations;
61 0         0 my @tablenames;
62 27         298 foreach my $table_id ($self->_uniq( @table_ids )){
63              
64 27 50       206 my $table = DBR::Config::Table->new(
65             session => $self->{session},
66             table_id => $table_id,
67             ) or return $self->_error('Failed to create table object');
68              
69 27 50       160 my $allfields = $table->fields or return $self->_error('failed to retrieve fields for table');
70              
71 27         141 my @pk;
72             #We need to check to make sure that all PK fields are included in the query results.
73             #These are field objects, but don't use them elsewhere. They are devoid of query indexes
74 27         179 foreach my $checkfield (@$allfields){
75 103         328 my $field = $self->{fieldmap}->{ $checkfield->field_id };
76              
77 103 100       393 if( $checkfield->is_pkey ){
78 27 50       151 if(!$field){
79 0         0 return $self->_error('Resultset is missing primary key field ' . $checkfield->name);
80             }
81              
82 27         119 push @pk, $field->clone( with_index => 1 ); # Make a clean copy of the field object in case this one has an alias
83             }else{
84 76 100       219 if(!$field){
85 60         104 push @fields, $checkfield; #not in the resultset, but we should still know about it
86 60         415 $self->{fieldmap}->{ $checkfield->field_id } = $checkfield;
87             }
88             }
89 103   66     559 $field ||= $checkfield;
90              
91 103         308 $flookup{ $field->name } = $field->clone( with_index => 1 ); # Make a clean copy of the field object in case this one has an alias
92             }
93              
94 27         98 $tablemap{$table_id} = $table;
95 27         83 $pkmap{$table_id} = \@pk;
96              
97 27 50       173 my $relations = $table->relations or return $self->_error('failed to retrieve relations for table');
98 27         62 push @allrelations, @$relations;
99 27         109 push @tablenames, $table->name;
100             }
101 27         117 $self->{name} = join('/',@tablenames);
102              
103 27 50       354 my $scope = $query->scope or croak 'failed to fetch scope object';
104 27 50       132 my $instance = $query->instance or croak 'failed to fetch instance object';
105              
106 27 50       193 my $helper = DBR::Record::Helper->new(
107             session => $self->{session},
108             instance => $instance,
109             tablemap => \%tablemap,
110             pkmap => \%pkmap,
111             flookup => \%flookup,
112             scope => $scope,
113             lastidx => $query->lastidx,
114             ) or return $self->_error('Failed to create Helper object');
115              
116 27         315 my $mode = 'rw';
117 27         79 foreach my $field (@fields){
118 103         159 my $mymode = $mode;
119 103 100 66     536 $mymode = 'ro' if $field->is_readonly or $instance->is_readonly;
120 103 50       368 $self->_mk_accessor(
121             mode => $mymode,
122             field => $field->clone(with_index => 1), # Make a clean copy of the field object in case this one has an alias
123             helper => $helper,
124             ) or return $self->_error('Failed to create accessor');
125             }
126              
127 27         90 foreach my $relation (@allrelations){
128 35 50       184 $self->_mk_relation(
129             relation => $relation,
130             helper => $helper,
131             ) or return $self->_error('Failed to create relation');
132             }
133              
134 27         146 my $isa = qualify_to_ref( $self->{recordclass} . '::ISA');
135 27         399 @{ *$isa } = ('DBR::Record::Base');
  27         634  
136              
137 27 50       160 $self->_mk_method(
138             method => 'set',
139             helper => $helper,
140             ) or $self->_error('Failed to create set method');
141              
142 27 50       112 $self->_mk_method(
143             method => 'delete',
144             helper => $helper,
145             ) or $self->_error('Failed to create set method');
146 27         231 return 1;
147             }
148              
149              
150              
151              
152              
153             sub _mk_accessor{
154 103     103   236 my $self = shift;
155 103         521 my %params = @_;
156              
157 103 50       410 my $mode = $params{mode} or return $self->_error('Mode is required');
158 103 50       339 my $helper = $params{helper} or return $self->_error('helper is required');
159              
160 103         164 my $field = $params{field};
161 103         297 my $method = $field->name;
162              
163 103         175 my $obj = '$_[0]';
164 103         406 my $record = $obj . '[0]';
165 103         162 my $buddy = $obj . '[1]';
166              
167 103         123 my $setvalue = '$_[1]';
168 103         130 my $value;
169              
170 103         460 my $idx = $field->index;
171 103 100       277 if(defined $idx){ #did we actually fetch this?
172 43         136 $value = $record . '[' . $idx . ']';
173             }else{
174 60         240 $value = "\$h->getfield( $record, \$f )";
175             }
176              
177 103         140 my $code;
178             my $trans;
179 103 100       674 if ($trans = $field->translator){
180 27         561 $value = "\$t->forward($value)";
181             }
182              
183 103 100 66     1194 if($mode eq 'rw' && $field){
    50          
184 76         380 $code = " exists( $setvalue ) ? \$h->setfield( $record, \$f, $setvalue ) : $value ";
185             }elsif($mode eq 'ro'){
186 27         163 $code = " $value ";
187             }
188 103         512 $code = "sub {$code}";
189              
190 103         2348 $self->_logDebug3("$method = $code");
191              
192 103 50       441 my $subref = _eval_accessor($helper,$field,$trans,$code) or $self->_error('Failed to eval accessor ' . $@);
193              
194 103         637 my $symbol = qualify_to_ref( $self->{recordclass} . '::' . $method );
195 103         2337 *$symbol = $subref;
196              
197 103         852 return 1;
198             }
199              
200             #Seperate sub for scope cleanliness
201             # This creates a blend of custom written perl code, and closure.
202             sub _eval_accessor{
203 103     103   294 my $h = shift; #helper
204 103         146 my $f = shift; #field
205 103         141 my $t = shift; #translator
206              
207 103         37181 return eval shift;
208             }
209              
210              
211              
212              
213             sub _mk_relation{
214 35     35   102 my $self = shift;
215 35         159 my %params = @_;
216              
217 35 50       148 my $relation = $params{relation} or return $self->_error('relation is required');
218 35 50       121 my $helper = $params{helper} or return $self->_error('helper is required');
219              
220 35         183 my $method = $relation->name;
221              
222 35         2684 my $obj = '$_[0]';
223 35         72 my $record = $obj . '[0]';
224 35         73 my $buddy = $obj . '[1]';
225              
226 35 50       155 my $field_id = $relation->field_id or return $self->_error('failed to retrieve field_id');
227              
228 35 50       160 my $field = $self->{fieldmap}->{ $field_id } or return $self->_error("field_id '$field_id' is not valid");
229              
230 35         101 my $code = "\$h->getrelation( $obj, \$r, \$f )";
231              
232 35         94 $code = "sub {$code}";
233 35         385 $self->_logDebug3("$method = $code");
234              
235 35 50       139 my $subref = _eval_relation($helper,$relation,$field,$code) or $self->_error('Failed to eval relation' . $@);
236              
237             {
238 18     18   152 no warnings 'redefine';
  18         41  
  18         12810  
  35         68  
239 35         200 my $symbol = qualify_to_ref( $self->{recordclass} . '::' . $method );
240 35         911 *$symbol = $subref;
241             }
242              
243 35         439 return 1;
244             }
245              
246             #Seperate sub for scope cleanliness
247             # This creates a blend of custom written perl code, and closure.
248             sub _eval_relation{
249 35     35   77 my $h = shift;
250 35         52 my $r = shift;
251 35         56 my $f = shift;
252              
253 35         3139 return eval shift;
254             }
255              
256              
257              
258              
259             sub _mk_method{
260 54     54   98 my $self = shift;
261 54         194 my %params = @_;
262              
263 54 50       185 my $helper = $params{helper} or return $self->_error('helper is required');
264 54 50       160 my $method = $params{method} or return $self->_error('method is required');
265              
266 54         88 my $obj = 'shift';
267 54         100 my $record = $obj . '->[0]';
268              
269 54         148 my $code = "\$h->$method($record,\@_)";
270              
271 54         136 $code = "sub {$code}";
272 54         228 $self->_logDebug3("$method = $code");
273              
274 54 50       197 my $subref = _eval_method($helper,$code) or $self->_error('Failed to eval method' . $@);
275 54         285 my $symbol = qualify_to_ref( $self->{recordclass} . '::' . $method );
276 54         11724 *$symbol = $subref;
277              
278 54         275 return 1;
279             }
280              
281             #Seperate sub for scope cleanliness
282             sub _eval_method{
283 54     54   100 my $h = shift;
284 54         5724 return eval shift;
285             }
286              
287              
288              
289             sub DESTROY{ # clean up the temporary object from the symbol table
290 27     27   3452 my $self = shift;
291 27         97 my $class = $self->{recordclass};
292             #$self->_logDebug2("Destroy $self->{name} ($class)");
293 27         160 push @IDPOOL, $self->{classidx};
294              
295             #print STDERR "DESTROY $class, $self->{classidx}\n";
296 27         156 Symbol::delete_package($class);
297             }
298              
299             1;
300              
301              
302              
303             1;