File Coverage

blib/lib/Catalyst/Plugin/CDBI/Transaction.pm
Criterion Covered Total %
statement 6 34 17.6
branch 0 14 0.0
condition 0 11 0.0
subroutine 2 4 50.0
pod 1 1 100.0
total 9 64 14.0


line stmt bran cond sub pod time code
1             package Catalyst::Plugin::CDBI::Transaction;
2              
3 2     2   10124 use strict;
  2         4  
  2         87  
4 2     2   11 use base 'Class::Data::Inheritable';
  2         4  
  2         1933  
5              
6             __PACKAGE__->mk_classdata('cdbi_models');
7              
8             our $VERSION = '0.03';
9              
10             =head1 NAME
11              
12             Catalyst::Plugin::CDBI::Transaction - Simple transaction handling for Catalyst
13             apps that use Class::DBI models
14              
15             =head1 SYNOPSIS
16              
17             # ... in your application class:
18             package MyApp;
19              
20             use Catalyst qw/CDBI::Transaction/;
21              
22             # ... in a controller class:
23             package MyApp::Controller::Foo;
24              
25             sub create_in_transaction : Local {
26             my ( $self, $c ) = @_;
27              
28             $c->transaction( sub {
29             MyApp::M::CDBI::Sometable->create({
30             field_1 => 'value 1',
31             field_2 => 'value 2',
32             });
33             } )
34             or $c->log->error("Transaction failed: " . $c->error->[-1]);
35             }
36              
37             =head1 DESCRIPTION
38              
39             Handle L<Catalyst::Model::CDBI> database operations inside a transaction
40              
41             =head1 METHODS
42              
43             =over 4
44              
45             =item $c->transaction
46              
47             =item $c->trans
48              
49             =item $c->atomic
50            
51             Pass a coderef to $c->transaction, and it will be executed atomically (inside
52             of a transaction):
53              
54             $c->transaction( $ref_to_anonymous_subroutine );
55              
56             Returns true and commits the transaction if all the statements inside the
57             coderef were successful. Upon failure, adds the error message to $c->error,
58             returns false and automatically rolls back the transaction.
59              
60             This method was adapted from the idiom presented in the L<Class::DBI>
61             documentation, under the TRANSACTIONS heading.
62              
63             =cut
64              
65             *trans = \&transaction;
66             *atomic = \&transaction; # Because it just sounds cooler
67              
68             sub transaction {
69 0     0 1   my ( $c, $coderef ) = @_;
70              
71 0           my @cdbi = @{ $c->_cdbi_models };
  0            
72 0 0         die "Couldn't find a CDBI component" unless @cdbi;
73              
74             # Stash away previous AutoCommit values for CDBI classes
75 0           my %ac_prev;
76 0           for my $cdbi ( map { ref $_ } @cdbi ) {
  0            
77             # Only touch models that don't have AutoCommit set to zero.
78 0   0       $ac_prev{$cdbi} = $cdbi->db_Main->{AutoCommit} || next;
79 0           $cdbi->db_Main->{AutoCommit} = 0;
80             }
81 0           $_->db_Main->{AutoCommit} = 0 for keys %ac_prev;
82              
83             # Execute the code in $coderef inside a transaction
84 0           eval { $coderef->() };
  0            
85 0           my $error;
86 0 0         if ( $@ ) {
87 0           $error = $@;
88             # dbi_rollback might die too
89 0           eval {
90             $_->dbi_rollback or
91 0   0       $c->log->error("dbi_rollback failed in $_: $!") for @cdbi;
92             };
93 0           $c->error($error);
94             }
95            
96             # Restore previous AutoCommit values for each model class.
97             # Will trigger a commit() if AutoCommit was previously true.
98 0           $_->db_Main->{AutoCommit} = $ac_prev{$_} for keys %ac_prev;
99              
100 0 0         return $error ? 0 : 1;
101             }
102              
103             =item $c->_cdbi_models
104              
105             Called internally by $c->transaction to search all laoded components and
106             return those that have a db_Main() method and where AutoCommit is not explicitly
107             disabled. Caches the search result for subsequent calls. Returns an arrayref
108             containing all the found components, or undef if none found.
109              
110             =cut
111              
112             sub _cdbi_models {
113 0     0     my $c = shift;
114              
115 0 0         return $c->cdbi_models if $c->cdbi_models;
116 0           my @models;
117 0           for my $class ( keys %{ $c->components } ) {
  0            
118 0 0 0       if ( $class->can('db_Main') && $class->columns ) {
119 0 0 0       push @models, $c->comp($class) unless
120             exists $c->comp($class)->db_Main->{AutoCommit} &&
121             !$c->comp($class)->db_Main->{AutoCommit};
122             }
123             }
124 0 0         return @models ? $c->cdbi_models(\@models) : undef;
125             }
126              
127             =back
128              
129             =head1 NOTES/CAVEATS
130              
131             All loaded model components used in the coderef passed to $c->transaction must
132             have AutoCommit set to a true value. If AutoCommit is off for a connection,
133             $c->transaction will not trigger commits or rollbacks on that connection, and you
134             will need to take care of the commit yourself.
135              
136             This plugin is intended for running the occasional transaction on a database
137             connection that normally runs in AutoCommit mode. If you choose to run with
138             AutoCommit off, then you probably should handle your transactions manually rather
139             than with Catalyst::Plugin::CDBI::Transaction.
140              
141             This plugin currently only works with Class::DBI models. It has been tested with
142             Catalyst::Model::CDBI and Catalyst::Model::CDBI::Sweet.
143              
144             Be careful when running this plugin under mod_perl. If the coderef passed to
145             $c->transaction is from a named subroutine, mod_perl won't recompile that
146             subroutine on each request, and thus will ignore future changes to the lexical
147             variables outside the subroutine. This is probably not what you want. In
148             summary, always pass an I<anonymous> subroutine to $c->transaction, like in the
149             SYNOPSIS above. And check your error logs for the infamous "won't stay shared"
150             message. See the mod_perl docs, specifically "Understanding Closures", for
151             more details.
152              
153             =head1 SEE ALSO
154              
155             L<Class::DBI> (especially the TRANSACTIONS section), L<Catalyst>
156              
157             =head1 AUTHOR
158              
159             Brian Cooke, C<mrkoffee@saltedsnail.com>
160              
161             =head1 LICENSE
162              
163             This plugin is free software. You may redistribute or modify it under the same
164             terms as Perl itself.
165              
166             =cut
167              
168             1;