File Coverage

blib/lib/DBIx/Simple/Class.pm
Criterion Covered Total %
statement 226 239 94.5
branch 98 202 48.5
condition 52 123 42.2
subroutine 57 58 98.2
pod 24 31 77.4
total 457 653 69.9


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