File Coverage

lib/Class/DBI/DFV.pm
Criterion Covered Total %
statement 77 78 98.7
branch 27 32 84.3
condition 7 14 50.0
subroutine 12 12 100.0
pod 4 4 100.0
total 127 140 90.7


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