File Coverage

blib/lib/DBIx/Simple/Class.pm
Criterion Covered Total %
statement 235 241 97.5
branch 100 202 49.5
condition 52 123 42.2
subroutine 61 61 100.0
pod 24 31 77.4
total 472 658 71.7


line stmt bran cond sub pod time code
1             package DBIx::Simple::Class;
2 3     3   32973 use 5.010001;
  3         14  
  3         127  
3 3     3   17 use strict;
  3         6  
  3         84  
4 3     3   22 use warnings;
  3         11  
  3         97  
5 3     3   25 use Carp;
  3         4  
  3         240  
6 3     3   11168 use Params::Check;
  3         39188  
  3         297  
7 3     3   3834 use DBIx::Simple;
  3         95486  
  3         576  
8              
9             our $VERSION = '1.008';
10              
11              
12             #CONSTANTS
13              
14             #defauld debug mode
15             my $DEBUG = 0;
16 24 100   24 1 1878 sub DEBUG { defined $_[1] ? ($DEBUG = $_[1]) : $DEBUG }
17              
18             #abstract tablename
19             sub TABLE {
20 1     1 1 192 croak("You must define a table-name for your class: sub TABLE {'tablename'}!");
21             }
22              
23             #abstract table columns
24             sub COLUMNS {
25 2     2 1 345 croak("You must define fields for your class: sub COLUMNS {['id','name','etc']}!");
26             }
27              
28             #Used to validate params to field-setters
29             #Passed to Params::Check::check()
30             sub CHECKS {
31 1     1 1 138 croak(
32             "You must define your CHECKS subroutine that returns your private \$_CHECKS HASHREF!"
33             );
34             }
35              
36             sub is_base_class {
37 3     3   1137 no strict 'refs';
  3         9  
  3         16199  
38 24     24 1 47 return scalar grep { __PACKAGE__ eq $_ } @{"$_[0]\::ISA"};
  23         185  
  24         117  
39             }
40              
41             #default where
42 2     2 1 11 sub WHERE { {} }
43              
44             #default primary key
45 27     27 1 172 sub PRIMARY_KEY {'id'}
46              
47             #no default aliases
48 68     68 1 307 sub ALIASES { {} }
49              
50             #should we quote identifiers for a class or not?
51             sub QUOTE_IDENTIFIERS {
52 14     14 1 29 my $class = shift;
53 14         25 state $QUOTE_IDENTIFIERS = {};
54 14   100     120 return $QUOTE_IDENTIFIERS->{$class} //= shift || '';
      100        
55             }
56              
57             #Used to store unquoted identifirers as they were before quoting
58             #See BUILD()
59             sub _UNQUOTED {
60 67   66 67   2859 my $class = ref $_[0] || $_[0]; #class
61 67         83 state $UNQUOTED = {};
62 67   100     333 return $UNQUOTED->{$class} //= {};
63             }
64              
65             #for outside modification during tests
66             my $_attributes_made = {};
67 3     3   745 sub _attributes_made {$_attributes_made}
68              
69             #stored generated SQL strings
70             my $SQL_CACHE = {};
71 2     2   107 sub _SQL_CACHE {$SQL_CACHE}
72              
73             my $SQL = {};
74             $SQL = {
75             SELECT => sub {
76              
77             #my $class = shift;
78             return $SQL_CACHE->{$_[0]}{SELECT} ||= do {
79             my $where = $_[0]->WHERE;
80             my $dbh = $_[0]->dbix->dbh;
81             'SELECT '
82             . join(',', @{$_[0]->COLUMNS})
83             . ' FROM '
84             . $_[0]->TABLE
85             . (
86             (keys %$where)
87             ? ' WHERE '
88             . join(
89             ' AND ', map { "$_=" . $dbh->quote($where->{$_}) }
90             keys %$where
91             )
92             : ''
93             );
94             }
95             },
96             INSERT => sub {
97             my $class = $_[0];
98              
99             #cache this query and return it
100             return $SQL_CACHE->{$class}{INSERT} ||= do {
101             my ($pk, $table, @columns) =
102             ($class->PRIMARY_KEY, $class->TABLE, @{$class->COLUMNS});
103              
104             #return of the do
105             "INSERT INTO $table ("
106             . join(',', @columns)
107             . ') VALUES('
108             . join(',', map {'?'} @columns) . ')';
109             };
110             },
111             UPDATE => sub {
112             my $class = $_[0];
113              
114             #cache this query and return it
115             return $SQL_CACHE->{$class}{UPDATE} ||= do {
116             my $pk = $class->PRIMARY_KEY;
117              
118             #do we always update all columns?!?! Yes, if we always retreive all columns.
119             my $SET = join(', ', map {qq($/$_=?)} @{$class->COLUMNS});
120             'UPDATE ' . $class->TABLE . " SET $SET WHERE $pk=%s";
121             }
122             },
123             SELECT_BY_PK => sub {
124              
125             #my $class = $_[0];
126              
127             #cache this query and return it
128             return $SQL_CACHE->{$_[0]}{SELECT_BY_PK} ||= do {
129             'SELECT '
130             . join(',', @{$_[0]->COLUMNS})
131             . ' FROM '
132             . $_[0]->TABLE
133             . ' WHERE '
134             . $_[0]->PRIMARY_KEY . '=?';
135             };
136             },
137              
138             _LIMIT => sub {
139              
140             #works for MySQL, SQLite, PostgreSQL
141             #TODO:See SQL::Abstract::Limit for other implementations
142             #and implement it using this technique.
143             " LIMIT $_[1]" . ($_[2] ? " OFFSET $_[2] " : '');
144             },
145             };
146              
147             # generate(d) limit clause
148             sub SQL_LIMIT {
149 2     2 1 1486 my $_LIMIT = $SQL->{_LIMIT};
150 2         9 return $_LIMIT->(@_);
151             }
152              
153             sub SQL {
154 25     25 1 3570 my ($class, $args) = _get_obj_args(@_); #class
155 25 100       222 croak('This is a class method. Do not use as object method.') if ref $class;
156              
157 24 100       88 if (ref $args) { #adding new SQL strings($k=>$v pairs)
    50          
158 5 100       8 return $SQL->{$class} = {%{$SQL->{$class} || $SQL}, %$args};
  5         80  
159             }
160              
161             #a key
162             elsif ($args) {
163              
164             #do not return hidden keys
165 19 100       250 croak("Named query '$args' can not be used directly") if $args =~ /^_+/x;
166              
167             #allow subclasses to override parent sqls and cache produced SQL
168 18   33     313 my $_SQL =
169             $SQL_CACHE->{$class}{$args}
170             || $SQL->{$class}{$args}
171             || $SQL->{$args}
172             || $args;
173 18 100       46 if (ref $_SQL) {
174 13         44 return $_SQL->(@_);
175             }
176             else {
177 5         100 return $_SQL;
178             }
179             }
180              
181             #they want all
182 0         0 return $SQL;
183             }
184              
185              
186             #ATTRIBUTES
187              
188             #copy/paste/override this method in your base schema classes
189             #if you want more instances per application
190             sub dbix {
191              
192             # Singleton DBIx::Simple instance
193 100     100 1 8193 state $DBIx;
194 100   66     987 return ($_[1] ? ($DBIx = $_[1]) : $DBIx)
195             || croak('DBIx::Simple is not instantiated. Please first do '
196             . $_[0]
197             . '->dbix(DBIx::Simple->connect($DSN,$u,$p,{...})');
198             }
199              
200 67     67 1 229 sub dbh { $_[0]->dbix->dbh }
201              
202             #METHODS
203              
204             sub new {
205 18     18 1 9615 my ($class, $fields) = _get_obj_args(@_);
206 18         43 local $Params::Check::WARNINGS_FATAL = 1;
207 18         38 local $Params::Check::CALLER_DEPTH = $Params::Check::CALLER_DEPTH + 1;
208              
209 18   66     131 $fields = Params::Check::check($class->CHECKS, $fields)
210             || croak(Params::Check::last_error());
211 16 100       1157 $class->BUILD()
212             unless $_attributes_made->{$class};
213 15         157 return bless {data => $fields}, $class;
214             }
215              
216             sub new_from_dbix_simple {
217 23 100   23 1 2954 if (wantarray) {
218 16         659 return (map { bless {data => $_, new_from_dbix_simple => 1}, $_[0]; }
  5         61  
219 5         7 @{$_[1]->{st}->{sth}->fetchall_arrayref({})});
220             }
221 18 50       829 return bless {
222              
223             #$_[1]->hash
224             data =>
225             $_[1]->{st}->{sth}->fetchrow_hashref($_[1]->{lc_columns} ? 'NAME_lc' : 'NAME'),
226             new_from_dbix_simple => 1
227             },
228             $_[0];
229             }
230              
231             sub select {
232 4     4 1 49 my ($class, $where) = _get_obj_args(@_);
233 4 100       25 $_attributes_made->{$class} || $class->BUILD();
234 3         18 $class->new_from_dbix_simple(
235 3         12 $class->dbix->select($class->TABLE, $class->COLUMNS, {%{$class->WHERE}, %$where}));
236             }
237              
238             sub query {
239 9     9 1 692 my $class = shift;
240 9 100       44 $_attributes_made->{$class} || $class->BUILD();
241 8         16 local $Carp::CarpLevel = $Carp::CarpLevel + 1;
242 8 100       28 return $class->dbix->query(@_) if $class->is_base_class;
243 7         23 $class->new_from_dbix_simple($class->dbix->query(@_));
244             }
245              
246             sub select_by_pk {
247 8     8 1 1246 my ($class, $pk) = @_;
248 8 100       36 $_attributes_made->{$class} || $class->BUILD();
249 7   66     22 return $class->new_from_dbix_simple(
250             $class->dbix->query(
251             $SQL_CACHE->{$class}{SELECT_BY_PK} || $class->SQL('SELECT_BY_PK'), $pk
252             )
253             );
254             }
255              
256             {
257 3     3   98 no warnings qw(once);
  3         5  
  3         5439  
258             *find = \&select_by_pk;
259             }
260              
261             sub BUILD {
262 16     16 1 703 my $class = shift;
263              
264             #TODO: Make DEBUG swichable per class
265             $class->dbh->{Callbacks}{prepare} = sub {
266 23 50   23   2006 $DEBUG || return;
267 0         0 my ($dbh, $query, $attrs) = @_;
268 0         0 my ($package, $filename, $line, $subroutine) = caller(2);
269 0         0 carp("SQL from $subroutine in $filename:$line :\n$query\n");
270 0         0 return;
271 16         161 };
272             #
273 16 100       458 if ($class->is_base_class) {
274 3 100       170 carp "Nothing more to build. This is the base class: $class" if $DEBUG;
275 3         435 return;
276             }
277 13 100       221 (!ref $class)
278             || croak("Call this method as $class->BUILD()");
279 12         155 $class->_UNQUOTED->{TABLE} = $class->TABLE;
280 12         25 $class->_UNQUOTED->{WHERE} = {%{$class->WHERE}}; #copy
  12         121  
281 12         32 $class->_UNQUOTED->{COLUMNS} = [@{$class->COLUMNS}]; #copy
  12         127  
282              
283 12         37 my $code = '';
284 12         18 foreach (@{$class->_UNQUOTED->{COLUMNS}}) {
  12         26  
285              
286 43   66     132 my $alias = $class->ALIASES->{$_} || $_;
287 43 100       10000 croak("You can not use '$alias' as a column name since it is already defined in "
288             . __PACKAGE__
289             . '. Please define an \'alias\' for the column to be used as method.')
290             if __PACKAGE__->can($alias);
291 42 100       334 next if $class->can($alias); #careful: no redefine
292 24 100       75 $code = "package $class; use strict;$/use warnings;$/use utf8;$/" unless $code;
293 24         158 $code .= <<"SUB";
294             sub $alias {
295             my (\$s,\$v) = \@_;
296             if(defined \$v){ #setting value
297             #Not using Params::Check
298             my \$allow = (\$s->CHECKS->{qq{$_}}?\$s->CHECKS->{qq{$_}}{allow}:'')||'';
299             if(ref \$allow eq 'CODE'){
300             \$s->{data}{qq{$_}} = \$allow->(\$v) ? \$v : Carp::croak("$_ is of invalid type");
301             }
302             elsif(ref \$allow eq 'Regexp'){
303             \$s->{data}{qq{$_}} =
304             \$v =~ \$allow ? \$v : Carp::croak("$_ is of invalid type");
305             }
306             elsif(\$allow && !ref \$allow){
307             \$s->{data}{qq{$_}} =
308             \$v eq \$allow ? \$v : Carp::croak("$_ is of invalid type");
309             }
310             else{
311             \$s->{data}{qq{$_}} = \$v;
312             }
313             #\$s->_check(qq{$_}=>\$v);#Using Params::Check
314             #make it chainable
315             return \$s;
316             }
317             #getting value
318             return \$s->{data}{qq{$_}} //= \$s->CHECKS->{qq{$_}}{default}; #getting value
319             }
320              
321             SUB
322              
323             }
324              
325 11         44 my $dbh = $class->dbh;
326 11 100       599 if ($class->QUOTE_IDENTIFIERS) {
327 4         86 $code
328             .= 'no warnings qw"redefine";'
329             . "sub $class\::TABLE {'"
330             . $dbh->quote_identifier($class->TABLE) . "'}";
331 4         117 my %where = %{$class->WHERE};
  4         83  
332 4         15 $code .= "sub $class\::WHERE {{";
333 4         13 for (keys %where) {
334 4         22 $code
335             .= 'qq{'
336             . $dbh->quote_identifier($_)
337             . '}=>qq{'
338             . $dbh->quote($where{$_}) . '}, '
339             . $/;
340             }
341 4         148 $code .= '}}#end WHERE' . $/;
342 4         7 my @columns = @{$class->COLUMNS};
  4         83  
343 4         12 $code .= "sub $class\::COLUMNS {[";
344 4         9 for (@columns) {
345 12         194 $code .= 'qq{' . $dbh->quote_identifier($_) . '},';
346             }
347 4         86 $code .= ']}#end COLUMNS' . $/;
348             } #if ($class->QUOTE_IDENTIFIERS)
349 11         89 $code .= "$/1;";
350              
351             #I know what I am doing. I think so...
352 11 100 0 3 0 909 unless (eval $code) { ##no critic (BuiltinFunctions::ProhibitStringyEval)
  3 0 33 26 0 29  
  1 0 33 7 0 3  
  1 100 0 3 0 40  
  3 0 66 13 0 17  
  9 50 33 11 0 666  
  3 0 0 1 0 39  
  3 50 33 1   13  
  1 50 0 1   2  
  1 0 0 1   44  
  1 100 33 1   11  
  1 0 33 1   4  
  1 100 33 1   43  
  1 0 66 1   7  
  1 50 0 1   2  
  3 50 0 1   71  
  26 0 66 1   11374  
  26 100 0 1   74  
  2 0 0 1   24  
  2 50 33 1   23  
  1 0 33 1   2  
  1 50 0 1   55  
  1 50 66     6  
  2 0 33     7  
  2 100 0     44  
  25 0 33     267  
  7 0       15  
  7 0       27  
  1 0       6  
  1 0       2  
  1 0       29  
  1 50       6  
  1 0       2  
  1 0       30  
  1 0       4  
  7 50       53  
  3 50       8  
  3 50       978  
  1 100       2  
  1 0       117  
  1 0       9  
  1 0       2  
  1 0       47  
  1 0       6  
  1 0       2  
  3 50       380  
  13 0       2931  
  13 0       41  
  5 0       27  
  5 0       28  
  1 0       3  
  5 0       13726  
  1 50       5  
  1 0       2  
  2 100       41  
  9 0       62  
  11 50       7169  
  11 50       38  
  4 0       33  
  4 100       19  
  4 100       40  
  1 0       5  
  1 0       1  
  1 50       23  
  2 0       10  
  8 0       44  
  1 100       6  
353 4         1548 croak($class . " compiler error: $/$code$/$@$/");
354             }
355 7 100       55 if ($class->DEBUG) {
356 1         140 carp($class . " generated accessors: $/$code$/$@$/");
357             }
358              
359             #make sure we die loudly
360 7         582 $dbh->{RaiseError} = 1;
361 7         34 return $_attributes_made->{$class} = 1;
362             }
363              
364              
365             #conveninece for getting key/vaule arguments
366             sub _get_args {
367 9 0   9   49 return ref($_[0]) ? $_[0] : (@_ % 2) ? $_[0] : {@_};
    0          
368             }
369 154 100   154   990 sub _get_obj_args { return (shift, ref($_[0]) ? $_[0] : (@_ % 2) ? $_[0] : {@_}); }
    100          
370              
371             sub _check {
372 8     8   44 my ($self, $key, $value) = @_;
373 10         745 local $Params::Check::WARNINGS_FATAL = 1;
374 10         26 local $Params::Check::CALLER_DEPTH = $Params::Check::CALLER_DEPTH + 1;
375              
376 4   100     39 my $args_out =
377             Params::Check::check({$key => $self->CHECKS->{$key} || {}}, {$key => $value});
378 3         18 return $args_out->{$key};
379             }
380              
381             #fieldvalues HASHREF
382             sub data {
383 65     74 1 1581 my ($self, $args) = _get_obj_args(@_);
384 65 100 100     388 if (ref $args && keys %$args) {
    100          
385 4         14 for my $field (keys %$args) {
386 8   66     36 my $alias = $self->ALIASES->{$field} || $field;
387 8 100       31 unless (grep { $field eq $_ } @{$self->_UNQUOTED->{COLUMNS}}) {
  22         390  
  17         1330  
388 13 100       251 Carp::cluck(
389             "There is not such field $field in table " . $self->TABLE . '! Skipping...')
390             if $DEBUG;
391 5         3093 next;
392             }
393              
394             #we may have getters/setters written by the author of the subclass
395             # so call each setter separately
396 8         154 $self->$alias($args->{$field});
397             }
398             }
399              
400             #a key (!ref $args)
401             elsif (!ref $args) {
402 50   66     140 my $alias = $self->ALIASES->{$args} || $args;
403 46         1489 return $self->$alias;
404             }
405              
406             #they want all that we touched in $self->{data}
407 19         124 return $self->{data};
408             }
409              
410             sub save {
411 13     25 1 1014 my ($self, $data) = _get_obj_args(@_);
412              
413             #allow data to be passed directly and overwrite current data
414 15 100       60 if (keys %$data) { $self->data($data); }
  9         816  
415 16         34 local $Carp::MaxArgLen = 0;
416 16 100       76 if (!$self->{new_from_dbix_simple}) {
417 12         61 return $self->{new_from_dbix_simple} = $self->insert();
418             }
419             else {
420 3         34 return $self->update();
421             }
422 0         0 return;
423             }
424              
425             sub update {
426 6     8 1 20 my ($self) = @_;
427 5         19 my $pk = $self->PRIMARY_KEY;
428 5 100       185 $self->{data}{$pk} || croak('Please define primary key column (\$self->$pk(?))!');
429 5         23 my $dbh = $self->dbh;
430 6   66     45 $self->{SQL_UPDATE} ||= do {
431 22         246 my $SET =
432 18         1358 join(', ', map { $dbh->quote_identifier($_) . '=? ' } keys %{$self->{data}});
  7         30  
433 7         161 'UPDATE ' . $self->TABLE . " SET $SET WHERE $pk=?";
434             };
435 7         876 return $dbh->prepare($self->{SQL_UPDATE})
436 4         33 ->execute(values %{$self->{data}}, $self->{data}{$pk});
437             }
438              
439             sub insert {
440 13     28 1 25 my ($self) = @_;
441 13         63 my ($pk, $class) = ($self->PRIMARY_KEY, ref $self);
442              
443 54         915 $self->dbh->prepare_cached($SQL_CACHE->{$class}{INSERT} || $class->SQL('INSERT'))
444             ->execute(
445             map {
446              
447             #set expected defaults
448 23         1235 $self->data($_)
449 16   66     62 } @{$class->_UNQUOTED->{COLUMNS}}
450             );
451              
452             #user set the primary key already
453 23   66     127 return $self->{data}{$pk}
454             ||= $self->dbh->last_insert_id(undef, undef, $self->TABLE, $pk);
455              
456             }
457              
458             sub create {
459 3     11 1 27 my $self = shift->new(@_);
460 3         17 $self->insert;
461 1         39 return $self;
462             }
463              
464             1;
465              
466             __END__