File Coverage

blib/lib/DBIx/Mint/Table.pm
Criterion Covered Total %
statement 155 161 96.2
branch 48 48 100.0
condition 20 34 58.8
subroutine 14 14 100.0
pod 7 7 100.0
total 244 264 92.4


line stmt bran cond sub pod time code
1             package DBIx::Mint::Table;
2              
3 12     12   27702 use DBIx::Mint;
  12         19  
  12         296  
4 12     12   42 use Carp;
  12         14  
  12         588  
5 12     12   43 use Moo::Role;
  12         11  
  12         67  
6              
7             has _name => (is => 'ro', default => sub { '_DEFAULT' });
8              
9             # Methods that insert data
10             sub create {
11 3     3 1 569 my $class = shift;
12 3         4 my $mint;
13 3 100 66     18 if (ref $_[0] && ref $_[0] eq 'DBIx::Mint') {
14 2         5 $mint = shift;
15             }
16 3         53 my $obj = $class->new(@_);
17 3         36 $obj->insert($mint);
18 3         7 return $obj;
19             }
20              
21             sub insert {
22             # Input:
23             # Case 1) a class name, a Mint object, any number of hash refs to insert
24             # Case 2) a class name, any number of hash refs to insert
25             # Case 3) a class name, key-value pairs
26             # Case 4) a blessed object and a Mint object
27             # Case 5) a blessed object
28              
29 10     10 1 3467 my $proto = shift;
30 10         16 my $class;
31             my $mint;
32 0         0 my @objects;
33 10 100       30 if (!ref $proto) {
34 6         9 $class = $proto;
35 6 100 100     48 if (ref $_[0] && ref $_[0] eq 'DBIx::Mint') {
    100          
36             # Case 1
37 2         4 $mint = shift;
38 2         5 @objects = @_;
39             }
40             elsif (ref $_[0]) {
41             # Case 2
42 2         10 $mint = DBIx::Mint->instance('_DEFAULT');
43 2         6 @objects = @_;
44             }
45             else {
46             # Case 3
47 2         7 $mint = DBIx::Mint->instance('_DEFAULT');
48 2         6 my %data = @_;
49 2         5 @objects = (\%data);
50             }
51             }
52             else {
53 4         7 $class = ref $proto;
54 4         6 @objects = ($proto);
55 4 100 66     44 if ($_[0] && ref $_[0] eq 'DBIx::Mint') {
56             # Case 4
57 2         4 $mint = shift;
58             }
59             else {
60             # Case 5
61 2         7 $mint = DBIx::Mint->instance('_DEFAULT');
62             }
63             }
64              
65 10   33     42 my $schema = $mint->schema->for_class( $class )
66             || croak "A schema definition for class $class is needed to use DBIx::Mint::Table";
67              
68             # Fields that do not go into the database
69 10         13 my %to_be_removed;
70 10         13 @to_be_removed{ @{ $schema->fields_not_in_db } } = (1) x @{ $schema->fields_not_in_db };
  10         28  
  10         35  
71              
72 10         16 my @fields = grep {!exists $to_be_removed{$_}} keys %{ $objects[0] };
  35         59  
  10         34  
73 10         21 my @quoted = map { $mint->dbh->quote_identifier( $_ ) } @fields;
  29         1615  
74              
75 10         629 my $sql = sprintf 'INSERT INTO %s (%s) VALUES (%s)',
76             $schema->table, join(', ', @quoted), join(', ', ('?') x @fields);
77              
78             my $sub = sub {
79 10     10   275 my $sth = $_->prepare($sql);
80 10         592 my @ids;
81 10         18 foreach my $obj (@objects) {
82             # Obtain values from the object
83 16         66 my @values = @$obj{ @fields };
84 16         172339 $sth->execute(@values);
85 16 100       164 if ($schema->auto_pk) {
86 15         159 my $id = $_->last_insert_id(undef, undef, $schema->table, undef);
87 15         88 $obj->{ $schema->pk->[0] } = $id;
88             }
89 16         25 push @ids, [ @$obj{ @{ $schema->pk } } ];
  16         142  
90             }
91             return @ids
92 10         65 };
  10         247  
93 10         47 my @ids = $mint->connector->run( fixup => $sub );
94 10 100       247 return wantarray ? @ids : $ids[0][0];
95             }
96              
97              
98             sub update {
99             # Input:
100             # Case 1) a class name, a Mint object, two hash refs
101             # Case 2) a class name, two hash refs
102             # Case 3) a blessed object
103              
104 11     11 1 3128 my $proto = shift;
105 11         16 my $class;
106             my $mint;
107 0         0 my $set;
108 0         0 my $where;
109 0         0 my $schema;
110 11 100       30 if (!ref $proto) {
111 4         6 $class = $proto;
112 4 100       9 if (@_ == 3) {
113             # Case 1
114 2         4 ($mint, $set, $where) = @_;
115 2 100       140 croak "DBIx::Mint::Table update: Expected the first argument to be a DBIx::Mint object "
116             . "(since the three-args version was used)"
117             unless ref $mint eq 'DBIx::Mint';
118             }
119             else {
120             # Case 2
121 2         4 ($set, $where) = @_;
122 2         8 $mint = DBIx::Mint->instance('_DEFAULT');
123             }
124 3   33     14 $schema = $mint->schema->for_class($class)
125             || croak "A schema definition for class $class is needed to use DBIx::Mint::Table";
126              
127 3 100 66     103 croak "DBIx::Mint::Table update: called with incorrect arguments"
128             unless ref $set && ref $where;
129             }
130             else {
131             # Case 3: Updating a blessed object
132 7         9 $class = ref $proto;
133 7         41 my %copy = %$proto;
134 7         11 $set = \%copy;
135              
136 7         29 $mint = DBIx::Mint->instance( $proto->_name );
137 7   33     25 $schema = $mint->schema->for_class($class)
138             || croak "A schema definition for class $class is needed to use DBIx::Mint::Table";
139              
140 7         9 my @pk = @{ $schema->pk };
  7         26  
141 7         15 my %where = map { $_ => $proto->$_ } @pk;
  7         28  
142 7         10 $where = \%where;
143              
144 7         8 delete $set->{$_} foreach @{ $schema->fields_not_in_db }, @pk;
  7         41  
145             }
146            
147             # Build the SQL
148 9         49 my ($sql, @bind) = $mint->abstract->update($schema->table, $set, $where);
149            
150             # Execute the SQL
151 9     9   2434 return $mint->connector->run( fixup => sub { $_->do($sql, undef, @bind) } );
  9         236  
152             }
153              
154             sub delete {
155             # Input:
156             # Case 1) a class name, a Mint object, a data hash ref
157             # Case 2) a class name, a data hash ref
158             # Case 3) a class name, a list of scalars (primary key values)
159             # Case 4) a blessed object
160            
161 5     5 1 1456 my $proto = shift;
162 5         7 my $class;
163             my $data;
164 0         0 my $mint;
165 5 100       13 if (!ref $proto) {
166 3         4 $class = $proto;
167 3 100       10 if (ref $_[0] eq 'DBIx::Mint') {
    100          
168             # Case 1
169 1         3 ($mint, $data) = @_;
170             }
171             elsif (ref $_[0]) {
172             # Case 2
173 1         2 $data = shift;
174 1         3 $mint = DBIx::Mint->instance('_DEFAULT');
175             }
176             else {
177             # Case 3
178 1         3 my %data = @_;
179 1         2 $data = \%data;
180 1         3 $mint = DBIx::Mint->instance('_DEFAULT');
181             }
182             }
183             else {
184             # Case 4
185 2         4 $class = ref $proto;
186 2         8 my %data = %$proto;
187 2         4 $data = \%data;
188 2   50     7 my $name = delete $data->{_name} || '_DEFAULT';
189 2         6 $mint = DBIx::Mint->instance($name);
190             }
191            
192 5   33     18 my $schema = $mint->schema->for_class($class)
193             || croak "A schema definition for class $class is needed to use DBIx::Mint::Table";
194              
195             # Build the SQL
196 5         27 my ($sql, @bind) = $mint->abstract->delete($schema->table, $data);
197 5         1250 my $conn = $mint->connector;
198 5     5   27 my $res = $conn->run( fixup => sub { $_->do($sql, undef, @bind) } );
  5         153  
199 5 100 66     46440 if (ref $proto && $res) {
200 2         8 %$proto = ();
201             }
202 5         20 return $res;
203             }
204              
205             # Returns a single, inflated object using its primary keys
206             sub find {
207 48     48 1 47323 my $class = shift;
208 48 100       295 croak "find must be called as a class method" if ref $class;
209            
210             # Input:
211             # Case 1) a Mint object, a data hash ref
212             # Case 2) a Mint object, a list of scalars (primary key values)
213             # Case 3) a data hash ref
214             # Case 4) a list of scalars (primary key values)
215 47         46 my $data;
216             my $mint;
217 0         0 my $schema;
218 47 100 100     170 if (ref $_[0] && ref $_[0] eq 'DBIx::Mint') {
219 6         8 $mint = shift;
220 6         30 $schema = $mint->schema->for_class($class);
221 6 100       18 if (ref $_[0]) {
222             # Case 1
223 1         3 $data = shift;
224             }
225             else {
226             # Case 2
227 5         5 my @pk = @{ $schema->pk };
  5         18  
228 5         6 my %data;
229 5         12 @data{@pk} = @_;
230 5         10 $data = \%data;
231             }
232             }
233             else {
234 41         161 $mint = DBIx::Mint->instance('_DEFAULT');
235 41         161 $schema = $mint->schema->for_class($class);
236 41 100       85 if (ref $_[0]) {
237             # Case 3
238 1         8 $data = shift;
239             }
240             else {
241             # Case 4
242 40         40 my @pk = @{ $schema->pk };
  40         129  
243 40         48 my %data;
244 40         103 @data{@pk} = @_;
245 40         70 $data = \%data;
246             }
247             }
248              
249 47         102 my $table = $schema->table;
250 47         251 my ($sql, @bind) = $mint->abstract->select($table, '*', $data);
251            
252             # Execute the SQL
253 47     47   8866 my $res = $mint->connector->run( fixup => sub { $_->selectall_arrayref($sql, {Slice => {}}, @bind) } );
  47         1998  
254 47 100       10380 return undef unless defined $res->[0];
255              
256 42         160 $res->[0]->{_name} = $mint->name;
257 42         116 my $obj = bless $res->[0], $class;
258 42         134 return $obj;
259             }
260              
261             sub find_or_create {
262 3     3 1 789 my $class = shift;
263 3         5 my $mint;
264 3 100       11 if (ref $_[0] eq 'DBIx::Mint') {
265 1         3 $mint = shift;
266             }
267             else {
268 2         6 $mint = DBIx::Mint->instance;
269             }
270 3         12 my $obj = $class->find($mint, @_);
271 3 100       12 $obj = $class->create($mint, @_) if ! defined $obj;
272 3         10 return $obj;
273             }
274              
275             sub result_set {
276 5     5 1 63240 my ($class, $instance) = @_;
277 5         10 my $mint;
278 5 100       14 if (ref $instance) {
279 3         4 $mint = $instance;
280             }
281             else {
282 2   50     10 $instance //= '_DEFAULT';
283 2         9 $mint = DBIx::Mint->instance($instance);
284             }
285            
286 5         27 my $schema = $mint->schema->for_class($class);
287 5 100       213 croak "result_set: The schema for $class is undefined" unless defined $schema;
288 4         85 return DBIx::Mint::ResultSet->new( table => $schema->table, instance => $mint->name );
289             }
290              
291             1;
292              
293             =pod
294              
295             =head1 NAME
296              
297             DBIx::Mint::Table - Role that maps a class to a table
298              
299             =head1 SYNOPSIS
300              
301             # In your class:
302            
303             package Bloodbowl::Coach;
304             use Moo;
305             with 'DBIx::Mint::Table';
306            
307             has 'id' => ( is => 'rwp', required => 1 );
308             has 'name' => ( is => 'ro', required => 1 );
309             ....
310            
311             # And in your schema:
312             $schema->add_class(
313             class => 'Bloodbowl::Coach',
314             table => 'coaches',
315             pk => 'id',
316             auto_pk => 1
317             );
318            
319             # Finally, in your application:
320             my $coach = Bloodbowl::Coach->find(3);
321             say $coach->name;
322            
323             $coach->name('Will E. Coyote');
324             $coach->update;
325            
326             my @ids = Bloodbowl::Coach->insert(
327             { name => 'Coach 1' },
328             { name => 'Coach 2' },
329             { name => 'Coach 3' }
330             );
331            
332             $coach->delete;
333            
334             my $coach = Bloodbowl::Coach->find_or_create(3);
335             say $coach->id;
336            
337             # The following two lines are equivalent:
338             my $rs = Bloodbowl::Coach->result_set;
339             my $rs = DBIx::Mint::ResultSet->new( table => 'coaches' );
340              
341             =head1 DESCRIPTION
342              
343             This role allows your class to interact with a database table. It allows for record modification (insert, update and delete records) as well as data fetching (find and find_or_create) and access to DBIx::Mint::ResultSet objects.
344              
345             Database modification methods can be called as instance or class methods. In the first case, they act only on the calling object. When called as class methods they allow for the modification of several records.
346              
347             Triggers can be added using the methods before, after, and around from L.
348              
349             The database modifying parts of the routines are run under DBIx::Connector's fixup mode, as they are so small that no side-effects are expected. If you use transactions, the connection will be checked only at the outermost block method call. See L for more information.
350              
351             =head1 METHODS
352              
353             =head2 create
354              
355             This methods is a convenience that calls new and insert to create a new object. The following two lines are equivalent:
356              
357             my $coach = Bloodbowl::Coach->create( name => 'Will E. Coyote');
358             my $coach = Bloodbowl::Coach->new( name => 'Will E. Coyote')->insert;
359              
360             Or, using a different database connection:
361              
362             my $mint = DBIx::Mint->instance('other');
363             my $coach = Bloodbowl::Coach->create( $mint, name => 'Will E. Coyote');
364              
365             =head2 insert
366              
367             When called as a class method, it takes a list of hash references and inserts them into the table which corresponds to the calling class. The hash references must have the same keys to benefit from a prepared statement holder. The list of fields is taken from the first record. If only one record is used, it can be simply a list of key-value pairs.
368              
369             When called as an instance method, it inserts the data contained within the object into the database.
370              
371             # Using the default DBIx::Mint object:
372            
373             Bloodbowl::Coach->insert( name => 'Bruce Wayne' );
374             Bloodbowl::Coach->insert(
375             { name => 'Will E. Coyote' },
376             { name => 'Clark Kent' },
377             { name => 'Peter Parker' });
378              
379             $batman->insert;
380              
381             Additionally, it can be given an alternative DBIx::Mint object to act on a connection other than the default one:
382              
383             # Using a given DBIx::Mint object:
384             Bloodbowl::Coach->insert( $mint,
385             { name => 'Will E. Coyote' },
386             { name => 'Clark Kent' },
387             { name => 'Peter Parker' });
388              
389             $batman->insert($mint);
390              
391             =head2 update
392              
393             When called as a class method it will act over the whole table. The first argument defines the change to update and the second, the conditions that the records must comply with to be updated:
394              
395             Bloodbowl::Coach->update( { email => 'unknown'}, { email => undef });
396            
397             When called as an instance method it updates only the record that corresponds to the calling object:
398              
399             $coach->name('Mr. Will E. Coyote');
400             $coach->update;
401              
402             To use a DBIx::Mint instance other than the default one:
403              
404             my $mint = DBIx::Mint->instance('database_2');
405             Bloodbowl::Coach->update( { email => 'unknown'}, { email => undef }, $mint);
406              
407             =head2 delete
408              
409             This method deletes information from the corresponding table. When called as a class method it acts on the whole table; when called as an instance method it deletes the calling object from the database:
410              
411             Bloodbowl::Coach->delete({ email => undef });
412             Bloodbowl::Team->delete( name => 'Tinieblas');
413             $coach->delete;
414              
415             The statements above delete information using the default database connection. If a named DBIx::Mint instance is needed:
416              
417             my $mint = DBIx::Mint->instance('database_2');
418             Bloodbowl::Coach->delete({ email => undef }, $mint);
419              
420             =head2 find
421              
422             Fetches a single record from the database and blesses it into the calling class. It can be called as a class record only. It can as take as input either the values of the primary keys for the corresponding table or a hash reference with criteria to fetch a single record:
423              
424             my $coach_3 = Bloodbowl::Coach->find(3);
425             my $coach_3 = Bloodbowl::Coach->find({ name => 'coach 3'});
426              
427             To use a named DBIx::Mint instance:
428              
429             my $mint = DBIx::Mint->instance('database_2');
430             my $coach_3 = Bloodbowl::Coach->find({ id => 3 }, $mint);
431              
432             =head2 find_or_create
433              
434             This method will call 'create' if the requested record is not found in the database.
435              
436             my $obj = Bloodbowl::Coach->find_or_create(
437             name => 'Bob', email => 'bob@coaches.net'
438             );
439             my $obj = Bloodbowl::Coach->find_or_create(
440             $mint, { name => 'Bob', email => 'bob@coaches.net' }
441             );
442              
443             =head2 result_set
444              
445             Get a L object for the table associated with this class. Optionally, use a named Mint object:
446              
447             my $rs = Bloodbowl::Team->result_set; # With default db
448             my $rs = Bloodbowl::Team->result_set('other'); # With other db
449              
450             =head1 SEE ALSO
451              
452             This module is part of L.
453              
454             =head1 AUTHOR
455              
456             Julio Fraire,
457              
458             =head1 LICENCE AND COPYRIGHT
459              
460             Copyright (c) 2013, Julio Fraire. All rights reserved.
461              
462             =head1 LICENSE
463              
464             This module is free software; you can redistribute it and/or
465             modify it under the same terms as Perl itself. See L.
466              
467             This program is distributed in the hope that it will be useful,
468             but WITHOUT ANY WARRANTY; without even the implied warranty of
469             MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
470              
471             =cut
472