File Coverage

blib/lib/Data/FormValidator/Profile.pm
Criterion Covered Total %
statement 99 99 100.0
branch 23 24 95.8
condition n/a
subroutine 23 23 100.0
pod 11 11 100.0
total 156 157 99.3


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