line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
# Copyright 2005 Edmund von der Burg |
2
|
|
|
|
|
|
|
# Distributed under the same license as Perl itself. |
3
|
|
|
|
|
|
|
|
4
|
1
|
|
|
1
|
|
675
|
use strict; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
37
|
|
5
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
51
|
|
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
package Class::DBI::DFV; |
8
|
1
|
|
|
1
|
|
23
|
use base 'Class::DBI'; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
1351
|
|
9
|
|
|
|
|
|
|
|
10
|
1
|
|
|
1
|
|
108190
|
use Data::FormValidator; |
|
1
|
|
|
|
|
39905
|
|
|
1
|
|
|
|
|
63
|
|
11
|
1
|
|
|
1
|
|
14
|
use Data::Dumper; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
1194
|
|
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = '0.02'; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head1 NAME |
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
Class::DBI::DFV - check that your data is valid using DFV |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=head1 SYNOPSIS |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
package My::DBI; |
22
|
|
|
|
|
|
|
use base 'Class::DBI::DFV'; |
23
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
__PACKAGE__->connection(...); |
25
|
|
|
|
|
|
|
__PACKAGE__->table(...); |
26
|
|
|
|
|
|
|
__PACKAGE__->columns( All => qw( id val_unique val_optional ) ); |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
sub dfv_profile { |
29
|
|
|
|
|
|
|
my $class = shift; |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
return { |
32
|
|
|
|
|
|
|
filters => 'trim', |
33
|
|
|
|
|
|
|
required => [qw/val_unique/], |
34
|
|
|
|
|
|
|
constraint_methods => { val_unique => qr/^\d+$/ }, |
35
|
|
|
|
|
|
|
}; |
36
|
|
|
|
|
|
|
} |
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=head1 INTRODUCTION |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
NOTE: this module is still under development - please see the bottom |
41
|
|
|
|
|
|
|
of the pod for how you can help. |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
C combines the database abstraction of L |
44
|
|
|
|
|
|
|
with the data validation of L. It allows you to |
45
|
|
|
|
|
|
|
specify a DFV profile that the data must match. This profile is |
46
|
|
|
|
|
|
|
applied when you do an C or a C. If the profile does not |
47
|
|
|
|
|
|
|
match then the normal C_croak> method is called. |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head1 METHODS |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
=cut |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
my $DEBUG = 0; |
54
|
|
|
|
|
|
|
warn "DEBUG is true" if $DEBUG; |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=head2 validate_column_values |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
Class::DBI::DFV overides the C method to do |
59
|
|
|
|
|
|
|
the actual validating. Once it has validated the data it then calls |
60
|
|
|
|
|
|
|
the parent class' C method. There is no need |
61
|
|
|
|
|
|
|
to call this in your code - it is called by Class::DBI. Be warned |
62
|
|
|
|
|
|
|
though if you decide to override it as well. |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=cut |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
sub validate_column_values { |
67
|
8
|
|
|
8
|
1
|
891037
|
my ( $self, $class ) = _self_class(shift); |
68
|
8
|
|
50
|
|
|
48
|
my $data = shift || {}; |
69
|
|
|
|
|
|
|
|
70
|
8
|
50
|
|
|
|
29
|
warn "Raw: ", Dumper $data if $DEBUG; |
71
|
|
|
|
|
|
|
|
72
|
8
|
100
|
|
|
|
20
|
if ($self) { |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# Fill in any blanks that there are. |
75
|
|
|
|
|
|
|
#warn "Filling in the blanks."; |
76
|
1
|
|
|
|
|
67
|
for my $field ( map { $_->name } $class->columns('All') ) { |
|
5
|
|
|
|
|
103
|
|
77
|
5
|
100
|
|
|
|
726
|
$$data{$field} = $self->get($field) |
78
|
|
|
|
|
|
|
unless exists $$data{$field}; |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
} |
81
|
|
|
|
|
|
|
|
82
|
8
|
50
|
|
|
|
86
|
warn "Input: ", Dumper $data if $DEBUG; |
83
|
|
|
|
|
|
|
|
84
|
8
|
|
|
|
|
93
|
my $dfv_profile = $class->_get_dfv_profile; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# Check that the data is good |
87
|
8
|
|
|
|
|
75
|
my $results = Data::FormValidator->check( $data, $dfv_profile ); |
88
|
|
|
|
|
|
|
|
89
|
8
|
|
|
|
|
1848
|
$class->dfv_results($results); |
90
|
|
|
|
|
|
|
|
91
|
8
|
100
|
66
|
|
|
98
|
if ( $results->has_invalid || $results->has_missing ) { |
92
|
|
|
|
|
|
|
|
93
|
2
|
|
|
|
|
23
|
Class::DBI::_croak( "validation failed in '$class': " |
94
|
|
|
|
|
|
|
. Dumper( $results->msgs, $data ) ); |
95
|
0
|
|
|
|
|
0
|
return; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
else { |
98
|
6
|
|
|
|
|
112
|
%$data = %{ $results->valid }; |
|
6
|
|
|
|
|
26
|
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
# If we are already in the database and the Primary has not |
101
|
|
|
|
|
|
|
# changed then don't save it. |
102
|
6
|
|
|
|
|
112
|
my $primary_column = $class->columns('Primary'); |
103
|
|
|
|
|
|
|
|
104
|
6
|
100
|
|
|
|
330
|
if ($self) { |
105
|
|
|
|
|
|
|
|
106
|
1
|
50
|
|
|
|
87
|
Class::DBI->_croak( |
107
|
|
|
|
|
|
|
"Attempting to change primary key detected - Class::DBI does NOT support this" |
108
|
|
|
|
|
|
|
) |
109
|
|
|
|
|
|
|
if $self->id ne $$data{$primary_column}; |
110
|
|
|
|
|
|
|
|
111
|
1
|
|
|
|
|
93
|
delete $$data{$primary_column}; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
6
|
50
|
|
|
|
31
|
warn "Valid: ", Dumper $data if $DEBUG; |
115
|
|
|
|
|
|
|
|
116
|
6
|
|
66
|
|
|
153
|
my $whatever = $self || $class; |
117
|
6
|
|
|
|
|
140
|
return $whatever->SUPER::validate_column_values($data); |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=head2 dfv_results |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
eval { My::DBI->create( \%data ) } |
124
|
|
|
|
|
|
|
|| warn "ERROR: ", Dumper( My::DBI->dfv_results->msgs ); |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
The C method gives you access to the last results |
127
|
|
|
|
|
|
|
produced by Data::FormValidator. |
128
|
|
|
|
|
|
|
|
129
|
|
|
|
|
|
|
=cut |
130
|
|
|
|
|
|
|
|
131
|
|
|
|
|
|
|
our $_RESULTS = undef; |
132
|
|
|
|
|
|
|
|
133
|
|
|
|
|
|
|
sub dfv_results { |
134
|
9
|
|
|
9
|
1
|
2131
|
my $class = shift; |
135
|
9
|
100
|
|
|
|
38
|
return $_RESULTS unless @_; |
136
|
8
|
|
|
|
|
24
|
return $_RESULTS = shift; |
137
|
|
|
|
|
|
|
} |
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=head2 dfv_base_profile |
140
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub dfv_base_profile { |
142
|
|
|
|
|
|
|
return { |
143
|
|
|
|
|
|
|
filters => 'trim', |
144
|
|
|
|
|
|
|
msgs => { |
145
|
|
|
|
|
|
|
format => 'validation error: %s', |
146
|
|
|
|
|
|
|
constraints => { unique_constraint => 'duplicate' }, |
147
|
|
|
|
|
|
|
}, |
148
|
|
|
|
|
|
|
}; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
You will find that there are many things that you will want to put in |
152
|
|
|
|
|
|
|
all your profiles. If in your parent class you create |
153
|
|
|
|
|
|
|
C then the values in this will be combined with the |
154
|
|
|
|
|
|
|
C that you create. As a general rule anything that is |
155
|
|
|
|
|
|
|
specified in the profile will override the values in the base profile. |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
=cut |
158
|
|
|
|
|
|
|
|
159
|
1
|
|
|
1
|
1
|
3
|
sub dfv_base_profile { return {}; } |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Combine the dfv_profile and the base_dfv_profile. |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
our %_CACHED_PROFILES = (); |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head2 _get_dfv_profile |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
This is a private method but as it changes your profile it is |
168
|
|
|
|
|
|
|
documented here. The first thing it does is to combine the |
169
|
|
|
|
|
|
|
C and the C. |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
Having done that it then looks at what columns you have in the |
172
|
|
|
|
|
|
|
database and puts all the ones that are not in the profile's |
173
|
|
|
|
|
|
|
C list in the C list. |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
Finally it caches the profile to make execution faster. Make sure that |
176
|
|
|
|
|
|
|
you use sub refs if you want something to be executed each time the |
177
|
|
|
|
|
|
|
profile is parsed, eg: |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
defaults => { |
180
|
|
|
|
|
|
|
wrong => rand(1000), |
181
|
|
|
|
|
|
|
right => sub { rand(1000) }, |
182
|
|
|
|
|
|
|
}, |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
The 'wrong' one will always return the same value - as the value is |
185
|
|
|
|
|
|
|
created when the profile is created. The 'right' one will be executed |
186
|
|
|
|
|
|
|
each time that the profile is applied and so will be different each |
187
|
|
|
|
|
|
|
time. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=cut |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
sub _get_dfv_profile { |
192
|
8
|
|
|
8
|
|
16
|
my $class = shift; |
193
|
8
|
100
|
|
|
|
40
|
return $_CACHED_PROFILES{$class} if $_CACHED_PROFILES{$class}; |
194
|
|
|
|
|
|
|
|
195
|
1
|
|
|
|
|
8
|
my $base = $class->dfv_base_profile; |
196
|
1
|
|
|
|
|
10
|
my $profile = $class->dfv_profile; |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
# Add the stuff in base to the profile if it is missing. |
199
|
1
|
|
0
|
|
|
5
|
$$profile{$_} ||= $$base{$_} for keys %$base; |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
# Do obvious stuff |
202
|
1
|
50
|
|
|
|
6
|
unless ( $$profile{optional} ) { |
203
|
1
|
|
|
|
|
2
|
my %required = map { $_ => 1 } @{ $$profile{required} }; |
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
3
|
|
204
|
5
|
|
|
|
|
15
|
my @optional = |
205
|
1
|
|
|
|
|
7
|
grep { !$required{$_} } map { $_->name } $class->columns('All'); |
|
5
|
|
|
|
|
103
|
|
206
|
1
|
|
|
|
|
4
|
$$profile{optional} = \@optional; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# warn Dumper $profile; |
210
|
1
|
|
|
|
|
5
|
return $_CACHED_PROFILES{$class} = $profile; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub _self_class { |
214
|
8
|
100
|
|
8
|
|
43
|
my $self = ref( $_[0] ) ? $_[0] : undef; |
215
|
8
|
100
|
|
|
|
28
|
my $class = $self ? ref($self) : $_[0]; |
216
|
8
|
|
|
|
|
100
|
return ( $self, $class ); |
217
|
|
|
|
|
|
|
} |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
############################################################################ |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
=head2 unique_constraint |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
EXPERIMENTAL - this is a constraint that lets you check that the |
224
|
|
|
|
|
|
|
database does not contain duplicate values. Please see the module |
225
|
|
|
|
|
|
|
C in the test suite for usage. The way that this |
226
|
|
|
|
|
|
|
constraint is used may well change. |
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
=cut |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
sub unique_constraint { |
231
|
2
|
|
|
2
|
1
|
15
|
my $class = shift; |
232
|
2
|
|
|
|
|
77
|
my $table = $class->table; |
233
|
|
|
|
|
|
|
|
234
|
2
|
|
|
|
|
29
|
my @columns = @_; |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
return sub { |
237
|
13
|
|
|
13
|
|
12395
|
my $dfvr = shift; |
238
|
13
|
|
|
|
|
51
|
my $main_field = $dfvr->get_current_constraint_field; |
239
|
13
|
|
|
|
|
70
|
my @fields = @columns; |
240
|
13
|
100
|
|
|
|
49
|
@fields = ($main_field) unless scalar @fields; |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
#warn Dumper $dfvr; |
243
|
|
|
|
|
|
|
#warn "Fields to check: ", join ', ', @fields; |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# Set things up. |
246
|
13
|
|
|
|
|
54
|
$dfvr->name_this('unique_constraint'); |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Create the args to search for. |
249
|
13
|
|
|
|
|
64
|
my %args = map { $_ => $dfvr->{__INPUT_DATA}{$_} } @fields; |
|
18
|
|
|
|
|
71
|
|
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
#warn "args: ", Dumper \%args; |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
# See if the value is stored in the database. |
254
|
13
|
|
|
|
|
76
|
my $existing = $class->retrieve(%args); |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# If nothing found then it cannot be a duplicate. |
257
|
13
|
100
|
|
|
|
18824
|
return 1 unless $existing; |
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
# If it was found it might be ourselves. |
260
|
3
|
|
|
|
|
279
|
my $new_id = $dfvr->{__INPUT_DATA}{ $class->columns('Primary') }; |
261
|
3
|
|
|
|
|
132
|
my $old_id = $existing->id; |
262
|
|
|
|
|
|
|
|
263
|
3
|
100
|
66
|
|
|
200
|
if ( $new_id && $new_id eq $old_id ) { |
264
|
1
|
|
|
|
|
7
|
return 1; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# It exists and is not us - duplicate. |
268
|
2
|
|
|
|
|
12
|
$dfvr->msgs->{$main_field} = 'duplicate'; |
269
|
2
|
|
|
|
|
165
|
return 0; |
270
|
2
|
|
|
|
|
28
|
}; |
271
|
|
|
|
|
|
|
} |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=head1 SEE ALSO |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
L - Simple Database Abstraction |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
L - Validates user input (usually from an HTML |
278
|
|
|
|
|
|
|
form) based on input profile. |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
=head1 AUTHOR |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
Edmund von der Burg - C |
283
|
|
|
|
|
|
|
|
284
|
|
|
|
|
|
|
=head1 CONTRIBUTE |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
If you want to change something is Class::DBI::DFV I would be |
287
|
|
|
|
|
|
|
delighted to help. You can get the latest from |
288
|
|
|
|
|
|
|
L. Anonymous |
289
|
|
|
|
|
|
|
access is read-only but if you have an idea please contact me and I'll |
290
|
|
|
|
|
|
|
create an account for you so you can commit too. |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
=cut |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
1; |
295
|
|
|
|
|
|
|
|