File Coverage

blib/lib/Alzabo/Runtime/Row.pm
Criterion Covered Total %
statement 46 132 34.8
branch 1 30 3.3
condition 1 15 6.6
subroutine 16 30 53.3
pod 4 9 44.4
total 68 216 31.4


line stmt bran cond sub pod time code
1             package Alzabo::Runtime::Row;
2              
3 11     11   64 use strict;
  11         20  
  11         602  
4 11     11   63 use vars qw($VERSION);
  11         24  
  11         450  
5              
6 11     11   65 use Alzabo;
  11         20  
  11         378  
7              
8 11         95 use Alzabo::Exceptions ( abbr => [ qw( logic_exception no_such_row_exception
9 11     11   72 params_exception storable_exception ) ] );
  11         27  
10              
11 11     11   66 use Alzabo::Runtime;
  11         24  
  11         263  
12 11     11   7426 use Alzabo::Runtime::RowState::Deleted;
  11         36  
  11         319  
13 11     11   7842 use Alzabo::Runtime::RowState::Live;
  11         40  
  11         379  
14 11     11   10500 use Alzabo::Runtime::RowState::Potential;
  11         35  
  11         286  
15 11     11   76 use Alzabo::Utils;
  11         26  
  11         286  
16              
17 11     11   69 use Params::Validate qw( validate validate_with UNDEF SCALAR HASHREF BOOLEAN );
  11         23  
  11         1479  
18             Params::Validate::validation_options
19             ( on_fail => sub { params_exception join '', @_ } );
20              
21 11     11   69 use Storable ();
  11         23  
  11         432  
22              
23             $VERSION = 2.0;
24              
25             BEGIN
26             {
27 11     11   61 no strict 'refs';
  11         23  
  11         1176  
28 11     11   35 foreach my $meth ( qw( select select_hash update refresh delete
29             id_as_string is_live is_potential is_deleted ) )
30             {
31 99         1931 *{ __PACKAGE__ . "::$meth" } =
32 0     0   0 sub { my $s = shift;
33 99         448 $s->{state}->$meth( $s, @_ ) };
  0         0  
34             }
35             }
36              
37 11         3284 use constant NEW_SPEC => { table => { isa => 'Alzabo::Runtime::Table' },
38             pk => { type => SCALAR | HASHREF,
39             optional => 1,
40             },
41             prefetch => { type => UNDEF | HASHREF,
42             optional => 1,
43             },
44             state => { type => SCALAR,
45             default => 'Alzabo::Runtime::RowState::Live',
46             },
47             potential_row => { isa => 'Alzabo::Runtime::Row',
48             optional => 1,
49             },
50             values => { type => HASHREF,
51             default => {},
52             },
53             no_cache => { type => BOOLEAN, default => 0 },
54 11     11   72 };
  11         25  
55              
56             sub new
57             {
58 0     0 0   my $proto = shift;
59 0   0       my $class = ref $proto || $proto;
60              
61 0           my %p =
62             validate( @_, NEW_SPEC );
63              
64 0 0         my $self = $p{potential_row} ? $p{potential_row} : {};
65              
66 0           bless $self, $class;
67              
68 0           $self->{table} = $p{table};
69 0           $self->{state} = $p{state};
70              
71 0 0         $self->{state}->_init($self, @_) or return;
72              
73 0           return $self;
74             }
75              
76             sub table
77             {
78 0     0 1   my $self = shift;
79              
80 0           return $self->{table};
81             }
82              
83             sub schema
84             {
85 0     0 1   my $self = shift;
86              
87 0           return $self->table->schema;
88             }
89              
90 0     0 0   sub set_state { $_[0]->{state} = $_[1] };
91              
92 11     11   86 use constant ROWS_BY_FOREIGN_KEY_SPEC => { foreign_key => { isa => 'Alzabo::ForeignKey' } };
  11         20  
  11         21668  
93              
94             sub rows_by_foreign_key
95             {
96 0     0 1   my $self = shift;
97 0           my %p = validate_with( params => \@_,
98             spec => ROWS_BY_FOREIGN_KEY_SPEC,
99             allow_extra => 1,
100             );
101              
102 0           my $fk = delete $p{foreign_key};
103              
104 0 0         if ($p{where})
105             {
106 0 0         $p{where} = [ $p{where} ] unless Alzabo::Utils::is_arrayref( $p{where}[0] );
107             }
108              
109 0           push @{ $p{where} },
  0            
110 0           map { [ $_->[1], '=', $self->select( $_->[0]->name ) ] } $fk->column_pairs;
111              
112             # if the relationship is not 1..n, then only one row can be
113             # returned (or referential integrity has been hosed in the
114             # database).
115 0 0         return $fk->is_one_to_many ? $fk->table_to->rows_where(%p) : $fk->table_to->one_row(%p);
116             }
117              
118             # class method
119             sub id_as_string_ext
120             {
121 0     0 0   my $class = shift;
122 0           my %p = @_;
123 0           my $id_hash = $class->_make_id_hash(%p);
124              
125 0           local $^W; # weirdly, enough there are code paths that can
126             # lead here that'd lead to $id_hash having some
127             # values that are undef
128 0           return join ';:;_;:;', ( $p{table}->schema->name,
129             $p{table}->name,
130 0           map { $_, $id_hash->{$_} } sort keys %$id_hash );
131             }
132              
133             sub _make_id_hash
134             {
135 0     0     my $self = shift;
136 0           my %p = @_;
137              
138 0 0         return $p{pk} if ref $p{pk};
139              
140 0           return { ($p{table}->primary_key)[0]->name => $p{pk} };
141             }
142              
143             sub _update_pk_hash
144             {
145 0     0     my $self = shift;
146              
147 0           my @pk = keys %{ $self->{pk} };
  0            
148              
149 0           @{ $self->{pk} }{ @pk } = @{ $self->{data} }{ @pk };
  0            
  0            
150              
151 0           delete $self->{id_string};
152             }
153              
154             sub make_live
155             {
156 0     0 1   my $self = shift;
157              
158 0 0         logic_exception "Can only call make_live on potential rows"
159             unless $self->{state}->is_potential;
160              
161 0           my %p = @_;
162              
163 0           my %values;
164 0           foreach ( $self->table->columns )
165             {
166 0 0 0       next unless exists $p{values}->{ $_->name } || exists $self->{data}->{ $_->name };
167 0 0         $values{ $_->name } = ( exists $p{values}->{ $_->name } ?
168             $p{values}->{ $_->name } :
169             $self->{data}->{ $_->name } );
170             }
171              
172 0           my $table = $self->table;
173 0           delete @{ $self }{keys %$self}; # clear out everything
  0            
174              
175 0 0         $table->insert( @_,
176             potential_row => $self,
177             %values ? ( values => \%values ) : (),
178             );
179             }
180              
181             sub _cached_data_is_same
182             {
183 0     0     my $self = shift;
184 0           my ( $key, $val ) = @_;
185              
186             # The convolutions here are necessary to avoid avoid treating
187             # undef as being equal to 0 or ''. Stupid NULLs.
188 0 0 0       return 1
      0        
189             if ( exists $self->{data}{$key} &&
190             ( ( ! defined $val && ! defined $self->{data}{$key} ) ||
191             ( defined $val &&
192             defined $self->{data}{$key} &&
193             ( $val eq $self->{data}{$key} )
194             )
195             )
196             );
197              
198 0           return 0;
199             }
200              
201             sub _no_such_row_error
202             {
203 0     0     my $self = shift;
204              
205 0           my $err = 'Unable to find a row in ' . $self->table->name . ' where ';
206 0           my @vals;
207 0           while ( my( $k, $v ) = each %{ $self->{pk} } )
  0            
208             {
209 0 0         $v = '' unless defined $v;
210 0           my $val = "$k = $v";
211 0           push @vals, $val;
212             }
213 0           $err .= join ', ', @vals;
214              
215 0           no_such_row_exception $err;
216             }
217              
218             sub STORABLE_freeze
219             {
220 0     0 0   my $self = shift;
221 0           my $cloning = shift;
222              
223 0           my %data = %$self;
224              
225 0           my $table = delete $data{table};
226              
227 0           $data{schema} = $table->schema->name;
228 0           $data{table_name} = $table->name;
229              
230 0           my $ser = eval { Storable::nfreeze(\%data) };
  0            
231              
232 0 0         storable_exception $@ if $@;
233              
234 0           return $ser;
235             }
236              
237             sub STORABLE_thaw
238             {
239 0     0 0   my ( $self, $cloning, $ser ) = @_;
240              
241 0           my $data = eval { Storable::thaw($ser) };
  0            
242              
243 0 0         storable_exception $@ if $@;
244              
245 0           %$self = %$data;
246              
247 0           my $s = Alzabo::Runtime::Schema->load_from_file( name => delete $self->{schema} );
248 0           $self->{table} = $s->table( delete $self->{table_name} );
249              
250 0           return $self;
251             }
252              
253             BEGIN
254             {
255             # dumb hack to fix bugs in Storable 2.00 - 2.03 w/ a non-threaded
256             # Perl
257             #
258             # Basically, Storable somehow screws up the hooks business the
259             # _first_ time an object from a class with hooks is stored. So
260             # we'll just _force_ it do it once right away.
261 11 50 33 11   515 if ( $Storable::VERSION >= 2 && $Storable::VERSION <= 2.03 )
262             {
263 0           eval <<'EOF';
264             { package ___name; sub name { 'foo' } }
265             { package ___table; @table::ISA = '___name'; sub schema { bless {}, '___name' } }
266             my $row = bless { table => bless {}, '___table' }, __PACKAGE__;
267             Storable::thaw(Storable::nfreeze($row));
268             EOF
269             }
270             }
271              
272              
273             1;
274              
275             __END__