| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Array::To::Moose; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# Copyright (c) Stanford University. June 6th, 2010. |
|
4
|
|
|
|
|
|
|
# All rights reserved. |
|
5
|
|
|
|
|
|
|
# Author: Sam Brain <samb@stanford.edu> |
|
6
|
|
|
|
|
|
|
# This library is free software; you can redistribute it and/or modify |
|
7
|
|
|
|
|
|
|
# it under the same terms as Perl itself, either Perl version 5.8.8 or, |
|
8
|
|
|
|
|
|
|
# at your option, any later version of Perl 5 you may have available. |
|
9
|
|
|
|
|
|
|
# |
|
10
|
|
|
|
|
|
|
|
|
11
|
21
|
|
|
21
|
|
445120
|
use 5.008008; |
|
|
21
|
|
|
|
|
82
|
|
|
12
|
21
|
|
|
21
|
|
106
|
use strict; |
|
|
21
|
|
|
|
|
46
|
|
|
|
21
|
|
|
|
|
430
|
|
|
13
|
21
|
|
|
21
|
|
109
|
use warnings; |
|
|
21
|
|
|
|
|
37
|
|
|
|
21
|
|
|
|
|
822
|
|
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
require Exporter; |
|
16
|
21
|
|
|
21
|
|
94
|
use base qw( Exporter ); |
|
|
21
|
|
|
|
|
39
|
|
|
|
21
|
|
|
|
|
3918
|
|
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
our %EXPORT_TAGS = ( |
|
19
|
|
|
|
|
|
|
'ALL' => [ qw( array_to_moose |
|
20
|
|
|
|
|
|
|
throw_nonunique_keys throw_multiple_rows |
|
21
|
|
|
|
|
|
|
set_class_ind set_key_ind ) ], |
|
22
|
|
|
|
|
|
|
'TESTING' => [ qw( _check_descriptor _check_subobj |
|
23
|
|
|
|
|
|
|
_check_ref_attribs _check_non_ref_attribs ) ], |
|
24
|
|
|
|
|
|
|
); |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'ALL'} }, @{ $EXPORT_TAGS{'TESTING'} } ); |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
our @EXPORT = qw( array_to_moose |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
); |
|
31
|
|
|
|
|
|
|
|
|
32
|
21
|
|
|
21
|
|
14039
|
use version; our $VERSION = qv('0.0.9'); |
|
|
21
|
|
|
|
|
42069
|
|
|
|
21
|
|
|
|
|
115
|
|
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# BEGIN { $Exporter::Verbose=1 }; |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
#BEGIN { print "Got Array::To:Moose Module\n" } |
|
37
|
|
|
|
|
|
|
|
|
38
|
21
|
|
|
21
|
|
17284
|
use Params::Validate::Array qw(:all); |
|
|
21
|
|
|
|
|
251279
|
|
|
|
21
|
|
|
|
|
150
|
|
|
39
|
21
|
|
|
21
|
|
22735
|
use Array::GroupBy qw(igroup_by str_row_equal); |
|
|
21
|
|
|
|
|
21769
|
|
|
|
21
|
|
|
|
|
1502
|
|
|
40
|
21
|
|
|
21
|
|
118
|
use Carp; |
|
|
21
|
|
|
|
|
38
|
|
|
|
21
|
|
|
|
|
938
|
|
|
41
|
21
|
|
|
21
|
|
17254
|
use Data::Dumper; |
|
|
21
|
|
|
|
|
195987
|
|
|
|
21
|
|
|
|
|
1975
|
|
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
$Carp::Verbose = 1; |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
$Data::Dumper::Terse = 1; |
|
46
|
|
|
|
|
|
|
$Data::Dumper::Indent = 1; |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# strings for "key => ..." and "class => ..." indicators |
|
49
|
|
|
|
|
|
|
my ($KEY, $CLASS); |
|
50
|
|
|
|
|
|
|
|
|
51
|
21
|
|
|
21
|
|
221
|
BEGIN { $KEY = 'key' ; $CLASS = 'class' } |
|
|
21
|
|
|
|
|
35944
|
|
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# throw error if a HashRef[] key found to be non-unique |
|
54
|
|
|
|
|
|
|
my $throw_nonunique_keys; |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# throw error if there are multiple candidate rows for an attribute |
|
57
|
|
|
|
|
|
|
# which is a single object, "isa => 'MyObject'" |
|
58
|
|
|
|
|
|
|
my $throw_multiple_rows; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
############################################ |
|
61
|
|
|
|
|
|
|
# Set the indicators for "key => ..." and "class => ..." |
|
62
|
|
|
|
|
|
|
# If there is no arg, reset them back to the default 'key' and 'class' |
|
63
|
|
|
|
|
|
|
############################################ |
|
64
|
|
|
|
|
|
|
sub set_key_ind { |
|
65
|
2
|
50
|
66
|
2
|
1
|
19
|
croak "set_key_ind('$_[0]') not a legal identifier" |
|
66
|
|
|
|
|
|
|
if defined $_[0] and $_[0] !~ /^\w+$/; |
|
67
|
|
|
|
|
|
|
|
|
68
|
2
|
100
|
|
|
|
8
|
$KEY = defined $_[0] ? $_[0] : 'key'; |
|
69
|
|
|
|
|
|
|
} |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
############################################ |
|
72
|
|
|
|
|
|
|
sub set_class_ind { |
|
73
|
2
|
50
|
66
|
2
|
1
|
1598
|
croak "set_class_ind('$_[0]') not a legal identifier" |
|
74
|
|
|
|
|
|
|
if defined $_[0] and $_[0] !~ /^\w+$/; |
|
75
|
|
|
|
|
|
|
|
|
76
|
2
|
100
|
|
|
|
9
|
$CLASS = defined $_[0] ? $_[0] : 'class'; |
|
77
|
|
|
|
|
|
|
} |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
######################################## |
|
80
|
|
|
|
|
|
|
# throw error if non-unique keys in a HashRef['] is causing already-constructed |
|
81
|
|
|
|
|
|
|
# Moose objects to be overwritten |
|
82
|
|
|
|
|
|
|
# throw_nonunique_keys() to set, throw_nonunique_keys(0) to unset |
|
83
|
|
|
|
|
|
|
######################################## |
|
84
|
0
|
0
|
|
0
|
1
|
0
|
sub throw_nonunique_keys { $throw_nonunique_keys = defined $_[0] ? $_[0] : 1 } |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
######################################## |
|
87
|
|
|
|
|
|
|
# throw error if a single object attribute has multiple data rows |
|
88
|
|
|
|
|
|
|
# throw_multiple_rows() to set throw_multiple_rows(0) to unset |
|
89
|
|
|
|
|
|
|
######################################## |
|
90
|
0
|
0
|
|
0
|
1
|
0
|
sub throw_multiple_rows { $throw_multiple_rows = defined $_[0] ? $_[0] : 1 } |
|
91
|
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
########## |
|
93
|
|
|
|
|
|
|
# Usage |
|
94
|
|
|
|
|
|
|
# my $moose_object_ref = array_to_moose( data => $array_ref, |
|
95
|
|
|
|
|
|
|
# desc => { ... }, |
|
96
|
|
|
|
|
|
|
# ); |
|
97
|
|
|
|
|
|
|
############################################ |
|
98
|
|
|
|
|
|
|
sub array_to_moose { |
|
99
|
46
|
|
|
46
|
1
|
428777
|
my ($data, $desc) = validate(@_, |
|
100
|
|
|
|
|
|
|
[ data => { type => ARRAYREF }, |
|
101
|
|
|
|
|
|
|
desc => { type => HASHREF }, |
|
102
|
|
|
|
|
|
|
] |
|
103
|
|
|
|
|
|
|
); |
|
104
|
|
|
|
|
|
|
|
|
105
|
46
|
50
|
|
|
|
2266
|
croak "'data => ...' isn't a 2D array (AoA)" |
|
106
|
|
|
|
|
|
|
unless ref($data->[0]); |
|
107
|
|
|
|
|
|
|
|
|
108
|
46
|
50
|
|
|
|
121
|
croak 'empty descriptor' |
|
109
|
|
|
|
|
|
|
unless keys %$desc; |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
#print "data ", Dumper($data), "\ndesc ", Dumper($desc); |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
|
114
|
46
|
|
|
|
|
70
|
my $result = []; # returned result is either an array or a hash of objects |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# extract column of possible hash key |
|
117
|
46
|
|
|
|
|
62
|
my $keycol; |
|
118
|
|
|
|
|
|
|
|
|
119
|
46
|
100
|
|
|
|
149
|
if (exists $desc->{$KEY}) { |
|
120
|
12
|
|
|
|
|
18
|
$keycol = $desc->{$KEY}; |
|
121
|
|
|
|
|
|
|
|
|
122
|
12
|
|
|
|
|
22
|
$result = {}; # returning a hashref |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# _check_descriptor returns: |
|
127
|
|
|
|
|
|
|
# $class, the class of the object |
|
128
|
|
|
|
|
|
|
# $attribs, a hashref (attrib => column_number) of "simple" attributes |
|
129
|
|
|
|
|
|
|
# (column numbers only) |
|
130
|
|
|
|
|
|
|
# $ref_attribs, a hashref of attribute/column number values for |
|
131
|
|
|
|
|
|
|
# non-simple attributes, currently limited to "ArrayRef[`a]", |
|
132
|
|
|
|
|
|
|
# where `a is e.g 'Str', etc (i.e. `a is not a class) |
|
133
|
|
|
|
|
|
|
# $sub_desc, a hashref of sub-objects. |
|
134
|
|
|
|
|
|
|
# the keys are the attrib. names, the values the |
|
135
|
|
|
|
|
|
|
# descriptors of the next level down |
|
136
|
|
|
|
|
|
|
|
|
137
|
46
|
|
|
|
|
115
|
my ($class, $attribs, $ref_attribs, $sub_obj_desc) = |
|
138
|
|
|
|
|
|
|
_check_descriptor($data, $desc); |
|
139
|
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
#print "data ", Dumper($data), "\nattrib = ", Dumper($attribs), |
|
141
|
|
|
|
|
|
|
# "\nargs = ", Dumper([ values %$attribs ]); |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
#print "\$ref_attribs ", Dumper($ref_attribs); exit; |
|
144
|
|
|
|
|
|
|
|
|
145
|
46
|
|
|
|
|
245
|
my $iter = igroup_by( |
|
146
|
|
|
|
|
|
|
data => $data, |
|
147
|
|
|
|
|
|
|
compare => \&str_row_equal, |
|
148
|
|
|
|
|
|
|
args => [ values %$attribs ], |
|
149
|
|
|
|
|
|
|
); |
|
150
|
|
|
|
|
|
|
|
|
151
|
46
|
|
|
|
|
1860
|
while (my $subset = $iter->()) { |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
#print "subset: ", Dumper($subset), "\n"; |
|
154
|
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
#print "before 1: attrib ", Dumper($attribs), "\ndata ", Dumper($subset); |
|
156
|
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
# change attribs from col numbers to values: |
|
158
|
|
|
|
|
|
|
# from: { name => 1, sex => 2, ... } |
|
159
|
|
|
|
|
|
|
# to { name => 'Smith, J.', sex => 'male', ... } |
|
160
|
101
|
|
|
|
|
2920
|
my %attribs = map { $_ => $subset->[0]->[$attribs->{$_}] } keys %$attribs; |
|
|
235
|
|
|
|
|
613
|
|
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
# print "after 1: attrib ", Dumper(\%attribs), "\n"; |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# add the 'simple ArrayRef' sub-objects |
|
166
|
|
|
|
|
|
|
# (there should really be only one of these - test for it?) |
|
167
|
101
|
|
|
|
|
356
|
while (my($attr_name, $col) = each %$ref_attribs) { |
|
168
|
0
|
|
|
|
|
0
|
my @col = map { $_->[$col] } @$subset; |
|
|
0
|
|
|
|
|
0
|
|
|
169
|
0
|
|
|
|
|
0
|
$attribs{$attr_name} = \@col; |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# ... or ... |
|
172
|
|
|
|
|
|
|
#$attribs{$attr_name} = [ map { $_->[$col] } @$subset ]; |
|
173
|
|
|
|
|
|
|
} |
|
174
|
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
# print "after 2: attrib ", Dumper(\%attribs), "\n"; |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
# sub-objects - recursive call to array_to_moose() |
|
178
|
101
|
|
|
|
|
271
|
while( my($attr_name, $desc) = each %$sub_obj_desc) { |
|
179
|
|
|
|
|
|
|
|
|
180
|
33
|
50
|
|
|
|
118
|
my $type = $class->meta->find_attribute_by_name($attr_name)->type_constraint |
|
181
|
|
|
|
|
|
|
or croak "Moose attribute '$attr_name' has no type"; |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
#print "'$attr_name' has type '$type'"; |
|
184
|
|
|
|
|
|
|
|
|
185
|
33
|
|
|
|
|
2626
|
my $sub_obj = array_to_moose( data => $subset, |
|
186
|
|
|
|
|
|
|
desc => $desc, |
|
187
|
|
|
|
|
|
|
); |
|
188
|
|
|
|
|
|
|
|
|
189
|
33
|
|
|
|
|
86
|
$sub_obj = _check_subobj($class, $attr_name, $type, $sub_obj); |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
#print "type $type\n"; |
|
192
|
|
|
|
|
|
|
|
|
193
|
33
|
|
|
|
|
138
|
$attribs{$attr_name} = $sub_obj; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# print "after 2: attrib ", Dumper(\%attribs), "\n"; |
|
197
|
|
|
|
|
|
|
|
|
198
|
101
|
|
|
|
|
365
|
my $obj; |
|
199
|
101
|
|
|
|
|
126
|
eval { $obj = $class->meta->new_object(%attribs) }; |
|
|
101
|
|
|
|
|
318
|
|
|
200
|
101
|
50
|
|
|
|
92784
|
croak "Can't make a new '$class' object:\n$@\n" |
|
201
|
|
|
|
|
|
|
if $@; |
|
202
|
|
|
|
|
|
|
|
|
203
|
101
|
100
|
|
|
|
195
|
if (defined $keycol) { |
|
204
|
28
|
|
|
|
|
50
|
my $key_name = $subset->[0]->[$keycol]; |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# optionally croak if we are overwriting an existing hash entry |
|
207
|
|
|
|
|
|
|
croak "Non-unique key '$key_name' in '", $desc->{$CLASS}, "' class" |
|
208
|
28
|
50
|
33
|
|
|
74
|
if exists $result->{$key_name} and $throw_nonunique_keys; |
|
209
|
|
|
|
|
|
|
|
|
210
|
28
|
|
|
|
|
128
|
$result->{$key_name} = $obj; |
|
211
|
|
|
|
|
|
|
} else { |
|
212
|
73
|
|
|
|
|
80
|
push @{$result}, $obj; |
|
|
73
|
|
|
|
|
321
|
|
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
} |
|
215
|
46
|
|
|
|
|
491
|
return $result; |
|
216
|
|
|
|
|
|
|
} |
|
217
|
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
############################################ |
|
219
|
|
|
|
|
|
|
# Usage: my ($class, $attribs, $ref_attribs, $sub_desc) |
|
220
|
|
|
|
|
|
|
# = _check_descriptor($data, $desc) |
|
221
|
|
|
|
|
|
|
# |
|
222
|
|
|
|
|
|
|
# Check the correctness of the descriptor hashref, $desc. |
|
223
|
|
|
|
|
|
|
# |
|
224
|
|
|
|
|
|
|
# Checks of descriptor $desc include: |
|
225
|
|
|
|
|
|
|
# 1. "class => 'MyClass'" line exists, and that class "MyClass" has |
|
226
|
|
|
|
|
|
|
# been defined |
|
227
|
|
|
|
|
|
|
# 2. for "attrib => N" |
|
228
|
|
|
|
|
|
|
# or "key => N" lines, N, the column number, is an integer, and that |
|
229
|
|
|
|
|
|
|
# the column numbers is within limits of the data |
|
230
|
|
|
|
|
|
|
# 3. For "attrib => [N]", (note square brackets), N, the columnn number, |
|
231
|
|
|
|
|
|
|
# is within limits of the data |
|
232
|
|
|
|
|
|
|
# |
|
233
|
|
|
|
|
|
|
# Returns: |
|
234
|
|
|
|
|
|
|
# $class, the class name, |
|
235
|
|
|
|
|
|
|
# $attribs, hashref (name => column_index) of "simple" attributes |
|
236
|
|
|
|
|
|
|
# $ref_attribs hashref (name => column_index) of attribs which are |
|
237
|
|
|
|
|
|
|
# ArrayRef[']s of simple types (i.e. not a Class) |
|
238
|
|
|
|
|
|
|
# (HashRef[']s not implemented) |
|
239
|
|
|
|
|
|
|
# $sub_desc hashref (name => desc) of sub-object descriptors |
|
240
|
|
|
|
|
|
|
############################################ |
|
241
|
|
|
|
|
|
|
sub _check_descriptor { |
|
242
|
46
|
|
|
46
|
|
72
|
my ($data, $desc) = @_; |
|
243
|
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# remove from production! |
|
245
|
46
|
50
|
|
|
|
112
|
croak "_check_descriptor() needs two arguments" |
|
246
|
|
|
|
|
|
|
unless @_ == 2; |
|
247
|
|
|
|
|
|
|
|
|
248
|
46
|
50
|
|
|
|
149
|
my $class = $desc->{$CLASS} |
|
249
|
|
|
|
|
|
|
or croak "No class descriptor '$CLASS => ...' in descriptor:\n", |
|
250
|
|
|
|
|
|
|
Dumper($desc); |
|
251
|
|
|
|
|
|
|
|
|
252
|
46
|
|
|
|
|
54
|
my $meta; |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# see other example of getting meta in Moose::Manual::??? |
|
255
|
46
|
|
|
|
|
58
|
eval{ $meta = $class->meta }; |
|
|
46
|
|
|
|
|
186
|
|
|
256
|
46
|
50
|
|
|
|
890
|
croak "Class '$class' not defined: $@" |
|
257
|
|
|
|
|
|
|
if $@; |
|
258
|
|
|
|
|
|
|
|
|
259
|
46
|
|
|
|
|
56
|
my $ncols = @{ $data->[0] }; |
|
|
46
|
|
|
|
|
82
|
|
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# separate out simple (i.e. non-reference) attributes, reference |
|
262
|
|
|
|
|
|
|
# attributes, and sub-objects |
|
263
|
46
|
|
|
|
|
58
|
my ($attrib, $ref_attrib, $sub_desc); |
|
264
|
|
|
|
|
|
|
|
|
265
|
46
|
|
|
|
|
157
|
while ( my ($name, $value) = each %$desc) { |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# check lines which have 'simple' column numbers ( attrib or key => N) |
|
268
|
182
|
100
|
100
|
|
|
744
|
unless (ref($value) or $name eq $CLASS) { |
|
269
|
|
|
|
|
|
|
|
|
270
|
122
|
|
|
|
|
309
|
my $msg = "attribute '$name => $value'"; |
|
271
|
|
|
|
|
|
|
|
|
272
|
122
|
50
|
|
|
|
413
|
croak "$msg must be a (non-negative) integer" |
|
273
|
|
|
|
|
|
|
unless $value =~ /^\d+$/; |
|
274
|
|
|
|
|
|
|
|
|
275
|
122
|
50
|
|
|
|
293
|
croak "$msg greater than # cols in the data ($ncols)" |
|
276
|
|
|
|
|
|
|
if $value > $ncols - 1; |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
# check to see if there are attributes called 'class' or 'key' |
|
280
|
182
|
100
|
100
|
|
|
674
|
if ($name eq $CLASS or $name eq $KEY) { |
|
281
|
58
|
50
|
|
|
|
171
|
croak "The '$class' object has an attribute called '$name'" |
|
282
|
|
|
|
|
|
|
if $meta->find_attribute_by_name($name); |
|
283
|
|
|
|
|
|
|
|
|
284
|
58
|
|
|
|
|
2726
|
next; |
|
285
|
|
|
|
|
|
|
} |
|
286
|
|
|
|
|
|
|
|
|
287
|
124
|
50
|
|
|
|
360
|
croak "Attribute '$name' not in '$class' object" |
|
288
|
|
|
|
|
|
|
unless $meta->find_attribute_by_name($name); |
|
289
|
|
|
|
|
|
|
|
|
290
|
124
|
100
|
|
|
|
4433
|
if ((my $ref = ref($value)) eq 'HASH') { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
291
|
14
|
|
|
|
|
62
|
$sub_desc->{$name} = $value; |
|
292
|
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
} elsif ($ref eq 'ARRAY') { |
|
294
|
|
|
|
|
|
|
# descr entry looks like, e.g.: |
|
295
|
|
|
|
|
|
|
# attrib => [6], |
|
296
|
|
|
|
|
|
|
# |
|
297
|
|
|
|
|
|
|
# ( or attrib => [key => 6, value => 7], in future... ?) |
|
298
|
|
|
|
|
|
|
|
|
299
|
0
|
0
|
|
|
|
0
|
croak "attribute must be of form, e.g.: '$name => [N], " |
|
300
|
|
|
|
|
|
|
. "where N is a single integer'" |
|
301
|
|
|
|
|
|
|
unless @$value == 1; |
|
302
|
|
|
|
|
|
|
|
|
303
|
0
|
|
|
|
|
0
|
my $msg = "attribute '$name => [ " . $value->[0] . " ]'. '" . |
|
304
|
|
|
|
|
|
|
$value->[0] . "'"; |
|
305
|
|
|
|
|
|
|
|
|
306
|
0
|
0
|
|
|
|
0
|
croak "$msg must be a (non-negative) integer" |
|
307
|
|
|
|
|
|
|
unless $value->[0] =~ /^\d+$/; |
|
308
|
|
|
|
|
|
|
|
|
309
|
0
|
0
|
|
|
|
0
|
croak "$msg greater than # cols in the data ($ncols)" |
|
310
|
|
|
|
|
|
|
if $value->[0] > $ncols - 1; |
|
311
|
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
0
|
$ref_attrib->{$name} = $value->[0]; |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
} elsif ($ref) { |
|
315
|
0
|
|
|
|
|
0
|
croak "attribute '$name' can't be a '$ref' reference"; |
|
316
|
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
} else { |
|
318
|
|
|
|
|
|
|
# "simple" attribute |
|
319
|
110
|
|
|
|
|
452
|
$attrib->{$name} = $value; |
|
320
|
|
|
|
|
|
|
} |
|
321
|
|
|
|
|
|
|
} |
|
322
|
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# check ref- and ... |
|
325
|
46
|
50
|
|
|
|
95
|
_check_ref_attribs($class, $ref_attrib) |
|
326
|
|
|
|
|
|
|
if $ref_attrib; |
|
327
|
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# ... non-ref attributes from the descriptor against the Moose object |
|
329
|
46
|
50
|
|
|
|
149
|
_check_non_ref_attribs($class, $attrib) |
|
330
|
|
|
|
|
|
|
if $attrib; |
|
331
|
|
|
|
|
|
|
|
|
332
|
46
|
50
|
33
|
|
|
1829
|
croak "no attributes with column numbers in descriptor:\n", Dumper($desc) |
|
333
|
|
|
|
|
|
|
unless $attrib and %$attrib; |
|
334
|
|
|
|
|
|
|
|
|
335
|
46
|
|
|
|
|
132
|
return ($class, $attrib, $ref_attrib, $sub_desc); |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
######################################## |
|
339
|
|
|
|
|
|
|
# Usage: $sub_obj = _check_subobj($class, $attr_name, $type, $sub_obj); |
|
340
|
|
|
|
|
|
|
# |
|
341
|
|
|
|
|
|
|
# $class is the name of the current class |
|
342
|
|
|
|
|
|
|
# $attr_name is the name of the attribute in the descriptor, e.g. |
|
343
|
|
|
|
|
|
|
# MyObjs => { ... } (used only diagnostic messages) |
|
344
|
|
|
|
|
|
|
# $type is the expected Moose type of the sub-object |
|
345
|
|
|
|
|
|
|
# i.e. 'HashRef[MyObj]', 'ArrayRef[MyObj]', or 'MyObj' |
|
346
|
|
|
|
|
|
|
# $sub_obj_ref Reference to the data (just returned from a recursive call to |
|
347
|
|
|
|
|
|
|
# array_to_moose() ) to be stored in the sub-object, |
|
348
|
|
|
|
|
|
|
# i.e. isa => 'HashRef[MyObj]', isa => 'ArrayRef[MyObj]', |
|
349
|
|
|
|
|
|
|
# or isa => 'MyObj' |
|
350
|
|
|
|
|
|
|
# |
|
351
|
|
|
|
|
|
|
# |
|
352
|
|
|
|
|
|
|
# Checks that the data in $sub_obj_ref agrees with the type of the object to |
|
353
|
|
|
|
|
|
|
# contain it |
|
354
|
|
|
|
|
|
|
# if $type is a ref to an object (isa => 'MyObj'), _check_subobj() converts |
|
355
|
|
|
|
|
|
|
# $sub_obj_ref from an arrayref to sub-object to ref to a subobj |
|
356
|
|
|
|
|
|
|
# (see notes in code below) |
|
357
|
|
|
|
|
|
|
# |
|
358
|
|
|
|
|
|
|
# Throws error is it finds a type mis-match |
|
359
|
|
|
|
|
|
|
######################################## |
|
360
|
|
|
|
|
|
|
sub _check_subobj { |
|
361
|
33
|
|
|
33
|
|
63
|
my ($class, $attr_name, $type, $sub_obj) = @_; |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# for now... |
|
364
|
33
|
50
|
|
|
|
80
|
croak "_check_subobj() should have 4 args" unless @_ == 4; |
|
365
|
|
|
|
|
|
|
|
|
366
|
|
|
|
|
|
|
#my $type = $class->meta->find_attribute_by_name($attr_name)->type_constraint |
|
367
|
|
|
|
|
|
|
# or croak "Moose class '$class' attribute '$attr_name' has no type"; |
|
368
|
|
|
|
|
|
|
|
|
369
|
33
|
100
|
|
|
|
104
|
if ( $type =~ /^HashRef\[([^]]*)\]/ ) { |
|
|
|
100
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
#print "subobj is of type ", ref($sub_obj), "\n"; |
|
372
|
|
|
|
|
|
|
#print "subobj ", Dumper($sub_obj); |
|
373
|
|
|
|
|
|
|
|
|
374
|
9
|
50
|
|
|
|
342
|
croak "Moose attribute '$attr_name' has type '$type' " |
|
375
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
|
376
|
|
|
|
|
|
|
. "of type '" . ref($sub_obj) . "'\n" |
|
377
|
|
|
|
|
|
|
if ref($sub_obj) ne 'HASH'; |
|
378
|
|
|
|
|
|
|
|
|
379
|
|
|
|
|
|
|
#print "\$1 '$1', value: ", ref( ( values %{$sub_obj} )[0] ), "\n"; |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
croak("Moose attribute '$attr_name' has type '$type' " |
|
382
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
|
383
|
0
|
|
|
|
|
0
|
. "of type 'HashRef[" . ref( ( values %{$sub_obj} )[0] ) |
|
384
|
|
|
|
|
|
|
. "]'\n") |
|
385
|
9
|
50
|
|
|
|
14
|
if ref( ( values %{$sub_obj} )[0] ) ne $1; |
|
|
9
|
|
|
|
|
43
|
|
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
} elsif ( $type =~ /^ArrayRef\[([^]]*)\]/ ) { |
|
388
|
|
|
|
|
|
|
|
|
389
|
18
|
50
|
|
|
|
1219
|
croak "Moose attribute '$attr_name' has type '$type' " |
|
390
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
|
391
|
|
|
|
|
|
|
. "of type '" . ref($sub_obj) . "'\n" |
|
392
|
|
|
|
|
|
|
if ref($sub_obj) ne 'ARRAY'; |
|
393
|
|
|
|
|
|
|
|
|
394
|
18
|
50
|
|
|
|
64
|
croak "Moose attribute '$attr_name' has type '$type' " |
|
395
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
|
396
|
|
|
|
|
|
|
. "of type 'ArrayRef[" . ref( $sub_obj->[0] ) . "]'\n" |
|
397
|
|
|
|
|
|
|
if ref( $sub_obj->[0] ) ne $1; |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
} else { |
|
400
|
|
|
|
|
|
|
|
|
401
|
|
|
|
|
|
|
# not isa => 'ArrayRef[MyObj]' or 'HashRef[MyObj]' but isa => 'MyObj', |
|
402
|
|
|
|
|
|
|
# *but* since array_to_moose() can return only a hash- or arrayref of Moose |
|
403
|
|
|
|
|
|
|
# objects, $sub_obj will be an arrayref of Moose objects, which we convert to a |
|
404
|
|
|
|
|
|
|
# ref to an object |
|
405
|
|
|
|
|
|
|
|
|
406
|
6
|
50
|
|
|
|
401
|
croak "Moose attribute '$attr_name' has type '$type' " |
|
407
|
|
|
|
|
|
|
. "but your descriptor generated a '" |
|
408
|
|
|
|
|
|
|
. ref($sub_obj) |
|
409
|
|
|
|
|
|
|
. "' object and not the expected ARRAY" |
|
410
|
|
|
|
|
|
|
unless ref $sub_obj eq 'ARRAY'; |
|
411
|
|
|
|
|
|
|
|
|
412
|
|
|
|
|
|
|
# optionally give error if we got more than one row |
|
413
|
6
|
50
|
66
|
|
|
25
|
croak "Expected a single '$type' object, but got ", |
|
414
|
|
|
|
|
|
|
scalar @$sub_obj, " of them" |
|
415
|
|
|
|
|
|
|
if @$sub_obj != 1 and $throw_multiple_rows; |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
# convert from arrayref of objects to ref to object |
|
418
|
6
|
|
|
|
|
10
|
$sub_obj = $sub_obj->[0]; |
|
419
|
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# print "\$sub_obj type is ", ref($sub_obj), "\n"; |
|
421
|
|
|
|
|
|
|
|
|
422
|
6
|
50
|
|
|
|
20
|
croak "Moose attribute '$attr_name' has type '$type' " |
|
423
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
|
424
|
|
|
|
|
|
|
. "of type '" . ref( $sub_obj ) . "'" |
|
425
|
|
|
|
|
|
|
unless ref( $sub_obj ) eq $type; |
|
426
|
|
|
|
|
|
|
} |
|
427
|
33
|
|
|
|
|
257
|
return $sub_obj; |
|
428
|
|
|
|
|
|
|
} |
|
429
|
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
{ |
|
431
|
|
|
|
|
|
|
|
|
432
|
|
|
|
|
|
|
# The Moose type hierarchy (from Moose::Manual::Types) is: |
|
433
|
|
|
|
|
|
|
# Any |
|
434
|
|
|
|
|
|
|
# Item |
|
435
|
|
|
|
|
|
|
# Bool |
|
436
|
|
|
|
|
|
|
# Maybe[`a] |
|
437
|
|
|
|
|
|
|
# Undef |
|
438
|
|
|
|
|
|
|
# Defined |
|
439
|
|
|
|
|
|
|
# Value |
|
440
|
|
|
|
|
|
|
# Str |
|
441
|
|
|
|
|
|
|
# Num |
|
442
|
|
|
|
|
|
|
# Int |
|
443
|
|
|
|
|
|
|
# ClassName |
|
444
|
|
|
|
|
|
|
# RoleName |
|
445
|
|
|
|
|
|
|
# Ref |
|
446
|
|
|
|
|
|
|
# ScalarRef[`a] |
|
447
|
|
|
|
|
|
|
# ArrayRef[`a] |
|
448
|
|
|
|
|
|
|
# HashRef[`a] |
|
449
|
|
|
|
|
|
|
# CodeRef |
|
450
|
|
|
|
|
|
|
# RegexpRef |
|
451
|
|
|
|
|
|
|
# GlobRef |
|
452
|
|
|
|
|
|
|
# FileHandle |
|
453
|
|
|
|
|
|
|
# Object |
|
454
|
|
|
|
|
|
|
|
|
455
|
|
|
|
|
|
|
# So the test for |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
my %simple_types; |
|
458
|
|
|
|
|
|
|
|
|
459
|
|
|
|
|
|
|
BEGIN |
|
460
|
|
|
|
|
|
|
{ |
|
461
|
21
|
|
|
21
|
|
61
|
%simple_types = map { $_ => 1 } |
|
|
210
|
|
|
|
|
15103
|
|
|
462
|
|
|
|
|
|
|
qw ( Any Item Bool Undef Defined Value Str Num Int __ANON__ ); |
|
463
|
|
|
|
|
|
|
} |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
######################################## |
|
466
|
|
|
|
|
|
|
# Usage: |
|
467
|
|
|
|
|
|
|
# _check_ref_attribs($class, $ref_attribs); |
|
468
|
|
|
|
|
|
|
# Checks that "reference" attributes from the descriptor (e.g., attr => [N]) |
|
469
|
|
|
|
|
|
|
# are ArrayRef[]'s of simple attributes in the Moose object |
|
470
|
|
|
|
|
|
|
# (e.g., isa => ArrayRef['Str']) |
|
471
|
|
|
|
|
|
|
# Throws an exception if check fails |
|
472
|
|
|
|
|
|
|
# |
|
473
|
|
|
|
|
|
|
# where: |
|
474
|
|
|
|
|
|
|
# $class is the current Moose class |
|
475
|
|
|
|
|
|
|
# $ref_attribs an hashref of Moose attributes which are "ref |
|
476
|
|
|
|
|
|
|
# attributes", e.g., " has 'hobbies' (isa => 'ArrayRef[Str]'); " |
|
477
|
|
|
|
|
|
|
# |
|
478
|
|
|
|
|
|
|
######################################## |
|
479
|
|
|
|
|
|
|
sub _check_ref_attribs { |
|
480
|
0
|
|
|
0
|
|
0
|
my ($class, $ref_attribs) = @_; |
|
481
|
|
|
|
|
|
|
|
|
482
|
0
|
0
|
|
|
|
0
|
my $meta = $class->meta |
|
483
|
|
|
|
|
|
|
or croak "No meta for class '$class'?"; |
|
484
|
|
|
|
|
|
|
|
|
485
|
0
|
|
|
|
|
0
|
foreach my $attrib ( keys %{ $ref_attribs } ) { |
|
|
0
|
|
|
|
|
0
|
|
|
486
|
0
|
|
|
|
|
0
|
my $msg = "Moose class '$class' ref attrib '$attrib'"; |
|
487
|
|
|
|
|
|
|
|
|
488
|
0
|
0
|
|
|
|
0
|
my $constraint = $meta->find_attribute_by_name($attrib)->type_constraint |
|
489
|
|
|
|
|
|
|
or croak "$msg has no type constraint"; |
|
490
|
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
#print "_check_ref_attribs(): $attrib $constraint\n"; |
|
492
|
|
|
|
|
|
|
|
|
493
|
0
|
0
|
|
|
|
0
|
if ($constraint =~ /^ArrayRef\[([^]]*)\]/ ) { |
|
494
|
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
croak "$msg has bad type '$constraint' ('$1' is not a simple type)" |
|
496
|
0
|
0
|
|
|
|
0
|
unless $simple_types{$1}; |
|
497
|
|
|
|
|
|
|
|
|
498
|
0
|
|
|
|
|
0
|
return; |
|
499
|
|
|
|
|
|
|
} |
|
500
|
0
|
|
|
|
|
0
|
croak "$msg must be an ArrayRef[`a] and not a '$constraint'"; |
|
501
|
|
|
|
|
|
|
} |
|
502
|
|
|
|
|
|
|
} |
|
503
|
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
|
|
505
|
|
|
|
|
|
|
######################################## |
|
506
|
|
|
|
|
|
|
# Usage: |
|
507
|
|
|
|
|
|
|
# _check_non_ref_attribs($class, $non_ref_attribs); |
|
508
|
|
|
|
|
|
|
# Checks that non-ref attributes from the descriptor (e.g., attr => N) |
|
509
|
|
|
|
|
|
|
# are indeed simple attributes in the Moose object (e.g., isa => 'Str') |
|
510
|
|
|
|
|
|
|
# Throws an exception if check fails |
|
511
|
|
|
|
|
|
|
# |
|
512
|
|
|
|
|
|
|
# |
|
513
|
|
|
|
|
|
|
# where: |
|
514
|
|
|
|
|
|
|
# $class is the current Moose class |
|
515
|
|
|
|
|
|
|
# $non_ref_attribs an hashref of Moose attributes which are |
|
516
|
|
|
|
|
|
|
# non-reference, or "simple" attributes like 'Str', 'Int', etc. |
|
517
|
|
|
|
|
|
|
# The key is the attribute name, the value the type |
|
518
|
|
|
|
|
|
|
# |
|
519
|
|
|
|
|
|
|
######################################## |
|
520
|
|
|
|
|
|
|
sub _check_non_ref_attribs { |
|
521
|
46
|
|
|
46
|
|
77
|
my ($class, $attribs) = @_; |
|
522
|
|
|
|
|
|
|
|
|
523
|
46
|
50
|
|
|
|
134
|
my $meta = $class->meta |
|
524
|
|
|
|
|
|
|
or croak "No meta for class '$class'?"; |
|
525
|
|
|
|
|
|
|
|
|
526
|
46
|
|
|
|
|
618
|
foreach my $attrib ( keys %{ $attribs } ) { |
|
|
46
|
|
|
|
|
138
|
|
|
527
|
110
|
|
|
|
|
2293
|
my $msg = "Moose class '$class', attrib '$attrib'"; |
|
528
|
|
|
|
|
|
|
|
|
529
|
110
|
50
|
|
|
|
286
|
my $constraint = $meta->find_attribute_by_name($attrib)->type_constraint |
|
530
|
|
|
|
|
|
|
or croak "$msg has no type (isa => ...)"; |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
#print "_check_non_ref_attribs(): $attrib '$constraint'\n"; |
|
533
|
|
|
|
|
|
|
|
|
534
|
|
|
|
|
|
|
# kludge for Maybe[`] |
|
535
|
110
|
|
|
|
|
7316
|
$constraint =~ /^Maybe\[([^]]+)\]/; |
|
536
|
110
|
50
|
|
|
|
3690
|
$constraint = $1 if $1; |
|
537
|
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
#print " after: $attrib '$constraint'\n"; |
|
539
|
|
|
|
|
|
|
|
|
540
|
110
|
50
|
|
|
|
255
|
next if $simple_types{$constraint}; |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
$msg = "$msg has type '$constraint', but your descriptor had '$attrib => " |
|
543
|
0
|
|
|
|
|
|
. $attribs->{$attrib} . "'."; |
|
544
|
|
|
|
|
|
|
|
|
545
|
0
|
0
|
|
|
|
|
$msg .= " (Did you forget the '[]' brackets?)" |
|
546
|
|
|
|
|
|
|
if $constraint =~ /^ArrayRef/; |
|
547
|
|
|
|
|
|
|
|
|
548
|
0
|
|
|
|
|
|
croak $msg; |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
} |
|
551
|
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
} # end of local block |
|
553
|
|
|
|
|
|
|
|
|
554
|
|
|
|
|
|
|
|
|
555
|
|
|
|
|
|
|
1; |
|
556
|
|
|
|
|
|
|
|
|
557
|
|
|
|
|
|
|
__END__ |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
=head1 NAME |
|
560
|
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
Array::To::Moose - Build Moose objects from a data array |
|
562
|
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
=head1 VERSION |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
This document describes Array::To::Moose version 0.0.9 |
|
566
|
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
568
|
|
|
|
|
|
|
|
|
569
|
|
|
|
|
|
|
use Array::To::Moose; |
|
570
|
|
|
|
|
|
|
# or |
|
571
|
|
|
|
|
|
|
use Array::To::Moose qw(array_to_moose set_class_ind set_key_ind |
|
572
|
|
|
|
|
|
|
throw_nonunique_keys throw_multiple_rows ); |
|
573
|
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
C<Array::To::Moose> exports function C<array_to_moose()> by default, and |
|
575
|
|
|
|
|
|
|
convenience functions C<set_class_ind()>, C<set_key_ind()>, |
|
576
|
|
|
|
|
|
|
C<throw_nonunique_keys()> and C<throw_multiple_rows()> if requested. |
|
577
|
|
|
|
|
|
|
|
|
578
|
|
|
|
|
|
|
=head2 array_to_moose |
|
579
|
|
|
|
|
|
|
|
|
580
|
|
|
|
|
|
|
C<array_to_moose()> builds Moose objects from suitably-sorted |
|
581
|
|
|
|
|
|
|
2-dimensional arrays of data of the type returned by, e.g., |
|
582
|
|
|
|
|
|
|
L<DBI::selectall_arrayref()|DBI/selectall_arrayref> |
|
583
|
|
|
|
|
|
|
i.e. a reference to an array containing |
|
584
|
|
|
|
|
|
|
references to an array for each row of data fetched. |
|
585
|
|
|
|
|
|
|
|
|
586
|
|
|
|
|
|
|
=head2 Example 1a |
|
587
|
|
|
|
|
|
|
|
|
588
|
|
|
|
|
|
|
package Car; |
|
589
|
|
|
|
|
|
|
use Moose; |
|
590
|
|
|
|
|
|
|
|
|
591
|
|
|
|
|
|
|
has 'make' => (is => 'ro', isa => 'Str'); |
|
592
|
|
|
|
|
|
|
has 'model' => (is => 'ro', isa => 'Str'); |
|
593
|
|
|
|
|
|
|
has 'year' => (is => 'ro', isa => 'Int'); |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
package CarOwner; |
|
596
|
|
|
|
|
|
|
use Moose; |
|
597
|
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
has 'last' => (is => 'ro', isa => 'Str'); |
|
599
|
|
|
|
|
|
|
has 'first' => (is => 'ro', isa => 'Str'); |
|
600
|
|
|
|
|
|
|
has 'Cars' => (is => 'ro', isa => ArrayRef[Car]'); |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
... |
|
603
|
|
|
|
|
|
|
|
|
604
|
|
|
|
|
|
|
# in package main: |
|
605
|
|
|
|
|
|
|
|
|
606
|
|
|
|
|
|
|
use Array::To::Moose; |
|
607
|
|
|
|
|
|
|
|
|
608
|
|
|
|
|
|
|
# In this dataset Alex owns two cars, Jim one, and Alice three |
|
609
|
|
|
|
|
|
|
my $data = [ |
|
610
|
|
|
|
|
|
|
[ qw( Green Alex Ford Focus 2011 ) ], |
|
611
|
|
|
|
|
|
|
[ qw( Green Alex VW Jetta 2009 ) ], |
|
612
|
|
|
|
|
|
|
[ qw( Green Jim Honda Civic 2007 ) ], |
|
613
|
|
|
|
|
|
|
[ qw( Smith Alice Buick Regal 2012 ) ], |
|
614
|
|
|
|
|
|
|
[ qw( Smith Alice Toyota Camry 2008 ) ], |
|
615
|
|
|
|
|
|
|
[ qw( Smith Alice BMW X5 2010 ) ], |
|
616
|
|
|
|
|
|
|
]; |
|
617
|
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
my $CarOwners = array_to_moose( |
|
619
|
|
|
|
|
|
|
data => $data, |
|
620
|
|
|
|
|
|
|
desc => { |
|
621
|
|
|
|
|
|
|
class => 'CarOwner', |
|
622
|
|
|
|
|
|
|
last => 0, |
|
623
|
|
|
|
|
|
|
first => 1, |
|
624
|
|
|
|
|
|
|
Cars => { |
|
625
|
|
|
|
|
|
|
class => 'Car', |
|
626
|
|
|
|
|
|
|
make => 2, |
|
627
|
|
|
|
|
|
|
model => 3, |
|
628
|
|
|
|
|
|
|
year => 4, |
|
629
|
|
|
|
|
|
|
} # Cars |
|
630
|
|
|
|
|
|
|
} # Car Owners |
|
631
|
|
|
|
|
|
|
); |
|
632
|
|
|
|
|
|
|
|
|
633
|
|
|
|
|
|
|
print $CarOwners->[2]->Cars->[1]->model; # prints "Camry" |
|
634
|
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
=head2 Example 1b - Hash(ref) Sub-objects |
|
636
|
|
|
|
|
|
|
|
|
637
|
|
|
|
|
|
|
In the above example, C<array_to_moose()> returns a reference to an |
|
638
|
|
|
|
|
|
|
B<array> of C<CarOwner> objects, C<$CarOwners>. |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
If a B<hash> of C<CarOwner> objects is required, a "C<key =E<gt>>... " entry |
|
641
|
|
|
|
|
|
|
must be added to the descriptor hash. For example, to construct a hash of |
|
642
|
|
|
|
|
|
|
C<CarOwner> objects, whose key is the owner's first name, (unique for |
|
643
|
|
|
|
|
|
|
every person in the example data), the call |
|
644
|
|
|
|
|
|
|
becomes: |
|
645
|
|
|
|
|
|
|
|
|
646
|
|
|
|
|
|
|
my $CarOwnersH = array_to_moose( |
|
647
|
|
|
|
|
|
|
data => $data, |
|
648
|
|
|
|
|
|
|
desc => { |
|
649
|
|
|
|
|
|
|
class => 'CarOwner', |
|
650
|
|
|
|
|
|
|
key => 1, # note key |
|
651
|
|
|
|
|
|
|
last => 0, |
|
652
|
|
|
|
|
|
|
first => 1, |
|
653
|
|
|
|
|
|
|
Cars => { |
|
654
|
|
|
|
|
|
|
class => 'Car', |
|
655
|
|
|
|
|
|
|
make => 2, |
|
656
|
|
|
|
|
|
|
model => 3, |
|
657
|
|
|
|
|
|
|
year => 4, |
|
658
|
|
|
|
|
|
|
} # Cars |
|
659
|
|
|
|
|
|
|
} # Car Owners |
|
660
|
|
|
|
|
|
|
); |
|
661
|
|
|
|
|
|
|
|
|
662
|
|
|
|
|
|
|
print $CarOwnersH->{Alex}->Cars->[0]->make; # prints "Ford" |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
Similarly, to construct the C<Cars> sub-objects as I<hash> sub-objects |
|
665
|
|
|
|
|
|
|
(and not an I<array> as above), define C<CarOwner> as: |
|
666
|
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
package CarOwner; |
|
668
|
|
|
|
|
|
|
use Moose; |
|
669
|
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
has 'last' => (is => 'ro', isa => 'Str' ); |
|
671
|
|
|
|
|
|
|
has 'first' => (is => 'ro', isa => 'Str' ); |
|
672
|
|
|
|
|
|
|
has 'Cars' => (is => 'ro', isa => 'HashRef[Car]'); # Was 'ArrayRef[Car]' |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
and noting that the car C<make> is unique for each person in the C<$data> dataset, we |
|
675
|
|
|
|
|
|
|
construct the reference to an array of objects with the call: |
|
676
|
|
|
|
|
|
|
|
|
677
|
|
|
|
|
|
|
$CarOwners = array_to_moose( |
|
678
|
|
|
|
|
|
|
data => $data, |
|
679
|
|
|
|
|
|
|
desc => { |
|
680
|
|
|
|
|
|
|
class => 'CarOwner', |
|
681
|
|
|
|
|
|
|
last => 0, |
|
682
|
|
|
|
|
|
|
first => 1, |
|
683
|
|
|
|
|
|
|
Cars => { |
|
684
|
|
|
|
|
|
|
class => 'Car', |
|
685
|
|
|
|
|
|
|
key => 2, # note key |
|
686
|
|
|
|
|
|
|
model => 3, |
|
687
|
|
|
|
|
|
|
year => 4, |
|
688
|
|
|
|
|
|
|
} # Cars |
|
689
|
|
|
|
|
|
|
} # Car Owners |
|
690
|
|
|
|
|
|
|
); |
|
691
|
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
print $CarOwners->[2]->Cars->{BMW}->model; # prints 'X5' |
|
693
|
|
|
|
|
|
|
|
|
694
|
|
|
|
|
|
|
=head2 Example 1c - "Simple" Reference Attributes |
|
695
|
|
|
|
|
|
|
|
|
696
|
|
|
|
|
|
|
If, instead of the car owner object containing an ArrayRef or HashRef of |
|
697
|
|
|
|
|
|
|
C<Car> sub-objects, it contains, say, a ArrayRef of strings representing the |
|
698
|
|
|
|
|
|
|
names of the car makers: |
|
699
|
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
package SimpleCarOwner; |
|
701
|
|
|
|
|
|
|
use Moose; |
|
702
|
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
has 'last' => (is => 'ro', isa => 'Str' ); |
|
704
|
|
|
|
|
|
|
has 'first' => (is => 'ro', isa => 'Str' ); |
|
705
|
|
|
|
|
|
|
has 'CarMakers' => (is => 'ro', isa => 'ArrayRef[Str]'); |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
Using the same dataset from Example 1a, we construct an arrayref |
|
708
|
|
|
|
|
|
|
C<SimpleCarOwner> objects as: |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
$SimpleCarOwners = array_to_moose( |
|
711
|
|
|
|
|
|
|
data => $data, |
|
712
|
|
|
|
|
|
|
desc => { |
|
713
|
|
|
|
|
|
|
class => 'SimpleCarOwner', |
|
714
|
|
|
|
|
|
|
last => 0, |
|
715
|
|
|
|
|
|
|
first => 1, |
|
716
|
|
|
|
|
|
|
CarMakers => [2], # Note the '[...]' brackets |
|
717
|
|
|
|
|
|
|
} |
|
718
|
|
|
|
|
|
|
); |
|
719
|
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
print $SimpleCarOwners->[2]->[1]; # prints 'Toyota' |
|
721
|
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
I.e., when the object attribute is an I<ArrayRef> of one of the Moose "simple" types, |
|
723
|
|
|
|
|
|
|
e.g. C<'Str'>, C<'Num'>, C<'Bool'>, |
|
724
|
|
|
|
|
|
|
etc (See L<Moose::Manual::Types|THE TYPES>), then the column number should |
|
725
|
|
|
|
|
|
|
appear in square brackets ('C<CarMakers =E<gt> [2]>' above) to differentiate them from the bare |
|
726
|
|
|
|
|
|
|
types (C<last =E<gt> 0,> and C<first =E<gt> 1,> above). |
|
727
|
|
|
|
|
|
|
|
|
728
|
|
|
|
|
|
|
Note that Array::To::Moose doesn't (yet) handle the case of hashrefs of |
|
729
|
|
|
|
|
|
|
"simple" types, e.g., C<( isa =E<gt> "HashRef[Str]" )> |
|
730
|
|
|
|
|
|
|
|
|
731
|
|
|
|
|
|
|
=head2 Example 2 - Use with DBI |
|
732
|
|
|
|
|
|
|
|
|
733
|
|
|
|
|
|
|
The main rationale for writing C<Array::To::Moose> is to make it easy to build |
|
734
|
|
|
|
|
|
|
Moose objects from data extracted from relational databases, |
|
735
|
|
|
|
|
|
|
especially when the database query |
|
736
|
|
|
|
|
|
|
involves multiple tables with one-to-many relationships to each other. |
|
737
|
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
As an example, consider a database which models patients making visits |
|
739
|
|
|
|
|
|
|
to a clinic on multiple occasions, and on each visit, having a doctor |
|
740
|
|
|
|
|
|
|
run some tests and diagnose the patient's complaint. In this model, the |
|
741
|
|
|
|
|
|
|
database I<Patient> table would have a one-to-many relationship with the |
|
742
|
|
|
|
|
|
|
I<Visit> table, which in turn would have a one-to-many relationship with |
|
743
|
|
|
|
|
|
|
the I<Test> table |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
The corresponding Moose model has nested Moose objects which reflects those |
|
746
|
|
|
|
|
|
|
one-to-many relationships, i.e., |
|
747
|
|
|
|
|
|
|
multiple Visit objects per Patient object and multiple Test objects |
|
748
|
|
|
|
|
|
|
per Visit object, declared as: |
|
749
|
|
|
|
|
|
|
|
|
750
|
|
|
|
|
|
|
package Test; |
|
751
|
|
|
|
|
|
|
use Moose; |
|
752
|
|
|
|
|
|
|
has 'name' => (is => 'rw', isa => 'Str'); |
|
753
|
|
|
|
|
|
|
has 'result' => (is => 'rw', isa => 'Str'); |
|
754
|
|
|
|
|
|
|
|
|
755
|
|
|
|
|
|
|
package Visit; |
|
756
|
|
|
|
|
|
|
use Moose; |
|
757
|
|
|
|
|
|
|
has 'date' => (is => 'rw', isa => 'Str' ); |
|
758
|
|
|
|
|
|
|
has 'md' => (is => 'rw', isa => 'Str' ); |
|
759
|
|
|
|
|
|
|
has 'diagnosis' => (is => 'rw', isa => 'Str' ); |
|
760
|
|
|
|
|
|
|
has 'Tests' => (is => 'rw', isa => 'HashRef[Test]' ); |
|
761
|
|
|
|
|
|
|
|
|
762
|
|
|
|
|
|
|
package Patient; |
|
763
|
|
|
|
|
|
|
use Moose; |
|
764
|
|
|
|
|
|
|
has 'last' => (is => 'rw', isa => 'Str' ); |
|
765
|
|
|
|
|
|
|
has 'first' => (is => 'rw', isa => 'Str' ); |
|
766
|
|
|
|
|
|
|
has 'Visits' => (is => 'rw', isa => 'ArrayRef[Visit]' ); |
|
767
|
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
In the main program: |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
use DBI; |
|
771
|
|
|
|
|
|
|
use Array::To::Moose; |
|
772
|
|
|
|
|
|
|
|
|
773
|
|
|
|
|
|
|
... |
|
774
|
|
|
|
|
|
|
|
|
775
|
|
|
|
|
|
|
my $sql = q{ |
|
776
|
|
|
|
|
|
|
SELECT |
|
777
|
|
|
|
|
|
|
P.Last, P.First |
|
778
|
|
|
|
|
|
|
,V.Date, V.Doctor, V.Diagnosis |
|
779
|
|
|
|
|
|
|
,T.Name, T.Result |
|
780
|
|
|
|
|
|
|
FROM |
|
781
|
|
|
|
|
|
|
Patient P |
|
782
|
|
|
|
|
|
|
,Visit V |
|
783
|
|
|
|
|
|
|
,Test T |
|
784
|
|
|
|
|
|
|
WHERE |
|
785
|
|
|
|
|
|
|
-- join clauses |
|
786
|
|
|
|
|
|
|
P.Patient_key = V.Patient_key |
|
787
|
|
|
|
|
|
|
AND V.Visit_key = T.Visit_key |
|
788
|
|
|
|
|
|
|
... |
|
789
|
|
|
|
|
|
|
ORDER BY |
|
790
|
|
|
|
|
|
|
P.Last, P.First, V.Date |
|
791
|
|
|
|
|
|
|
}; |
|
792
|
|
|
|
|
|
|
|
|
793
|
|
|
|
|
|
|
my $dbh = DBI->connect(...); |
|
794
|
|
|
|
|
|
|
|
|
795
|
|
|
|
|
|
|
my $data = $dbh->selectall_arrayref($sql); |
|
796
|
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
# rows of @$data contain: |
|
798
|
|
|
|
|
|
|
# Last, First, Date, Doctor, Diagnosis, Name, Result |
|
799
|
|
|
|
|
|
|
# at positions: [0] [1] [2] [3] [4] [5] [6] |
|
800
|
|
|
|
|
|
|
|
|
801
|
|
|
|
|
|
|
my $patients = array_to_moose( |
|
802
|
|
|
|
|
|
|
data => $data, |
|
803
|
|
|
|
|
|
|
desc => { |
|
804
|
|
|
|
|
|
|
class => 'Patient', |
|
805
|
|
|
|
|
|
|
last => 0, |
|
806
|
|
|
|
|
|
|
first => 1, |
|
807
|
|
|
|
|
|
|
Visits => { |
|
808
|
|
|
|
|
|
|
class => 'Visit', |
|
809
|
|
|
|
|
|
|
date => 2, |
|
810
|
|
|
|
|
|
|
md => 3, |
|
811
|
|
|
|
|
|
|
diagnosis => 4, |
|
812
|
|
|
|
|
|
|
Tests => { |
|
813
|
|
|
|
|
|
|
class => 'Test', |
|
814
|
|
|
|
|
|
|
key => 5, |
|
815
|
|
|
|
|
|
|
name => 5, |
|
816
|
|
|
|
|
|
|
result => 6, |
|
817
|
|
|
|
|
|
|
} # tests |
|
818
|
|
|
|
|
|
|
} # visits |
|
819
|
|
|
|
|
|
|
} # patients |
|
820
|
|
|
|
|
|
|
); |
|
821
|
|
|
|
|
|
|
|
|
822
|
|
|
|
|
|
|
print $patients->[2]->Visits->[0]->Tests->{BP}->result; # prints '120/80' |
|
823
|
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
Note: We used the Test C<name> as the key for the Visit 'C<Tests>', as the |
|
825
|
|
|
|
|
|
|
tests have unique names within any one Visit. |
|
826
|
|
|
|
|
|
|
(See t/5.t) |
|
827
|
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
829
|
|
|
|
|
|
|
|
|
830
|
|
|
|
|
|
|
As shown in the above examples, the general usage is: |
|
831
|
|
|
|
|
|
|
|
|
832
|
|
|
|
|
|
|
package MyClass; |
|
833
|
|
|
|
|
|
|
use Moose; |
|
834
|
|
|
|
|
|
|
(define Moose object(s)) |
|
835
|
|
|
|
|
|
|
... |
|
836
|
|
|
|
|
|
|
use Array::To::Moose; |
|
837
|
|
|
|
|
|
|
... |
|
838
|
|
|
|
|
|
|
my $data_ref = selectall_arrayref($sql); # for example |
|
839
|
|
|
|
|
|
|
|
|
840
|
|
|
|
|
|
|
my $object_ref = array_to_moose( |
|
841
|
|
|
|
|
|
|
data => $data_ref |
|
842
|
|
|
|
|
|
|
desc => { |
|
843
|
|
|
|
|
|
|
class => 'MyClass', |
|
844
|
|
|
|
|
|
|
key => K, # only for HashRefs |
|
845
|
|
|
|
|
|
|
attrib_1 => N1, |
|
846
|
|
|
|
|
|
|
attrib_2 => N2, |
|
847
|
|
|
|
|
|
|
... |
|
848
|
|
|
|
|
|
|
attrib_m => [ M ], |
|
849
|
|
|
|
|
|
|
... |
|
850
|
|
|
|
|
|
|
SubObject => { |
|
851
|
|
|
|
|
|
|
class => 'MySubClass', |
|
852
|
|
|
|
|
|
|
... |
|
853
|
|
|
|
|
|
|
} |
|
854
|
|
|
|
|
|
|
} |
|
855
|
|
|
|
|
|
|
); |
|
856
|
|
|
|
|
|
|
|
|
857
|
|
|
|
|
|
|
Where: |
|
858
|
|
|
|
|
|
|
|
|
859
|
|
|
|
|
|
|
C<array_to_moose()> returns an array- or hash reference of C<MyClass> |
|
860
|
|
|
|
|
|
|
Moose objects. |
|
861
|
|
|
|
|
|
|
All Moose classes (C<MyClass>, C<MySubClass>, etc) must |
|
862
|
|
|
|
|
|
|
already have been defined by the user. |
|
863
|
|
|
|
|
|
|
|
|
864
|
|
|
|
|
|
|
C<$data_ref> is a reference to an array containing references to arrays of |
|
865
|
|
|
|
|
|
|
scalars of the kind returned by, e.g., |
|
866
|
|
|
|
|
|
|
L<DBI::selectall_arrayref()|DBI/selectall_arrayref> |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
C<desc> (descriptor) is a reference to a hash which contains several types |
|
869
|
|
|
|
|
|
|
of data: |
|
870
|
|
|
|
|
|
|
|
|
871
|
|
|
|
|
|
|
C<class =E<gt>> 'MyObj' is I<required> and defines the Moose class or |
|
872
|
|
|
|
|
|
|
package which will contain the data. The user should have defined this class |
|
873
|
|
|
|
|
|
|
already. |
|
874
|
|
|
|
|
|
|
|
|
875
|
|
|
|
|
|
|
C<key =E<gt> N > is required |
|
876
|
|
|
|
|
|
|
if the Moose object being constructed is to be a hashref, either at |
|
877
|
|
|
|
|
|
|
the top-level Moose object returned from C<array_to_moose()> or as a |
|
878
|
|
|
|
|
|
|
"C<isa =E<gt> 'HashRef[...]'>" sub-object. |
|
879
|
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
C<attrib =E<gt> N > where C<attrib> is the name of a Moose attribute |
|
881
|
|
|
|
|
|
|
("C<has 'attrib' =E<gt>> ...") |
|
882
|
|
|
|
|
|
|
|
|
883
|
|
|
|
|
|
|
C<attrib =E<gt> [ N ] > where C<attrib> is the name of a Moose "simple" sub-attribute |
|
884
|
|
|
|
|
|
|
("C<has =E<gt> 'attrib' ( isa =E<gt> 'ArrayRef[Type]' ...)> "), where C<Type> |
|
885
|
|
|
|
|
|
|
is a "simple" Moose type, e.g., C<'Str', 'Int'>, etc. |
|
886
|
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
In the above cases, C<N> is a positive integer containing the |
|
888
|
|
|
|
|
|
|
the corresponding zero-indexed |
|
889
|
|
|
|
|
|
|
column number in the data array where that attribute's data is to be found. |
|
890
|
|
|
|
|
|
|
|
|
891
|
|
|
|
|
|
|
=head2 Sub-Objects |
|
892
|
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
C<array_to_moose()> can handle three types of Moose sub-objects, i.e.: |
|
894
|
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
an array of sub-objects: |
|
896
|
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
has => 'Sub_Obj' ( isa => 'ArrayRef[MyObj]' ); |
|
898
|
|
|
|
|
|
|
|
|
899
|
|
|
|
|
|
|
a hash of sub-objects: |
|
900
|
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
has => 'Sub_Obj' ( isa => 'HashRef[MyObj]' ); |
|
902
|
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
or a single sub-object: |
|
904
|
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
has => 'Sub_Obj' ( isa => 'MyObj' ); |
|
906
|
|
|
|
|
|
|
|
|
907
|
|
|
|
|
|
|
the descriptor entry for C<Sub_Obj> in each of these cases is (almost) the same: |
|
908
|
|
|
|
|
|
|
|
|
909
|
|
|
|
|
|
|
desc => { |
|
910
|
|
|
|
|
|
|
class => ... |
|
911
|
|
|
|
|
|
|
... |
|
912
|
|
|
|
|
|
|
Sub_Obj => { |
|
913
|
|
|
|
|
|
|
class => 'MyObj', |
|
914
|
|
|
|
|
|
|
key => <keycol> # HashRef['] only |
|
915
|
|
|
|
|
|
|
attrib_a => <N>, |
|
916
|
|
|
|
|
|
|
... |
|
917
|
|
|
|
|
|
|
} # end SubObj |
|
918
|
|
|
|
|
|
|
... |
|
919
|
|
|
|
|
|
|
} # end desc |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
(A C<HashRef[']> sub-object will also I<require> a |
|
922
|
|
|
|
|
|
|
C<key =E<gt> N> entry in the descriptor). |
|
923
|
|
|
|
|
|
|
|
|
924
|
|
|
|
|
|
|
In addition, C<array_to_moose()> can also handle C<ArrayRef>s of "simple" |
|
925
|
|
|
|
|
|
|
types: |
|
926
|
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
has => 'Sub_Obj' ( isa => 'ArrayRef[Type]' ); |
|
928
|
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
where C<Type> is a "simple" Moose type, e.g., C<'Str', 'Int, 'Bool'>, etc. |
|
930
|
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
=head2 Ordering the data |
|
932
|
|
|
|
|
|
|
|
|
933
|
|
|
|
|
|
|
C<array_to_moose()> does not sort the input data array, and does all |
|
934
|
|
|
|
|
|
|
processing in a single pass through the data. This means that the data in the |
|
935
|
|
|
|
|
|
|
array must be sorted properly for the algorithm to work. |
|
936
|
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
For example, in the previous Patient/Visit/Test example, in which there are |
|
938
|
|
|
|
|
|
|
many I<Test>s per I<Visit> and many I<Visit>s per I<Patient>, the data in the |
|
939
|
|
|
|
|
|
|
I<Test> column(s) must change the fastest, the I<Visit> data slower, and the |
|
940
|
|
|
|
|
|
|
I<Patient> data the slowest: |
|
941
|
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
Patient Visit Test |
|
943
|
|
|
|
|
|
|
------ ----- ---- |
|
944
|
|
|
|
|
|
|
P1 V1 T1 |
|
945
|
|
|
|
|
|
|
P1 V1 T2 |
|
946
|
|
|
|
|
|
|
P1 V1 T3 |
|
947
|
|
|
|
|
|
|
P1 V2 T4 |
|
948
|
|
|
|
|
|
|
P1 V2 T5 |
|
949
|
|
|
|
|
|
|
P2 V3 T6 |
|
950
|
|
|
|
|
|
|
P2 V3 T7 |
|
951
|
|
|
|
|
|
|
P2 V4 T8 |
|
952
|
|
|
|
|
|
|
|
|
953
|
|
|
|
|
|
|
In SQL this would be accomplished by a C<SORT BY> clause, e.g.: |
|
954
|
|
|
|
|
|
|
|
|
955
|
|
|
|
|
|
|
SORT BY Patient.Key, Visit.Key, Test.Key |
|
956
|
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
=head2 throw_nonunique_keys () |
|
958
|
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
By default, C<array_to_moose()> does not check the uniqueness of hash key |
|
960
|
|
|
|
|
|
|
values within the data. If the key values in the data are not unique, |
|
961
|
|
|
|
|
|
|
existing hash entries will get overwritten, and |
|
962
|
|
|
|
|
|
|
the sub-object will contain the value from the last data row which |
|
963
|
|
|
|
|
|
|
contained that key value. For example: |
|
964
|
|
|
|
|
|
|
|
|
965
|
|
|
|
|
|
|
package Employer; |
|
966
|
|
|
|
|
|
|
use Moose; |
|
967
|
|
|
|
|
|
|
has 'year' => (is => 'rw', isa => 'Str'); |
|
968
|
|
|
|
|
|
|
has 'name' => (is => 'rw', isa => 'Str'); |
|
969
|
|
|
|
|
|
|
|
|
970
|
|
|
|
|
|
|
package Person; |
|
971
|
|
|
|
|
|
|
use Moose; |
|
972
|
|
|
|
|
|
|
has 'name' => (is => 'rw', isa => 'Str' ); |
|
973
|
|
|
|
|
|
|
has 'Employers' => (is => 'rw', isa => 'HashRef[Employer]'); |
|
974
|
|
|
|
|
|
|
|
|
975
|
|
|
|
|
|
|
... |
|
976
|
|
|
|
|
|
|
|
|
977
|
|
|
|
|
|
|
my $data = [ |
|
978
|
|
|
|
|
|
|
[ 'Anne Miller', '2005', 'Acme Corp' ], |
|
979
|
|
|
|
|
|
|
[ 'Anne Miller', '2006', 'Acme Corp' ], |
|
980
|
|
|
|
|
|
|
[ 'Anne Miller', '2007', 'Widgets, Inc' ], |
|
981
|
|
|
|
|
|
|
... |
|
982
|
|
|
|
|
|
|
]; |
|
983
|
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
The call: |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
my $obj = array_to_moose( |
|
987
|
|
|
|
|
|
|
data => $data, |
|
988
|
|
|
|
|
|
|
desc => { |
|
989
|
|
|
|
|
|
|
class => 'Person', |
|
990
|
|
|
|
|
|
|
name => 0, |
|
991
|
|
|
|
|
|
|
Employers => { |
|
992
|
|
|
|
|
|
|
class => 'Employer', |
|
993
|
|
|
|
|
|
|
key => 2, # using employer name as key |
|
994
|
|
|
|
|
|
|
year => 1, |
|
995
|
|
|
|
|
|
|
} # Employer |
|
996
|
|
|
|
|
|
|
} # Person |
|
997
|
|
|
|
|
|
|
); |
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
Because the employer was C<'Acme Corp'> in years 2005 & 2006, |
|
1000
|
|
|
|
|
|
|
C<array_to_moose> |
|
1001
|
|
|
|
|
|
|
will silently overwrite the 2005 Employer object with the data for the |
|
1002
|
|
|
|
|
|
|
2006 Employer object: |
|
1003
|
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
print $obj->[0]->Employers->{'Acme Corp'}->year, "\n"; # prints '2006' |
|
1005
|
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
Calling C<throw_uniq_keys()> (either with no argument, or with a non-zero |
|
1007
|
|
|
|
|
|
|
argument) enables reporting of non-unique keys. In the above example, |
|
1008
|
|
|
|
|
|
|
C<array_to_moose()> would exit with warning: |
|
1009
|
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
Non-unique key 'Acme Corp' in 'Employer' class ... |
|
1011
|
|
|
|
|
|
|
|
|
1012
|
|
|
|
|
|
|
Calling C<throw_uniq_keys(0)>, i.e. with an argument of zero will disable |
|
1013
|
|
|
|
|
|
|
subsequent reporting of non-unique keys. |
|
1014
|
|
|
|
|
|
|
(See t/8c.t) |
|
1015
|
|
|
|
|
|
|
|
|
1016
|
|
|
|
|
|
|
=head2 throw_multiple_rows () |
|
1017
|
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
For single-occurence sub-objects (i.e. C<( isa =E<gt> 'MyObj' )>), |
|
1019
|
|
|
|
|
|
|
if the data contains more than one row of data for the sub-object, |
|
1020
|
|
|
|
|
|
|
only the first row will be used to construct the single sub-object and |
|
1021
|
|
|
|
|
|
|
C<array_to_moose()> will not report the fact. E.g.: |
|
1022
|
|
|
|
|
|
|
|
|
1023
|
|
|
|
|
|
|
package Salary; |
|
1024
|
|
|
|
|
|
|
use Moose; |
|
1025
|
|
|
|
|
|
|
has 'year' => (is => 'rw', isa => 'Str'); |
|
1026
|
|
|
|
|
|
|
has 'amount' => (is => 'rw', isa => 'Int'); |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
package Person; |
|
1029
|
|
|
|
|
|
|
use Moose; |
|
1030
|
|
|
|
|
|
|
has 'name' => (is => 'rw', isa => 'Str' ); |
|
1031
|
|
|
|
|
|
|
has 'Salary' => (is => 'rw', isa => 'Salary'); # a single object |
|
1032
|
|
|
|
|
|
|
|
|
1033
|
|
|
|
|
|
|
... |
|
1034
|
|
|
|
|
|
|
|
|
1035
|
|
|
|
|
|
|
my $data = [ |
|
1036
|
|
|
|
|
|
|
[ 'John Smith', '2005', 23_350 ], |
|
1037
|
|
|
|
|
|
|
[ 'John Smith', '2006', 24_000 ], |
|
1038
|
|
|
|
|
|
|
[ 'John Smith', '2007', 26_830 ], |
|
1039
|
|
|
|
|
|
|
... |
|
1040
|
|
|
|
|
|
|
]; |
|
1041
|
|
|
|
|
|
|
|
|
1042
|
|
|
|
|
|
|
The call: |
|
1043
|
|
|
|
|
|
|
|
|
1044
|
|
|
|
|
|
|
my $obj = array_to_moose( |
|
1045
|
|
|
|
|
|
|
data => $data, |
|
1046
|
|
|
|
|
|
|
desc => { |
|
1047
|
|
|
|
|
|
|
class => 'Person' |
|
1048
|
|
|
|
|
|
|
name => 0, |
|
1049
|
|
|
|
|
|
|
Salary => { |
|
1050
|
|
|
|
|
|
|
class => 'Salary', |
|
1051
|
|
|
|
|
|
|
year => 1, |
|
1052
|
|
|
|
|
|
|
amount => 2 |
|
1053
|
|
|
|
|
|
|
} # Salary |
|
1054
|
|
|
|
|
|
|
} # Person |
|
1055
|
|
|
|
|
|
|
); |
|
1056
|
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
would silently assign to C<Salary>, the first row of the three Salary |
|
1058
|
|
|
|
|
|
|
data rows, i.e. for year 2005: |
|
1059
|
|
|
|
|
|
|
|
|
1060
|
|
|
|
|
|
|
print $object->[0]->Salary->year, "\n"; # prints '2005' |
|
1061
|
|
|
|
|
|
|
|
|
1062
|
|
|
|
|
|
|
Calling C<throw_multiple_rows()> |
|
1063
|
|
|
|
|
|
|
(either with no argument, or with a non-zero argument) |
|
1064
|
|
|
|
|
|
|
enables reporting of this situation. In the |
|
1065
|
|
|
|
|
|
|
above example, C<array_to_moose()> will exit with error: |
|
1066
|
|
|
|
|
|
|
|
|
1067
|
|
|
|
|
|
|
Expected a single 'Salary' object, but got 3 of them ... |
|
1068
|
|
|
|
|
|
|
|
|
1069
|
|
|
|
|
|
|
Calling C<throw_multiple_rows(0)>, i.e. with an argument of zero will disable |
|
1070
|
|
|
|
|
|
|
subsequent reporting of this error. |
|
1071
|
|
|
|
|
|
|
(See t/8d.t) |
|
1072
|
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
=head2 set_class_ind (), set_key_ind () |
|
1074
|
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
Problems arise if the Moose objects being constructed contain attributes |
|
1076
|
|
|
|
|
|
|
called I<class> or I<key>, causing ambiguities in the descriptor. (Does |
|
1077
|
|
|
|
|
|
|
C<key =E<gt> 5> mean the I<attribute> C<key> or the I<hash key> C<key> is in |
|
1078
|
|
|
|
|
|
|
the 5th column?) |
|
1079
|
|
|
|
|
|
|
|
|
1080
|
|
|
|
|
|
|
In these cases, C<set_class_ind()> and |
|
1081
|
|
|
|
|
|
|
C<set_key_ind()> can be used to change the keywords for C<class |
|
1082
|
|
|
|
|
|
|
=E<gt> ...> and C<key =E<gt> ...> descriptor entries. |
|
1083
|
|
|
|
|
|
|
|
|
1084
|
|
|
|
|
|
|
For example: |
|
1085
|
|
|
|
|
|
|
|
|
1086
|
|
|
|
|
|
|
package Letter; |
|
1087
|
|
|
|
|
|
|
use Moose; |
|
1088
|
|
|
|
|
|
|
|
|
1089
|
|
|
|
|
|
|
has 'address' => ( is => 'ro', isa => 'Str' ); |
|
1090
|
|
|
|
|
|
|
has 'class' => ( is => 'ro', isa => 'PostalClass' ); |
|
1091
|
|
|
|
|
|
|
... |
|
1092
|
|
|
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
set_key_ind('package'); # use "package =>" in place of "class =>" |
|
1094
|
|
|
|
|
|
|
|
|
1095
|
|
|
|
|
|
|
my $letters = array_to_moose( |
|
1096
|
|
|
|
|
|
|
data => $data, |
|
1097
|
|
|
|
|
|
|
desc => { |
|
1098
|
|
|
|
|
|
|
package => 'Letter', # the Moose class |
|
1099
|
|
|
|
|
|
|
address => 0, |
|
1100
|
|
|
|
|
|
|
class => 1, # the attribute 'class' |
|
1101
|
|
|
|
|
|
|
... |
|
1102
|
|
|
|
|
|
|
} |
|
1103
|
|
|
|
|
|
|
); |
|
1104
|
|
|
|
|
|
|
|
|
1105
|
|
|
|
|
|
|
|
|
1106
|
|
|
|
|
|
|
=head2 Read-only Attributes |
|
1107
|
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
One of the recommendations of L<Moose::Manual::BestPractices> |
|
1109
|
|
|
|
|
|
|
is to make attributes read-only (C<isa =E<gt> 'ro'>) wherever |
|
1110
|
|
|
|
|
|
|
possible. C<Array::To::Moose> supports this by evaluating all the |
|
1111
|
|
|
|
|
|
|
attributes for a given object given in the descriptor, then including |
|
1112
|
|
|
|
|
|
|
them all in the call to C<new(...)> when constructing the object. |
|
1113
|
|
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
For Moose objects with attributes which are |
|
1115
|
|
|
|
|
|
|
sub-objects, i.e. references to a Moose object, or references to an array or hash of |
|
1116
|
|
|
|
|
|
|
Moose objects, it means that the sub-objects must be evaluated before the |
|
1117
|
|
|
|
|
|
|
C<new()> call. The effect of this for multi-leveled Moose objects is that |
|
1118
|
|
|
|
|
|
|
object evaluations are carried out depth-first. |
|
1119
|
|
|
|
|
|
|
|
|
1120
|
|
|
|
|
|
|
=head2 Treatment of C<NULL>s |
|
1121
|
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
C<array_to_moose()> uses |
|
1123
|
|
|
|
|
|
|
L<Array::GroupBy::igroup_by|Array::GroupBy.pm/DESCRIPTION> |
|
1124
|
|
|
|
|
|
|
to compare the rows in |
|
1125
|
|
|
|
|
|
|
the data given in C<data =E<gt> ...>, using function |
|
1126
|
|
|
|
|
|
|
L<Array::GroupBy::str_row_equal()|Array::GroupBy.pm/Routines_str_row_equal()_and_num_row_equal()> |
|
1127
|
|
|
|
|
|
|
which compares the data as I<strings>. |
|
1128
|
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
If the data contains C<undef> values, typically returned from |
|
1130
|
|
|
|
|
|
|
database SQL queries in which L<DBI> maps NULL values to C<undef>, when |
|
1131
|
|
|
|
|
|
|
C<str_row_equal()> encounters C<undef> elements in I<corresponding> column |
|
1132
|
|
|
|
|
|
|
positions, it will consider the elements C<equal>. When I<corresponding> |
|
1133
|
|
|
|
|
|
|
column elements are defined and C<undef> respectively, the elements are |
|
1134
|
|
|
|
|
|
|
considered C<unequal>. |
|
1135
|
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
This truth table demonstrates the various combinations: |
|
1137
|
|
|
|
|
|
|
|
|
1138
|
|
|
|
|
|
|
-------+------------+--------------+--------------+-------------- |
|
1139
|
|
|
|
|
|
|
row 1 | ('a', 'b') | ('a', undef) | ('a', undef) | ('a', 'b' ) |
|
1140
|
|
|
|
|
|
|
row 2 | ('a', 'b') | ('a', undef) | ('a', 'b' ) | ('a', undef) |
|
1141
|
|
|
|
|
|
|
-------+------------+--------------+--------------+-------------- |
|
1142
|
|
|
|
|
|
|
equal? | yes | yes | no | no |
|
1143
|
|
|
|
|
|
|
|
|
1144
|
|
|
|
|
|
|
=head1 EXPORT |
|
1145
|
|
|
|
|
|
|
|
|
1146
|
|
|
|
|
|
|
C<array_to_moose> by default; C<throw_nonunique_keys>, C<throw_multiple_rows>, |
|
1147
|
|
|
|
|
|
|
C<set_class_ind> and C<set_key_ind> if requested. |
|
1148
|
|
|
|
|
|
|
|
|
1149
|
|
|
|
|
|
|
=head1 DIAGNOSTICS |
|
1150
|
|
|
|
|
|
|
|
|
1151
|
|
|
|
|
|
|
Errors in the call of C<array-to-moose()> will be caught by |
|
1152
|
|
|
|
|
|
|
L<Params::Validate::Array>, q.v. |
|
1153
|
|
|
|
|
|
|
|
|
1154
|
|
|
|
|
|
|
<array-to-moose> does a lot of error checking, and is probably annoyingly |
|
1155
|
|
|
|
|
|
|
chatty. Most of the errors generated are, of course, self-explanatory :-) |
|
1156
|
|
|
|
|
|
|
|
|
1157
|
|
|
|
|
|
|
=head1 DEPENDENCIES |
|
1158
|
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
Carp |
|
1160
|
|
|
|
|
|
|
Params::Validate::Array |
|
1161
|
|
|
|
|
|
|
Array::GroupBy |
|
1162
|
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
1164
|
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
L<DBI>, L<Moose>, L<Array::GroupBy> |
|
1166
|
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
=head1 BUGS |
|
1168
|
|
|
|
|
|
|
|
|
1169
|
|
|
|
|
|
|
The handling of Moose type constraints is primitive. |
|
1170
|
|
|
|
|
|
|
|
|
1171
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1172
|
|
|
|
|
|
|
|
|
1173
|
|
|
|
|
|
|
Sam Brain <samb@stanford.edu> |
|
1174
|
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
1176
|
|
|
|
|
|
|
|
|
1177
|
|
|
|
|
|
|
Copyright (c) Stanford University. June 6th, 2010. |
|
1178
|
|
|
|
|
|
|
All rights reserved. |
|
1179
|
|
|
|
|
|
|
Author: Sam Brain <samb@stanford.edu> |
|
1180
|
|
|
|
|
|
|
|
|
1181
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
|
1182
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.8.8 or, |
|
1183
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |
|
1184
|
|
|
|
|
|
|
|
|
1185
|
|
|
|
|
|
|
=cut |
|
1186
|
|
|
|
|
|
|
|
|
1187
|
|
|
|
|
|
|
# TODO |
|
1188
|
|
|
|
|
|
|
# |
|
1189
|
|
|
|
|
|
|
# test for non-square data array? |
|
1190
|
|
|
|
|
|
|
# |
|
1191
|
|
|
|
|
|
|
# - allow argument "compare => sub {...}" in array_to_moose() call to |
|
1192
|
|
|
|
|
|
|
# allow a user-defined row-comparison routine to be passed to |
|
1193
|
|
|
|
|
|
|
# Array::GroupBy::igroup_by() |
|
1194
|
|
|
|
|
|
|
# |
|
1195
|
|
|
|
|
|
|
# - make it Mouse-compatible? (All meta->... stuff would break?) |
|
1196
|
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
##### SUBROUTINE INDEX ##### |
|
1198
|
|
|
|
|
|
|
# # |
|
1199
|
|
|
|
|
|
|
# gen by index_subs.pl # |
|
1200
|
|
|
|
|
|
|
# on 24 Apr 2014 21:11 # |
|
1201
|
|
|
|
|
|
|
# # |
|
1202
|
|
|
|
|
|
|
############################ |
|
1203
|
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
|
|
1205
|
|
|
|
|
|
|
####### Packages ########### |
|
1206
|
|
|
|
|
|
|
|
|
1207
|
|
|
|
|
|
|
# Array::To::Moose ......................... 1 |
|
1208
|
|
|
|
|
|
|
# array_to_moose ......................... 2 |
|
1209
|
|
|
|
|
|
|
# set_class_ind .......................... 2 |
|
1210
|
|
|
|
|
|
|
# set_key_ind ............................ 2 |
|
1211
|
|
|
|
|
|
|
# throw_multiple_rows .................... 2 |
|
1212
|
|
|
|
|
|
|
# throw_nonunique_keys ................... 2 |
|
1213
|
|
|
|
|
|
|
# _check_descriptor ...................... 4 |
|
1214
|
|
|
|
|
|
|
# _check_non_ref_attribs ................. 9 |
|
1215
|
|
|
|
|
|
|
# _check_ref_attribs ..................... 8 |
|
1216
|
|
|
|
|
|
|
# _check_subobj .......................... 6 |
|
1217
|
|
|
|
|
|
|
|