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
|
22
|
|
|
22
|
|
753774
|
use 5.008008; |
|
22
|
|
|
|
|
90
|
|
|
22
|
|
|
|
|
1308
|
|
12
|
22
|
|
|
22
|
|
142
|
use strict; |
|
22
|
|
|
|
|
131
|
|
|
22
|
|
|
|
|
1039
|
|
13
|
22
|
|
|
22
|
|
152
|
use warnings; |
|
22
|
|
|
|
|
122
|
|
|
22
|
|
|
|
|
995
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
require Exporter; |
16
|
22
|
|
|
22
|
|
127
|
use base qw( Exporter ); |
|
22
|
|
|
|
|
42
|
|
|
22
|
|
|
|
|
5601
|
|
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
|
22
|
|
|
22
|
|
23832
|
use version; our $VERSION = qv('0.0.8'); |
|
22
|
|
|
|
|
89214
|
|
|
22
|
|
|
|
|
151
|
|
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
# BEGIN { $Exporter::Verbose=1 }; |
35
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
#BEGIN { print "Got Array::To:Moose Module\n" } |
37
|
|
|
|
|
|
|
|
38
|
22
|
|
|
22
|
|
25832
|
use Params::Validate::Array qw(:all); |
|
22
|
|
|
|
|
398269
|
|
|
22
|
|
|
|
|
197
|
|
39
|
22
|
|
|
22
|
|
43360
|
use Array::GroupBy qw(igroup_by str_row_equal); |
|
22
|
|
|
|
|
30540
|
|
|
22
|
|
|
|
|
7701
|
|
40
|
22
|
|
|
22
|
|
175
|
use Carp; |
|
22
|
|
|
|
|
51
|
|
|
22
|
|
|
|
|
1162
|
|
41
|
22
|
|
|
22
|
|
27326
|
use Data::Dumper; |
|
22
|
|
|
|
|
290045
|
|
|
22
|
|
|
|
|
3050
|
|
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
|
22
|
|
|
22
|
|
386
|
BEGIN { $KEY = 'key' ; $CLASS = 'class' } |
|
22
|
|
|
|
|
52579
|
|
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
|
0
|
0
|
0
|
0
|
1
|
0
|
croak "set_key_ind('$_[0]') not a legal identifier" |
66
|
|
|
|
|
|
|
if defined $_[0] and $_[0] !~ /^\w+$/; |
67
|
|
|
|
|
|
|
|
68
|
0
|
0
|
|
|
|
0
|
$KEY = defined $_[0] ? $_[0] : 'key'; |
69
|
|
|
|
|
|
|
} |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
############################################ |
72
|
|
|
|
|
|
|
sub set_class_ind { |
73
|
0
|
0
|
0
|
0
|
1
|
0
|
croak "set_class_ind('$_[0]') not a legal identifier" |
74
|
|
|
|
|
|
|
if defined $_[0] and $_[0] !~ /^\w+$/; |
75
|
|
|
|
|
|
|
|
76
|
0
|
0
|
|
|
|
0
|
$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
|
3
|
|
|
3
|
1
|
3798
|
my ($data, $desc) = validate(@_, |
100
|
|
|
|
|
|
|
[ data => { type => ARRAYREF }, |
101
|
|
|
|
|
|
|
desc => { type => HASHREF }, |
102
|
|
|
|
|
|
|
] |
103
|
|
|
|
|
|
|
); |
104
|
|
|
|
|
|
|
|
105
|
3
|
100
|
|
|
|
237
|
croak "'data => ...' isn't a 2D array (AoA)" |
106
|
|
|
|
|
|
|
unless ref($data->[0]); |
107
|
|
|
|
|
|
|
|
108
|
1
|
50
|
|
|
|
15
|
croak 'empty descriptor' |
109
|
|
|
|
|
|
|
unless keys %$desc; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
#print "data ", Dumper($data), "\ndesc ", Dumper($desc); |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
|
114
|
0
|
|
|
|
|
|
my $result = []; # returned result is either an array or a hash of objects |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# extract column of possible hash key |
117
|
0
|
|
|
|
|
|
my $keycol; |
118
|
|
|
|
|
|
|
|
119
|
0
|
0
|
|
|
|
|
if (exists $desc->{$KEY}) { |
120
|
0
|
|
|
|
|
|
$keycol = $desc->{$KEY}; |
121
|
|
|
|
|
|
|
|
122
|
0
|
|
|
|
|
|
$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
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
my $iter = igroup_by( |
146
|
|
|
|
|
|
|
data => $data, |
147
|
|
|
|
|
|
|
compare => \&str_row_equal, |
148
|
|
|
|
|
|
|
args => [ values %$attribs ], |
149
|
|
|
|
|
|
|
); |
150
|
|
|
|
|
|
|
|
151
|
0
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
my %attribs = map { $_ => $subset->[0]->[$attribs->{$_}] } keys %$attribs; |
|
0
|
|
|
|
|
|
|
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
|
0
|
|
|
|
|
|
while (my($attr_name, $col) = each %$ref_attribs) { |
168
|
0
|
|
|
|
|
|
my @col = map { $_->[$col] } @$subset; |
|
0
|
|
|
|
|
|
|
169
|
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
|
0
|
|
|
|
|
|
while( my($attr_name, $desc) = each %$sub_obj_desc) { |
179
|
|
|
|
|
|
|
|
180
|
0
|
0
|
|
|
|
|
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
|
0
|
|
|
|
|
|
my $sub_obj = array_to_moose( data => $subset, |
186
|
|
|
|
|
|
|
desc => $desc, |
187
|
|
|
|
|
|
|
); |
188
|
|
|
|
|
|
|
|
189
|
0
|
|
|
|
|
|
$sub_obj = _check_subobj($class, $attr_name, $type, $sub_obj); |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
#print "type $type\n"; |
192
|
|
|
|
|
|
|
|
193
|
0
|
|
|
|
|
|
$attribs{$attr_name} = $sub_obj; |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
# print "after 2: attrib ", Dumper(\%attribs), "\n"; |
197
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
my $obj; |
199
|
0
|
|
|
|
|
|
eval { $obj = $class->meta->new_object(%attribs) }; |
|
0
|
|
|
|
|
|
|
200
|
0
|
0
|
|
|
|
|
croak "Can't make a new '$class' object:\n$@\n" |
201
|
|
|
|
|
|
|
if $@; |
202
|
|
|
|
|
|
|
|
203
|
0
|
0
|
|
|
|
|
if (defined $keycol) { |
204
|
0
|
|
|
|
|
|
my $key_name = $subset->[0]->[$keycol]; |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# optionally croak if we are overwriting an existing hash entry |
207
|
0
|
0
|
0
|
|
|
|
croak "Non-unique key '$key_name' in '", $desc->{$CLASS}, "' class" |
208
|
|
|
|
|
|
|
if exists $result->{$key_name} and $throw_nonunique_keys; |
209
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
|
$result->{$key_name} = $obj; |
211
|
|
|
|
|
|
|
} else { |
212
|
0
|
|
|
|
|
|
push @{$result}, $obj; |
|
0
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
} |
214
|
|
|
|
|
|
|
} |
215
|
0
|
|
|
|
|
|
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
|
0
|
|
|
0
|
|
|
my ($data, $desc) = @_; |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
# remove from production! |
245
|
0
|
0
|
|
|
|
|
croak "_check_descriptor() needs two arguments" |
246
|
|
|
|
|
|
|
unless @_ == 2; |
247
|
|
|
|
|
|
|
|
248
|
0
|
0
|
|
|
|
|
my $class = $desc->{$CLASS} |
249
|
|
|
|
|
|
|
or croak "No class descriptor '$CLASS => ...' in descriptor:\n", |
250
|
|
|
|
|
|
|
Dumper($desc); |
251
|
|
|
|
|
|
|
|
252
|
0
|
|
|
|
|
|
my $meta; |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
# see other example of getting meta in Moose::Manual::??? |
255
|
0
|
|
|
|
|
|
eval{ $meta = $class->meta }; |
|
0
|
|
|
|
|
|
|
256
|
0
|
0
|
|
|
|
|
croak "Class '$class' not defined: $@" |
257
|
|
|
|
|
|
|
if $@; |
258
|
|
|
|
|
|
|
|
259
|
0
|
|
|
|
|
|
my $ncols = @{ $data->[0] }; |
|
0
|
|
|
|
|
|
|
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
# separate out simple (i.e. non-reference) attributes, reference |
262
|
|
|
|
|
|
|
# attributes, and sub-objects |
263
|
0
|
|
|
|
|
|
my ($attrib, $ref_attrib, $sub_desc); |
264
|
|
|
|
|
|
|
|
265
|
0
|
|
|
|
|
|
while ( my ($name, $value) = each %$desc) { |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# check lines which have 'simple' column numbers ( attrib or key => N) |
268
|
0
|
0
|
0
|
|
|
|
unless (ref($value) or $name eq $CLASS) { |
269
|
|
|
|
|
|
|
|
270
|
0
|
|
|
|
|
|
my $msg = "attribute '$name => $value'"; |
271
|
|
|
|
|
|
|
|
272
|
0
|
0
|
|
|
|
|
croak "$msg must be a (non-negative) integer" |
273
|
|
|
|
|
|
|
unless $value =~ /^\d+$/; |
274
|
|
|
|
|
|
|
|
275
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
0
|
|
|
|
if ($name eq $CLASS or $name eq $KEY) { |
281
|
0
|
0
|
|
|
|
|
croak "The '$class' object has an attribute called '$name'" |
282
|
|
|
|
|
|
|
if $meta->find_attribute_by_name($name); |
283
|
|
|
|
|
|
|
|
284
|
0
|
|
|
|
|
|
next; |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
0
|
0
|
|
|
|
|
croak "Attribute '$name' not in '$class' object" |
288
|
|
|
|
|
|
|
unless $meta->find_attribute_by_name($name); |
289
|
|
|
|
|
|
|
|
290
|
0
|
0
|
|
|
|
|
if ((my $ref = ref($value)) eq 'HASH') { |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
291
|
0
|
|
|
|
|
|
$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
|
|
|
|
|
croak "attribute must be of form, e.g.: '$name => [N], " |
300
|
|
|
|
|
|
|
. "where N is a single integer'" |
301
|
|
|
|
|
|
|
unless @$value == 1; |
302
|
|
|
|
|
|
|
|
303
|
0
|
|
|
|
|
|
my $msg = "attribute '$name => [ " . $value->[0] . " ]'. '" . |
304
|
|
|
|
|
|
|
$value->[0] . "'"; |
305
|
|
|
|
|
|
|
|
306
|
0
|
0
|
|
|
|
|
croak "$msg must be a (non-negative) integer" |
307
|
|
|
|
|
|
|
unless $value->[0] =~ /^\d+$/; |
308
|
|
|
|
|
|
|
|
309
|
0
|
0
|
|
|
|
|
croak "$msg greater than # cols in the data ($ncols)" |
310
|
|
|
|
|
|
|
if $value->[0] > $ncols - 1; |
311
|
|
|
|
|
|
|
|
312
|
0
|
|
|
|
|
|
$ref_attrib->{$name} = $value->[0]; |
313
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
} elsif ($ref) { |
315
|
0
|
|
|
|
|
|
croak "attribute '$name' can't be a '$ref' reference"; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
} else { |
318
|
|
|
|
|
|
|
# "simple" attribute |
319
|
0
|
|
|
|
|
|
$attrib->{$name} = $value; |
320
|
|
|
|
|
|
|
} |
321
|
|
|
|
|
|
|
} |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
# check ref- and ... |
325
|
0
|
0
|
|
|
|
|
_check_ref_attribs($class, $ref_attrib) |
326
|
|
|
|
|
|
|
if $ref_attrib; |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
# ... non-ref attributes from the descriptor against the Moose object |
329
|
0
|
0
|
|
|
|
|
_check_non_ref_attribs($class, $attrib) |
330
|
|
|
|
|
|
|
if $attrib; |
331
|
|
|
|
|
|
|
|
332
|
0
|
0
|
0
|
|
|
|
croak "no attributes with column numbers in descriptor:\n", Dumper($desc) |
333
|
|
|
|
|
|
|
unless $attrib and %$attrib; |
334
|
|
|
|
|
|
|
|
335
|
0
|
|
|
|
|
|
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
|
0
|
|
|
0
|
|
|
my ($class, $attr_name, $type, $sub_obj) = @_; |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
# for now... |
364
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
if ( $type =~ /^HashRef\[([^]]*)\]/ ) { |
|
|
0
|
|
|
|
|
|
370
|
|
|
|
|
|
|
|
371
|
|
|
|
|
|
|
#print "subobj is of type ", ref($sub_obj), "\n"; |
372
|
|
|
|
|
|
|
#print "subobj ", Dumper($sub_obj); |
373
|
|
|
|
|
|
|
|
374
|
0
|
0
|
|
|
|
|
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
|
0
|
|
|
|
|
|
croak("Moose attribute '$attr_name' has type '$type' " |
382
|
|
|
|
|
|
|
. "but your descriptor produced an object " |
383
|
0
|
|
|
|
|
|
. "of type 'HashRef[" . ref( ( values %{$sub_obj} )[0] ) |
384
|
|
|
|
|
|
|
. "]'\n") |
385
|
0
|
0
|
|
|
|
|
if ref( ( values %{$sub_obj} )[0] ) ne $1; |
386
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
} elsif ( $type =~ /^ArrayRef\[([^]]*)\]/ ) { |
388
|
|
|
|
|
|
|
|
389
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
|
|
|
|
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
|
0
|
0
|
0
|
|
|
|
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
|
0
|
|
|
|
|
|
$sub_obj = $sub_obj->[0]; |
419
|
|
|
|
|
|
|
|
420
|
|
|
|
|
|
|
# print "\$sub_obj type is ", ref($sub_obj), "\n"; |
421
|
|
|
|
|
|
|
|
422
|
0
|
0
|
|
|
|
|
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
|
0
|
|
|
|
|
|
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
|
22
|
|
|
22
|
|
74
|
%simple_types = map { $_ => 1 } |
|
198
|
|
|
|
|
32767
|
|
462
|
|
|
|
|
|
|
qw ( Any Item Bool Undef Defined Value Str Num Int ); |
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
|
|
|
my ($class, $ref_attribs) = @_; |
481
|
|
|
|
|
|
|
|
482
|
0
|
0
|
|
|
|
|
my $meta = $class->meta |
483
|
|
|
|
|
|
|
or croak "No meta for class '$class'?"; |
484
|
|
|
|
|
|
|
|
485
|
0
|
|
|
|
|
|
foreach my $attrib ( keys %{ $ref_attribs } ) { |
|
0
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
|
my $msg = "Moose class '$class' ref attrib '$attrib'"; |
487
|
|
|
|
|
|
|
|
488
|
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
|
|
|
|
|
if ($constraint =~ /^ArrayRef\[([^]]*)\]/ ) { |
494
|
|
|
|
|
|
|
|
495
|
0
|
0
|
|
|
|
|
croak "$msg has bad type '$constraint' ('$1' is not a simple type)" |
496
|
|
|
|
|
|
|
unless $simple_types{$1}; |
497
|
|
|
|
|
|
|
|
498
|
0
|
|
|
|
|
|
return; |
499
|
|
|
|
|
|
|
} |
500
|
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
|
0
|
|
|
0
|
|
|
my ($class, $attribs) = @_; |
522
|
|
|
|
|
|
|
|
523
|
0
|
0
|
|
|
|
|
my $meta = $class->meta |
524
|
|
|
|
|
|
|
or croak "No meta for class '$class'?"; |
525
|
|
|
|
|
|
|
|
526
|
0
|
|
|
|
|
|
foreach my $attrib ( keys %{ $attribs } ) { |
|
0
|
|
|
|
|
|
|
527
|
0
|
|
|
|
|
|
my $msg = "Moose class '$class', attrib '$attrib'"; |
528
|
|
|
|
|
|
|
|
529
|
0
|
0
|
|
|
|
|
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
|
0
|
|
|
|
|
|
$constraint =~ /^Maybe\[([^]]+)\]/; |
536
|
0
|
0
|
|
|
|
|
$constraint = $1 if $1; |
537
|
|
|
|
|
|
|
|
538
|
|
|
|
|
|
|
#print " after: $attrib '$constraint'\n"; |
539
|
|
|
|
|
|
|
|
540
|
0
|
0
|
|
|
|
|
next if $simple_types{$constraint}; |
541
|
|
|
|
|
|
|
|
542
|
0
|
|
|
|
|
|
$msg = "$msg has type '$constraint', but your descriptor had '$attrib => " |
543
|
|
|
|
|
|
|
. $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.8 |
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
|
|
|
|
|
|
|
|