File Coverage

blib/lib/Data/FormValidator/Profile.pm
Criterion Covered Total %
statement 95 95 100.0
branch 23 24 95.8
condition n/a
subroutine 21 21 100.0
pod 11 11 100.0
total 150 151 99.3


line stmt bran cond sub pod time code
1             package Data::FormValidator::Profile;
2              
3             ###############################################################################
4             # Required inclusions.
5             ###############################################################################
6 2     2   90582 use strict;
  2         6  
  2         71  
7 2     2   12 use warnings;
  2         5  
  2         49  
8 2     2   11 use Carp;
  2         9  
  2         202  
9 2     2   2035 use List::MoreUtils qw(part);
  2         8944  
  2         592  
10              
11             ###############################################################################
12             # Version number.
13             ###############################################################################
14             our $VERSION = '0.07';
15              
16             ###############################################################################
17             # Use the '_arrayify()' method from DFV.
18             ###############################################################################
19 2     2   3369 use Data::FormValidator;
  2         102423  
  2         2305  
20             *_arrayify = \&Data::FormValidator::_arrayify;
21              
22             ###############################################################################
23             # Subroutine: new()
24             ###############################################################################
25             # Creates a new DFV::Profile object, based on the given profile (which can be
26             # provided either as a HASH or a HASHREF).
27             ###############################################################################
28             sub new {
29 22     22 1 24260 my $class = shift;
30 22 100       116 my $self = {
31             'profile' => (ref($_[0]) eq 'HASH') ? $_[0] : {@_},
32             };
33 22         109 bless $self, $class;
34             }
35              
36             ###############################################################################
37             # Subroutine: check($data)
38             # Parameters: $data - Hash-ref of data to check
39             # Returns: $results - DFV::Results object
40             ###############################################################################
41             # Checks the given '$data' against the profile. This method simply acts as a
42             # short-hand to 'Data::FormValidator->check($data,$profile->profile)'.
43             ###############################################################################
44             sub check {
45 1     1 1 424 my ($self, $data) = @_;
46 1         5 return Data::FormValidator->check($data, $self->profile);
47             }
48              
49             ###############################################################################
50             # Subroutine: profile()
51             ###############################################################################
52             # Returns the actual profile, as a hash-ref. You need to call this method when
53             # you want to send the profile through to 'Data::FormValidator' to do data
54             # validation.
55             ###############################################################################
56             sub profile {
57 37     37 1 1486 my $self = shift;
58 37         149 return $self->{'profile'};
59             }
60              
61             ###############################################################################
62             # Subroutine: required()
63             ###############################################################################
64             # Returns the list of "required" fields in the validation profile.
65             ###############################################################################
66             sub required {
67 1     1 1 260 my $self = shift;
68 1         5 return _arrayify($self->{profile}{required});
69             }
70              
71             ###############################################################################
72             # Subroutine: optional()
73             ###############################################################################
74             # Returns the list of "optional" fields in the validation profile.
75             ###############################################################################
76             sub optional {
77 1     1 1 319 my $self = shift;
78 1         4 return _arrayify($self->{profile}{optional});
79             }
80              
81             ###############################################################################
82             # Subroutine: only(@fields)
83             # Parameters: @fields - List of fields to include
84             ###############################################################################
85             # Reduces the profile so that it only contains information on the given list of
86             # '@fields'.
87             #
88             # Returns '$self', to support call-chaining.
89             ###############################################################################
90             sub only {
91 2     2 1 2282 my ($self, @fields) = @_;
92 2         6 my %lookup = map { $_=>1 } @fields;
  5         19  
93 2     9   14 $self->_update( sub { exists $lookup{$_[0]} } );
  9         46  
94             }
95              
96             ###############################################################################
97             # Subroutine: remove(@fields)
98             # Parameters: @fields - List of fields to exclude
99             ###############################################################################
100             # Removes any of the given '@fields' from the profile.
101             #
102             # Returns '$self', to support call-chaining.
103             ###############################################################################
104             sub remove {
105 2     2 1 367 my ($self, @fields) = @_;
106 2         5 my %lookup = map { $_=>1 } @fields;
  2         9  
107 2     8   12 $self->_update( sub { not exists $lookup{$_[0]} } );
  8         35  
108             }
109              
110             ###############################################################################
111             # Subroutine: make_optional(@fields)
112             # Parameters: @fields - List of fields to force to optional
113             ###############################################################################
114             # Ensures that the given set of '@fields' are set as being optional (even if
115             # they were previously described as being required fields).
116             #
117             # Returns '$self', to support call-chaining.
118             ###############################################################################
119             sub make_optional {
120 2     2 1 1290 my ($self, @fields) = @_;
121 2         7 my $profile = $self->profile();
122              
123             # Partition the existing list of required fields into those that are still
124             # going to be required, and those that are being made optional.
125 2         6 my %make_optional = map { $_ => 1 } @fields;
  1         6  
126             my ($required, $optional) =
127 4     4   149 part { exists $make_optional{$_} }
128 2         14 _arrayify($profile->{required});
129              
130             # Update the lists of required/optional fields.
131 2         13 $profile->{required} = $required;
132 2 100       37 $profile->{optional} = [
133             _arrayify($profile->{optional}),
134 2         9 @{$optional || []},
135             ];
136              
137             # Support call chaining.
138 2         8 return $self;
139             }
140              
141             ###############################################################################
142             # Subroutine: make_required(@fields)
143             # Parameters: @fields - List of fields to force to required
144             ###############################################################################
145             # Ensures that the given set of '@fields' are set as being required (even if
146             # they were previously described as being optional fields).
147             #
148             # Returns '$self', to support call-chaining.
149             ###############################################################################
150             sub make_required {
151 2     2 1 793 my ($self, @fields) = @_;
152 2         8 my $profile = $self->profile();
153              
154             # Partition the existing list of optional fields into those that are still
155             # going to be required, and those that are being made required.
156 2         5 my %make_required = map { $_ => 1 } @fields;
  1         5  
157             my ($optional, $required) =
158 4     4   81 part { exists $make_required{$_} }
159 2         14 _arrayify($profile->{optional});
160              
161             # Update the lists of required/optional fields.
162 2         10 $profile->{optional} = $optional;
163 2 100       37 $profile->{required} = [
164             _arrayify($profile->{required}),
165 2         9 @{$required || []},
166             ];
167              
168             # Support call chaining.
169 2         8 return $self;
170             }
171              
172             ###############################################################################
173             # Subroutine: set(%options)
174             # Parameters: %options - DFV options to set
175             ###############################################################################
176             # Explicitly sets one or more '%options' into the profile. Useful when you
177             # KNOW exactly what you want to add/do to the profile.
178             #
179             # Returns '$self', to support call-chaining.
180             ###############################################################################
181             sub set {
182 1     1 1 711 my ($self, %options) = @_;
183 1         4 my $profile = $self->profile();
184 1         10 while (my ($key,$val) = each %options) {
185 2         8 $profile->{$key} = $val;
186             }
187 1         4 return $self;
188             }
189              
190             ###############################################################################
191             # Subroutine: add($field, %args)
192             # Parameters: $field - Field to add to validation profile
193             # %args - Hash of args controlling validation of field
194             ###############################################################################
195             # Adds the given '$field' to the validation profile, and sets up additional
196             # validation rules as per the provided '%args'.
197             #
198             # If the field already exists in the profile, this method throws a fatal
199             # exception.
200             #
201             # Returns '$self', to support call-chaining.
202             #
203             # Acceptable '%args' include:
204             # required - If non-zero, specifies that the field is required and is
205             # not an optional field (default is to be optional)
206             # default - Default value for the field.
207             # dependencies - "dependencies" for this field. Replaces existing value.
208             # filters - "field_filters" to be applied. Replaces existing value.
209             # constraints - "constraint_methods" for this field. Replaces existing
210             # value.
211             # msgs - Hash-ref of "constraint messages" that are related to
212             # this field. Replaces existing values.
213             #
214             # Here's an example to help show how the '%args' are mapped into a validation
215             # profile:
216             #
217             # $profile->add(
218             # 'username',
219             # required => 1,
220             # filters => ['trim', 'lc'],
221             # constraints => FV_length_between(4,32),
222             # msgs => {
223             # length_between => 'Username must be 4-32 chars in length.',
224             # },
225             # );
226             #
227             # becomes:
228             #
229             # {
230             # required => [qw( username )],
231             # field_filters => {
232             # username => ['trim', 'lc'],
233             # },
234             # constraint_methods => {
235             # username => FV_length_between(4,32),
236             # },
237             # msgs => {
238             # constraints => {
239             # length_between => 'Username must be ...',
240             # },
241             # },
242             # }
243             ###############################################################################
244             sub add {
245 10     10 1 4772 my ($self, $field, %args) = @_;
246              
247             # Get the profile we're manipulating.
248 10         50 my $profile = $self->profile();
249              
250             # Make sure that the field isn't already defined
251 10         21 foreach my $type (qw( required optional )) {
252 19 100       184 if (grep { $_ eq $field } _arrayify($profile->{$type})) {
  3         62  
253 1         26 croak "field '$field' already defined in DFV profile.\n";
254             }
255             }
256              
257             # Add the field to the profile
258 9 100       99 my $type = $args{'required'} ? 'required' : 'optional';
259 9         37 $profile->{$type} = [
260             _arrayify($profile->{$type}),
261             $field,
262             ];
263              
264             # Defaults
265 9 100       87 if ($args{'default'}) {
266 1         5 $profile->{'defaults'}{$field} = $args{'default'};
267             }
268              
269             # Dependencies
270 9 100       22 if ($args{'dependencies'}) {
271 1         6 $profile->{'dependencies'}{$field} = $args{'dependencies'};
272             }
273              
274             # Field filters
275 9 100       46 if ($args{'filters'}) {
276 2         8 $profile->{'field_filters'}{$field} = $args{'filters'};
277             }
278              
279             # Constraint methods
280 9 100       26 if ($args{'constraints'}) {
281 3         8 $profile->{'constraint_methods'}{$field} = $args{'constraints'};
282             }
283              
284             # Constraint messages
285 9 100       32 if ($args{'msgs'}) {
286 2         3 foreach my $key (keys %{$args{'msgs'}}) {
  2         7  
287 2         11 $profile->{'msgs'}{'constraints'}{$key} = $args{'msgs'}{$key};
288             }
289             }
290              
291             # Return ourselves back to the caller, for call chaining.
292 9         29 return $self;
293             }
294              
295             ###############################################################################
296             # Subroutine: _update($matcher)
297             # Parameters: $matcher - Field matching routine
298             ###############################################################################
299             # INTERNAL METHOD. Updates the profile so that it includes only those fields
300             # that return true from the given '$matcher' routine.
301             ###############################################################################
302             sub _update {
303 4     4   7 my ($self, $matcher) = @_;
304              
305             # Get the profile we're manipulating.
306 4         13 my $profile = $self->profile();
307              
308             # list-based fields: required, optional
309 4         11 foreach my $type (qw( required optional )) {
310 8 50       25 if (exists $profile->{$type}) {
311 15         394 $profile->{$type} = [
312 8         30 grep { $matcher->($_) } _arrayify($profile->{$type})
313             ];
314             }
315             }
316              
317             # hash-based fields: defaults, filters, constraints
318 4         9 foreach my $type (qw( default field_filters constraints constraint_methods )) {
319 16 100       133 if (exists $profile->{$type}) {
320 1         6 $profile->{$type} = {
321 2         6 map { $_ => $profile->{$type}{$_} }
322 2         5 grep { $matcher->($_) }
323 2         4 keys %{$profile->{$type}}
324             };
325             }
326             }
327              
328             # return ourselves back to the caller, for call chaining
329 4         19 return $self;
330             }
331              
332             1;
333              
334             =head1 NAME
335              
336             Data::FormValidator::Profile - Profile object for Data::FormValidator
337              
338             =head1 SYNOPSIS
339              
340             use Data::FormValidator;
341             use Data::FormValidator::Profile;
342              
343             # create a new DFV::Profile object
344             $profile = Data::FormValidator::Profile->new( {
345             optional => [qw( this that )],
346             required => [qw( some other thing )],
347             } );
348              
349             # query the optional/required fields in the profile
350             @optional = $profile->optional();
351             @required = $profile->required();
352              
353             # reduce the profile to just a limited set of fields
354             $profile->only( qw(this that) );
355              
356             # remove fields from the profile
357             $profile->remove( qw(some other thing) );
358              
359             # add a new field to the profile
360             $profile->add( 'username',
361             required => 1,
362             filters => 'trim',
363             constraints => [ ... ],
364             msgs => {
365             constraints => {
366             file_max_bytes => 'too big',
367             },
368             },
369             );
370              
371             # call chaining, to make manipulation quicker
372             $profile->only(qw( this that other ))
373             ->remove(qw( that ))
374             ->add(qw( foo ))
375             ->check($data);
376              
377             # use the profile to validate data
378             $data = { ... };
379             $res = $profile->check($data);
380             # ... or
381             $res = Data::FormValidator->check( $data, $profile->profile() );
382              
383             =head1 DESCRIPTION
384              
385             C provides an interface to help manage
386             C profiles.
387              
388             I found that I was frequently using C profiles to help
389             define my DB constraints and validation rules, but that depending on the
390             context I was working in I may only be manipulating a small handful of the
391             fields at any given point. Although I could query my DB layer to get the
392             default validation profile, I was really only concerned with the rules for two
393             or three fields. Thus, C, to help make it easier
394             to trim profiles to include only certain sets of fields in the profile.
395              
396             =head2 Limitations
397              
398             All said, though, C has some limitations that you
399             need to be aware of.
400              
401             =over
402              
403             =item *
404              
405             It B removes fields from the following profile attributes:
406              
407             required
408             optional
409             defaults
410             field_filters
411             constraints
412             constraint_methods
413              
414             B effort is made to update dependencies, groups, require_some, or anything
415             based on a regexp match. Yes, that does mean that this module is limited in
416             its usefulness if you've got really fancy C profiles.
417             That said, though, I'm not using anything that fancy, so it works for me.
418              
419             =item *
420              
421             To use the profile with C, use either the form of:
422              
423             $profile->check($data)
424              
425             or
426              
427             Data::FormValidator->check($data, $profile->profile)
428              
429             C won't accept a blessed object when calling
430             Ccheck()>, so you need to call
431             C<$profile-Eprofile()> to turn the
432             profile into a HASHREF first.
433              
434             Unless you're doing anything fancier and you've got an actual
435             C object that you're working with, its easier/simpler to
436             just call C<$profile-Echeck($data)>; that's the recommended interface.
437              
438             =back
439              
440             =head1 METHODS
441              
442             =over
443              
444             =item B
445              
446             Creates a new DFV::Profile object, based on the given profile (which can be
447             provided either as a HASH or a HASHREF).
448              
449             =item B
450              
451             Checks the given C<$data> against the profile. This method simply acts as a
452             short-hand to
453             Ccheck($data,$profile-Eprofile)>.
454              
455             =item B
456              
457             Returns the actual profile, as a hash-ref. You need to call this method
458             when you want to send the profile through to C to do
459             data validation.
460              
461             =item B
462              
463             Returns the list of "required" fields in the validation profile.
464              
465             =item B
466              
467             Returns the list of "optional" fields in the validation profile.
468              
469             =item B
470              
471             Reduces the profile so that it only contains information on the given list
472             of C<@fields>.
473              
474             Returns C<$self>, to support call-chaining.
475              
476             =item B
477              
478             Removes any of the given C<@fields> from the profile.
479              
480             Returns C<$self>, to support call-chaining.
481              
482             =item B
483              
484             Ensures that the given set of C<@fields> are set as being optional (even if
485             they were previously described as being required fields).
486              
487             Returns C<$self>, to support call-chaining.
488              
489             =item B
490              
491             Ensures that the given set of C<@fields> are set as being required (even if
492             they were previously described as being optional fields).
493              
494             Returns C<$self>, to support call-chaining.
495              
496             =item B
497              
498             Explicitly sets one or more C<%options> into the profile. Useful when you
499             KNOW exactly what you want to add/do to the profile.
500              
501             Returns C<$self>, to support call-chaining.
502              
503             =item B
504              
505             Adds the given C<$field> to the validation profile, and sets up additional
506             validation rules as per the provided C<%args>.
507              
508             If the field already exists in the profile, this method throws a fatal
509             exception.
510              
511             Returns C<$self>, to support call-chaining.
512              
513             Acceptable C<%args> include:
514              
515             =over
516              
517             =item required
518              
519             If non-zero, specifies that the field is required and is not an optional
520             field (default is to be optional)
521              
522             =item default
523              
524             Default value for the field.
525              
526             =item dependencies
527              
528             "dependencies" for this field. Replaces existing value.
529              
530             =item filters
531              
532             "field_filters" to be applied. Replaces existing value.
533              
534             =item constraints
535              
536             "constraint_methods" for this field. Replaces existing value.
537              
538             =item msgs
539              
540             Hash-ref of "constraint messages" that are related to this field. Replaces
541             existing values.
542              
543             =back
544              
545             Here's an example to help show how the C<%args> are mapped into a
546             validation profile:
547              
548             $profile->add(
549             'username',
550             required => 1,
551             filters => ['trim', 'lc'],
552             constraints => FV_length_between(4,32),
553             msgs => {
554             length_between => 'Username must be 4-32 chars in length.',
555             },
556             );
557              
558             becomes:
559              
560             {
561             required => [qw( username )],
562             field_filters => {
563             username => ['trim', 'lc'],
564             },
565             constraint_methods => {
566             username => FV_length_between(4,32),
567             },
568             msgs => {
569             constraints => {
570             length_between => 'Username must be ...',
571             },
572             },
573             }
574              
575             =back
576              
577             =head1 AUTHOR
578              
579             Graham TerMarsch (cpan@howlingfrog.com)
580              
581             =head1 COPYRIGHT
582              
583             Copyright (C) 2008, Graham TerMarsch. All Rights Reserved.
584              
585             This is free software; you can redistribute it and/or modify it under the same
586             terms as Perl itself.
587              
588             =head1 SEE ALSO
589              
590             L.
591              
592             =cut