File Coverage

blib/lib/DBIx/Class/Numeric.pm
Criterion Covered Total %
statement 79 79 100.0
branch 23 26 88.4
condition 10 13 76.9
subroutine 30 36 83.3
pod 1 3 33.3
total 143 157 91.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             DBIx::Class::Numeric - helper methods for numeric columns
4              
5             =head1 SYNOPSIS
6              
7             package MyApp::Schema::SomeTable;
8              
9             use base 'DBIx::Class';
10              
11             # Load the Numeric component
12             # Don't forget to load it *before* Core!
13             __PACKAGE__->load_components(qw/Numeric Core/);
14              
15             # Add columns as per usual
16             # Note, any numeric columns still need to appear here
17             __PACKAGE__->add_columns(
18             qw/primary_id some_string height width restricted bounded lower_bound upper_bound/
19             );
20              
21             # Define 'simple' numeric cols, these will have some extra accessors & mutators
22             # created
23             __PACKAGE__->numeric_columns(qw/height width/);
24              
25             # Define min and max values for a column
26             __PACKAGE__->numeric_columns(restricted => {min_value => 10, max_value => 30});
27              
28             # Define a column that's bound by the value of other columns
29             __PACKAGE__->numeric_columns(bounded => {lower_bound_col => 'lower_bound', upper_bound_col => 'upper_bound'});
30              
31             # ... meanwhile, after reading a record from the DB
32              
33             $row->increase_height(5); # Add 5 to height
34              
35             $row->decrease_width(9); # Subtract 9 from width
36              
37             $row->adjust_height(-5); # Subtract 5 from height
38             # (can be positive or negative, as can increase/decrease...
39             # adjust is just a clearer name...)
40              
41             $row->increment_height; # Increment height
42              
43             $row->decrement_width; # Decrement width
44              
45             $row->restricted(40); # restricted col will be set to '30' since that's the max
46              
47             $row->lower_bound(5);
48             $row->bounded(10); # bounded will be set to '5', since its lower bound was set to 5
49              
50             =head1 DESCRIPTION
51              
52             A DBIx::Class component that adds some extra accessors / mutators to any numeric columns
53             you define. Additionally, columns can have max and min values defined, or be bound to the
54             values of other columns in the table.
55              
56             This is useful if you have a lot of numeric columns to work with, and you want a bit of
57             syntactic sugar for adding / subtracting from the column, or you need upper/lower
58             bounds.
59              
60             =head1 METHODS
61              
62             =head2 numeric_columns(@cols)
63              
64             Call this method as you would add_columns(), and pass it a list of columns that are numeric. Note,
65             you need to pass the column names to add_columns() *and* numeric_columns().
66              
67             Any columns in this list will have extra accessors / mutators defined (see below).
68              
69             If the item in the list after a column name is a hashref, the hashref will define the arguments for
70             that numeric column. (If the next item's not a hashref, it's assumed to be the next column - you can
71             mix and match columns with and without arguments in the same call to numeric_colums().
72              
73             The valid keys in the argument hashref are:
74              
75             =over 4
76              
77             =item min_value / max_value
78              
79             These two keys define the minimum and/or maximum value of the column. If you attempt to set the column
80             to a value outside this range, it will be set to that min or max value accordingly.
81              
82             =item lower_bound_col / upper_bound_col
83              
84             If either of these are set to the name of a column in the same table, the numeric column will be
85             restricted in the same way as a min or max value, except the min/max value will be defined by the
86             value of the column specified.
87              
88             If the value of the lower or upper bound column changes, the bounded column won't be affected, until
89             its value is set. Eg. if your bounded column is currently 5, and you set it's lower_bound_col to
90             8 the bounded col won't change, even though it's below the minimum value. If you were to (eg) increment
91             the column, it would then be set to 8.
92              
93             =back
94              
95             =over
96              
97             =item WARNING
98              
99             Little (if any) validation is done on the list of cols passed to numeric_columns(). You could easily
100             pass it non-existant column names, etc. (This may be improved in a later release).
101              
102             In particular, no check is made to see if you are using incompatible combinations of min/max_value
103             and lower/uppper_bound_col (e.g. both a min_value and a lower_bound_col). Doing this is unsupported,
104             and may be prevented in the future (even thought it might 'kind of' work at the moment). You're free
105             to use compatible combinations, though, eg. a min_value and an upper_bound_col.
106              
107             =back
108              
109             =head2 increase_*, decrease_*, increment_*, decrement_*, adjust_*
110              
111             These 5 methods are added to your schema class for each column you pass to numeric_cols(). E.g. if
112             you have a numeric column called 'foo', you will automagically get methods called increment_foo(),
113             decrement_foo(), etc. They are fairly self-explanatory, with the possible exception of 'adjust_*'.
114             You can pass it either a positive or negative value to adjust the value of the column accordingly.
115              
116             =head1 AUTHOR
117              
118             Sam Crawley (Mutant) - mutant dot nz at gmail dot com
119              
120             =head1 LICENSE
121              
122             You may distribute this code under the same terms as Perl itself.
123              
124             =cut
125              
126             package DBIx::Class::Numeric;
127              
128 3     3   1421 use strict;
  3         7  
  3         70  
129 3     3   13 use warnings;
  3         6  
  3         117  
130              
131             our $VERSION = '0.004';
132              
133 3     3   13 use base qw(DBIx::Class Class::Accessor::Grouped);
  3         11  
  3         1096  
134              
135             __PACKAGE__->mk_group_accessors('inherited', '_numeric_col_def');
136              
137 3     3   118179 use Sub::Name ();
  3         9  
  3         492  
138              
139             sub numeric_columns {
140 7     7 1 169436 my $self = shift;
141 7         20 my @cols = @_;
142              
143 7         20 my $count = 0;
144 7         11 my %def;
145              
146 7         17 foreach my $col (@cols) {
147 24 100       63 next if ref $col eq 'HASH';
148              
149 14         23 my $args = {};
150 14 100       37 if (ref $cols[$count+1] eq 'HASH') {
151 10         22 $args = $cols[$count+1];
152             }
153 14         29 $def{$col} = $args;
154              
155             my %methods = (
156             adjust => sub {
157 1     1   396 _adjust($col, @_);
        1      
        1      
        0      
        0      
        0      
        0      
        0      
        0      
158             },
159             increase => sub {
160 1     1   359 _increase($col, @_);
        1      
        1      
161             },
162             decrease => sub {
163 1     1   490 _decrease($col, @_);
        1      
        1      
164             },
165             increment => sub {
166 1     1   443 _increment($col, @_);
        1      
        1      
167             },
168             decrement => sub {
169 1     1   435 _decrement($col, @_);
        1      
        1      
170             }
171 14         142 );
172              
173 14         53 while (my ($method_name, $subref) = each %methods) {
174 3     3   20 no strict 'refs';
  3         5  
  3         87  
175 3     3   15 no warnings 'redefine';
  3         6  
  3         1222  
176              
177 70         155 my $name = join '::', $self, "${method_name}_$col";
178 70         445 *$name = Sub::Name::subname($name, $subref);
179             }
180             }
181             continue {
182 24         46 $count++;
183             }
184              
185 7         158 my $existing = $self->_numeric_col_def;
186 7 100       624 %def = (%$existing, %def) if $existing;
187              
188 7         123 $self->_numeric_col_def(\%def);
189             }
190              
191             sub _increase {
192 5     5   11 my $col = shift;
193 5         7 my $self = shift;
194 5         8 my $increase = shift;
195              
196 5   50     13 $self->set_column($col, ($self->get_column($col) || 0) + ($increase || 0));
      50        
197             }
198              
199             sub _decrease {
200 2     2   6 _increase($_[0], $_[1], -$_[2]);
201             }
202              
203             sub _increment {
204 1     1   4 _increase($_[0], $_[1], 1);
205             }
206              
207             sub _decrement {
208 1     1   3 _decrease($_[0], $_[1], 1);
209             }
210              
211             sub _adjust {
212 1     1   4 _increase(@_);
213             }
214              
215             sub set_column {
216 12     12 0 24939 my $self = shift;
217 12         34 my $column = shift;
218 12         26 my $new_val = shift;
219              
220 12         34 $new_val = $self->_restrict_numeric($column, $new_val);
221              
222 12         71 return $self->next::method( $column, $new_val, @_ );
223             }
224              
225             sub insert {
226 2     2 0 781540 my $self = shift;
227              
228 2 50       71 if (my $def = $self->_numeric_col_def) {
229 2         110 foreach my $column (keys %$def) {
230 6 100 66     1016 next unless $def->{$column} && %{ $def->{$column} };
  6         53  
231              
232 5         46 my $val = $self->get_column($column);
233              
234 5 50       76 next unless defined $val;
235              
236 5         31 $self->set_column($column, $self->_restrict_numeric($column, $val));
237             }
238             }
239              
240 2         86 return $self->next::method( @_ );
241             }
242              
243             sub _restrict_numeric {
244 17     17   42 my $self = shift;
245 17         36 my $column = shift;
246 17         34 my $new_val = shift;
247              
248 17         433 my $def = $self->_numeric_col_def;
249              
250 17 50       560 if ($def) {
251 17 100 100     94 if (defined $def->{$column}{min_value} && $new_val < $def->{$column}{min_value}) {
252 1         4 $new_val = $def->{$column}{min_value};
253             }
254 17 100 100     81 if (defined $def->{$column}{max_value} && $new_val > $def->{$column}{max_value}) {
255 3         11 $new_val = $def->{$column}{max_value};
256             }
257 17 100       53 if (defined $def->{$column}{upper_bound_col}) {
258 6         25 my $max_val = $self->get_column($def->{$column}{upper_bound_col});
259 6 100       70 $new_val = $max_val if $new_val > $max_val;
260             }
261 17 100       73 if (defined $def->{$column}{lower_bound_col}) {
262 6         21 my $min_val = $self->get_column($def->{$column}{lower_bound_col});
263 6 100       73 $new_val = $min_val if $new_val < $min_val;
264             }
265             }
266              
267 17         65 return $new_val;
268             }
269              
270              
271             1;