File Coverage

blib/lib/DBIx/Class/Result/Validation.pm
Criterion Covered Total %
statement 92 94 97.8
branch 23 28 82.1
condition 9 12 75.0
subroutine 24 25 96.0
pod 9 9 100.0
total 157 168 93.4


line stmt bran cond sub pod time code
1             package DBIx::Class::Result::Validation;
2              
3 4     4   294080 use strict;
  4         4  
  4         95  
4 4     4   13 use warnings;
  4         4  
  4         76  
5              
6 4     4   14 use Carp;
  4         8  
  4         194  
7 4     4   807 use Try::Tiny;
  4         2891  
  4         169  
8 4     4   15 use Scalar::Util 'blessed';
  4         5  
  4         161  
9 4     4   1304 use DBIx::Class::Result::Validation::VException;
  4         19  
  4         226  
10              
11             =head1 NAME
12              
13             DBIx::Class::Result::Validation - DBIx::Class component to manage validation on result object
14              
15             =head1 VERSION
16              
17             Version 0.14
18              
19             =cut
20              
21             our $VERSION = '0.14';
22              
23             =head1 SYNOPSIS
24              
25             DBIx::Class::Result::Validation component call validate function before insert
26             or update object and unauthorized these actions if validation set
27             result_errors accessor.
28              
29             In your result class load_component :
30              
31             Package::Schema::Result::MyClass;
32              
33             use strict;
34             use warning;
35              
36             __PACKAGE__->load_component(qw/ ... Result::Validation /);
37              
38             defined your _validate function which will be called by validate function
39              
40             sub _validate
41             {
42             my $self = shift;
43             #validate if this object exist whith the same label
44             my @other = $self->result_source->resultset->search({ label => $self->label
45             id => {"!=", $self->id}});
46             if (scalar @other)
47             {
48             $self->add_result_error('label', 'label must be unique');
49             }
50              
51             }
52              
53             When you try to create or update an object Package::Schema::Result::MyClass,
54             if an other one with the same label exist, this one will be not created,
55             validate return 0 and $self->result_errors will be set.
56              
57             $self->result_errors return :
58              
59             { label => ['label must be unique'] }
60              
61             Otherwise, object is create, validate return 1 and $self->result_errors is undef.
62              
63             It is possible to set more than one key error and more than one error by key
64              
65             $self->add_result_error('label', 'label must be unique');
66             $self->add_result_error('label', "label must not be `$self->label'");
67             $self->add_result_error('id', 'id is ok but not label');
68              
69             $self->result_errors return :
70              
71             {
72             label => [
73             'label must be unique',
74             "label must not be `my label'"
75             ],
76             id => [
77             'id is ok but not label'
78             ]
79             }
80              
81             =head1 Reserved Accessor
82              
83             DBIx::Class::Result::Validation component create a new accessor to Result object.
84              
85             $self->result_errors
86              
87             This field is used to store all errors
88              
89             =cut
90              
91 4     4   21 use base qw/ DBIx::Class Class::Accessor::Grouped /;
  4         5  
  4         2441  
92             __PACKAGE__->mk_group_accessors(simple => qw(result_errors));
93              
94             =head1 SUBROUTINES/METHODS
95              
96             =head2 validate
97              
98             This validate function is called before insert or update action.
99             If result_errors is not defined it return true
100              
101             You can redefined it in your Result object and call back it with :
102              
103             return $self->next::method(@_);
104              
105             =cut
106              
107             sub validate {
108 8     8 1 58 my $self = shift;
109 8         34 $self->_erase_result_error();
110 8         328 my @columns = $self->result_source->columns;
111 8         161 foreach my $field (@columns)
112             {
113 48 100       1589 if ($self->result_source->column_info($field)->{'validation'})
114             {
115 40         507 my $validations;
116             #convert every sources of validation in array
117 40 100       79 if ( ref($self->result_source->column_info($field)->{'validation'}) ne 'ARRAY'){
118 24         236 push @{$validations},$self->result_source->column_info($field)->{'validation'};
  24         58  
119             }
120             else{
121 16         192 $validations = $self->result_source->column_info($field)->{'validation'};
122             }
123 40 50       363 if ( scalar(@{$validations}) >0 ){
  40         97  
124 40         35 foreach my $validation (@{$validations}){
  40         62  
125 56         599 my $validation_function = "validate_" . $validation;
126             #Unvalid validation method cause a croak exception
127             try{
128 56     56   1605 $self->$validation_function($field);
129             }
130             catch{
131 0     0   0 croak(
132             DBIx::Class::Result::Validation::VException->new(
133             object => $self,
134             message => "Validation : $validation is not valid"
135             )
136             );
137             }
138 56         339 }
139             }
140             }
141             }
142              
143 8         190 $self->_validate();
144 8 100       65 return 0 if (defined $self->result_errors);
145 3         14 return 1;
146             };
147              
148             =head2 error_reporting
149              
150             function to configure on object to find what is wrong after a Database throw
151              
152             =cut
153              
154             sub error_reporting {
155 5     5 1 8 return 1;
156             };
157              
158             =head2 _validate
159              
160             _validate function is the function to redefine with validation behaviour object
161              
162             =cut
163              
164             sub _validate
165             {
166 8     8   34279 return 1;
167             }
168              
169             =head2 add_result_error
170              
171             $self->add_result_error($key, $error_string)
172              
173             Add a string error attributed to a key (field of object)
174              
175             =cut
176              
177             sub add_result_error
178             {
179 8     8 1 4173 my ($self, $key, $value) = @_;
180 8 100       46 if (defined $self->result_errors)
181             {
182 3 100       11 if (defined $self->result_errors->{$key})
183 1         2 { push(@{$self->result_errors->{$key}}, $value); }
  1         10  
184             else
185 2         13 { $self->result_errors->{$key} = [$value]; }
186             }
187             else
188 5         28 { $self->result_errors({$key => [$value]}); }
189             }
190              
191             =head2 insert
192              
193             call before DBIx::Calss::Base insert
194              
195             Insert is done only if validate method return true
196              
197             =cut
198              
199             sub insert {
200 7     7 1 52033364 my $self = shift;
201              
202 7         46 my $insert = $self->next::can;
203 7         98 return $self->_try_next_method($self->next::can, @_);
204             }
205              
206             =head2 update
207              
208             Call before DBIx::Class::Base update
209              
210             Update is done only if validate method return true
211              
212             =cut
213              
214             sub update {
215 1     1 1 1902 my $self = shift;
216 1 50       3 if ( my $columns = shift ) {
217 0         0 $self->set_inflated_columns($columns);
218             }
219 1         4 return $self->_try_next_method( $self->next::can, @_ );
220             }
221              
222             sub _try_next_method {
223 8     8   69 my $self = shift;
224 8         14 my $next_method = shift;
225              
226 8         17 my $class = ref $self;
227 8         15 my $result;
228             try {
229 8 100   8   326 if ( $self->validate ) {
230 3         15 $result = $self->$next_method(@_);
231             }
232             else {
233 5         37 my $errors = $self->_get_errors;
234 5         1897 croak("$class: Validation failed.\n$errors");
235             }
236             }
237             catch {
238 5     5   435 my $error = $_;
239 5         43 $self->error_reporting();
240 5 50       24 $self->add_result_error(uncaught => $error) if !defined $self->result_errors;
241 5 50       19 croak $error
242             if ref $error eq 'DBIx::Class::Result::Validation::VException';
243 5         65 croak(
244             DBIx::Class::Result::Validation::VException->new(
245             object => $self,
246             message => "$error"
247             )
248             );
249 8         78 };
250 3         191981 return $result;
251             }
252              
253             sub _get_errors {
254 5     5   6 my $self = shift;
255              
256 5         43 require Data::Dumper;
257 4     4   55226 no warnings 'once';
  4         7  
  4         1641  
258 5         22 local $Data::Dumper::Indent = 1;
259 5         13 local $Data::Dumper::Sortkeys = 1;
260 5         9 local $Data::Dumper::Terse = 1;
261 5         32 return Data::Dumper::Dumper( $self->{result_errors} );
262             }
263              
264             =head2 _erase_result_error
265              
266             this function is called to re-init result_errors before call validate function
267              
268             =cut
269              
270             sub _erase_result_error
271             {
272 8     8   12 my $self = shift;
273 8         41 $self->result_errors(undef);
274             }
275              
276              
277             =head1 VALIDATION METHOD
278              
279             set of function to validate fields
280              
281             =cut
282              
283             =head2 validate_enum function
284              
285             validation of the enum field, should return a validation error if the field is set and is not in the list of enum
286              
287             =cut
288              
289             sub validate_enum {
290 16     16 1 26 my ($self, $field) = @_;
291 3         160 $self->add_result_error( $field, $field ." must be set with one of the following value: " .join(", ", @{$self->result_source->columns_info->{$field}->{extra}->{list}}) )
292             if(
293             (!defined ($self->$field) && !defined($self->result_source->columns_info->{$field}->{default_value})
294             or
295 16 100 100     414 (defined ($self->$field) && !($self->$field ~~ @{ $self->result_source->columns_info->{$field}->{extra}->{list} }))
  12   100     752  
      66        
296             )
297             );
298             }
299              
300             =head2 validate_defined
301              
302             validation of field which must be defined, return error if the field is not defined
303              
304             =cut
305              
306             sub validate_defined {
307 24     24 1 30 my ($self, $field) = @_;
308              
309 24 100       637 $self->add_result_error( $field, "must be set" )
310             unless defined $self->$field;
311             }
312              
313             =head2 validate_not_empty
314              
315             validation of a field which can be null but can't be empty
316              
317             =cut
318              
319             sub validate_not_empty {
320 8     8 1 15 my ($self, $field) = @_;
321              
322 8 50 33     201 $self->add_result_error( $field, "can not be empty" )
323             if defined $self->$field && $self->$field eq '';
324             }
325              
326             =head2 validate_not_null_or_not_zero
327              
328             validation of a field which can be null and not equal to 0
329             this can be used for data_type integer
330              
331             =cut
332              
333             sub validate_not_null_or_not_zero {
334 8     8 1 16 my ($self, $field) = @_;
335              
336 8 100       202 $self->add_result_error( $field, "can not be null or equal to 0" )
337             if !$self->$field;
338             }
339              
340             1;
341             __END__
342              
343             =head1 SEE ALSO
344              
345             L<"DBIx::Class">
346              
347             =head1 AUTHOR
348              
349             Nicolas Oudard <nicolas@oudard.org>
350              
351             =head1 CONTRIBUTORS
352              
353              
354             =head1 LICENSE
355              
356             You may distribute this code under the same terms as Perl itself.