File Coverage

blib/lib/DBIx/QuickORM/Row.pm
Criterion Covered Total %
statement 119 158 75.3
branch 40 64 62.5
condition 17 35 48.5
subroutine 33 51 64.7
pod 0 35 0.0
total 209 343 60.9


line stmt bran cond sub pod time code
1             package DBIx::QuickORM::Row;
2 22     22   209 use strict;
  22         127  
  22         1050  
3 22     22   155 use warnings;
  22         45  
  22         1822  
4              
5 22     22   146 use Carp qw/confess croak/;
  22         45  
  22         1889  
6 22     22   148 use Storable qw/dclone/;
  22         51  
  22         1782  
7 22     22   160 use List::Util qw/zip/;
  22         51  
  22         1805  
8 22     22   143 use Scalar::Util qw/blessed/;
  22         54  
  22         1298  
9 22     22   149 use DBIx::QuickORM::Util qw/column_key/;
  22         51  
  22         276  
10              
11 22     22   962 use DBIx::QuickORM::Affinity();
  22         54  
  22         445  
12 22     22   121 use DBIx::QuickORM::Link();
  22         53  
  22         1199  
13              
14             our $VERSION = '0.000019';
15              
16 22         4389 use DBIx::QuickORM::Connection::RowData qw{
17             STORED
18             PENDING
19             DESYNC
20             TRANSACTION
21 22     22   2368 };
  22         69  
22              
23 22         232 use DBIx::QuickORM::Util::HashBase qw{
24             +row_data
25 22     22   1450 };
  22         60  
26              
27 22     22   209 use Role::Tiny::With qw/with/;
  22         55  
  22         101654  
28             with 'DBIx::QuickORM::Role::Row';
29              
30 0     0 0 0 sub track_desync { 1 }
31              
32 463     463 0 1606 sub source { $_[0]->{+ROW_DATA}->source }
33 74     74 0 270 sub connection { $_[0]->{+ROW_DATA}->connection }
34              
35 2     2 0 15 sub row_data_obj { $_[0]->{+ROW_DATA} }
36 173     173 0 826 sub row_data { $_[0]->{+ROW_DATA}->active }
37              
38 25     25 0 164 sub stored_data { $_[0]->row_data->{+STORED} }
39 13     13 0 45 sub pending_data { $_[0]->row_data->{+PENDING} }
40 0     0 0 0 sub desynced_data { $_[0]->row_data->{+DESYNC} }
41              
42 0   0 0 0 0 sub is_invalid { $_[0]->{+ROW_DATA}->invalid // 0 }
43 17 100   17 0 8459 sub is_valid { $_[0]->{+ROW_DATA}->valid ? 1 : 0 }
44              
45 31 100 66 31 0 8464 sub in_storage { my $a = $_[0]->{+ROW_DATA}->active(no_fatal => 1); $a && $a->{+STORED} ? 1 : 0 }
  31         439  
46 34 100 100 34 0 177 sub is_stored { my $a = $_[0]->{+ROW_DATA}->active(no_fatal => 1); $a && $a->{+STORED} ? 1 : 0 }
  34         355  
47 0 0 0 0 0 0 sub is_desynced { my $a = $_[0]->{+ROW_DATA}->active(no_fatal => 1); $a && $a->{+DESYNC} ? 1 : 0 }
  0         0  
48 12 50 33 12 0 48 sub has_pending { my $a = $_[0]->{+ROW_DATA}->active(no_fatal => 1); $a && $a->{+PENDING} ? 1 : 0 }
  12         138  
49              
50             sub init {
51 106     106 0 250 my $self = shift;
52              
53 106 50       4622 confess "No 'row_data' provided" unless $self->{+ROW_DATA};
54             }
55              
56             sub clone {
57 2     2 0 26 my $self = shift;
58 2         10 my %overrides = @_;
59              
60 2         11 my $row_data = $self->row_data;
61 2   50     6 my $data = +{%{$row_data->{+STORED} // {}}, %{$row_data->{+PENDING} // {}}};
  2   100     16  
  2         19  
62              
63             # Remove primary key fields
64 2         16 delete $data->{$_} for $self->primary_key_field_list;
65              
66             # Use dclone in case there is an inflated json object or similar that we do not want shared
67 2         189 $data = dclone($data);
68              
69             # Add in any overrides
70 2         16 %$data = (%$data, %overrides);
71              
72 2         17 return $self->handle->vivify($data);
73             }
74              
75             #####################
76             # {{{ Sanity Checks #
77             #####################
78              
79             sub check_sync {
80 12 50 33 12 0 51 croak <<" EOT" if $_[0]->{+DESYNC} && !$_[0]->track_desync;
81              
82             This row is out of sync, this means it was refreshed while it had pending
83             changes and the data retrieved from the database does not match what was in
84             place when the pending changes were set.
85              
86             To fix such conditions you need to either use row->discard() to clear the
87             pending changes, or you need to call ->force_sync() to clear the desync flags
88             allowing you to save the row despite the discrepency.
89              
90             In addition it would be a good idea to call ->refresh() to have the most up to
91             date data.
92              
93             EOT
94              
95 12 100 100     54 croak <<" EOT" if $_[0]->connection->current_txn && !$_[0]->row_data->{+TRANSACTION};
96              
97             This row was fetched outside of the current transaction stack. The row has not
98             been refreshed since the new transaction stack started, meaning the data is
99             likely stale and unreliable. The row should be refreshed before making changes.
100             You can do this with a call to ->refresh().
101              
102             EOT
103              
104 10         38 return $_[0];
105             }
106              
107             #####################
108             # }}} Sanity Checks #
109             #####################
110              
111             ############################
112             # {{{ Manipulation Methods #
113             ############################
114              
115             sub force_sync {
116 0     0 0 0 my $self = shift;
117 0         0 delete $self->row_data->{+DESYNC};
118 0         0 return $self;
119             }
120              
121             # Fetch new data from the db
122             sub refresh {
123 2     2 0 16 my $self = shift;
124              
125 2         174 $self->check_pk;
126              
127 2 50       13 croak "This row is not in the database yet" unless $self->is_stored;
128 2         16 return $self->connection->handle($self)->one;
129             }
130              
131             # Remove pending changes (and clear desync)
132             sub discard {
133 0     0 0 0 my $self = shift;
134              
135 0         0 delete $self->row_data->{+DESYNC};
136 0         0 delete $self->row_data->{+PENDING};
137              
138 0         0 return $self;
139             }
140              
141             sub delete {
142 3     3 0 94 my $self = shift;
143              
144 3         25 $self->check_pk;
145              
146 2 50       10 croak "This row is not in the database yet" unless $self->is_stored;
147 2         11 return $self->connection->handle($self)->delete;
148             }
149              
150             sub update {
151 11     11 0 8017 my $self = shift;
152              
153 11         26 my $changes;
154 11 100       43 if (@_ == 1) {
155 9         26 ($changes) = @_;
156             }
157             else {
158 2         9 $changes = {@_};
159             }
160              
161 11         75 $self->check_pk;
162              
163 10         36 my $row_data = $self->row_data;
164 10         38 for my $field (keys %$changes) {
165 10         62 $row_data->{+PENDING}->{$field} = $changes->{$field};
166 10 50       56 delete $row_data->{+DESYNC}->{$field} if $row_data->{+DESYNC};
167             }
168              
169 10         53 $self->save();
170 8         43 return $self;
171             }
172              
173             ############################
174             # }}} Manipulation Methods #
175             ############################
176              
177             #####################
178             # {{{ Field methods #
179             #####################
180              
181 85     85 0 17896 sub field { shift->_field(_inflated_field => @_) }
182 0     0 0 0 sub raw_field { shift->_field(_raw_field => @_) }
183              
184 0     0 0 0 sub fields { my $d = $_[0]->row_data; $_[0]->_fields(_field => $d->{+PENDING}, $d->{+STORED}) }
  0         0  
185 0     0 0 0 sub raw_fields { my $d = $_[0]->row_data; $_[0]->_fields(_raw_field => $d->{+PENDING}, $d->{+STORED}) }
  0         0  
186              
187 0     0 0 0 sub stored_field { $_[0]->_inflated_field($_[0]->row_data->{+STORED}, $_[1]) }
188 0     0 0 0 sub pending_field { $_[0]->_inflated_field($_[0]->row_data->{+PENDING}, $_[1]) }
189              
190 27     27 0 104 sub raw_stored_field { $_[0]->_raw_field($_[0]->row_data->{+STORED}, $_[1]) }
191 0     0 0 0 sub raw_pending_field { $_[0]->_raw_field($_[0]->row_data->{+PENDING}, $_[1]) }
192              
193 0     0 0 0 sub stored_fields { $_[0]->_fields(_field => $_[0]->row_data->{+STORED}) }
194 0     0 0 0 sub pending_fields { $_[0]->_fields(_field => $_[0]->row_data->{+PENDING}) }
195 0     0 0 0 sub raw_stored_fields { $_[0]->_fields(_raw_field => $_[0]->row_data->{+STORED}) }
196 0     0 0 0 sub raw_pending_fields { $_[0]->_fields(_raw_field => $_[0]->row_data->{+PENDING}) }
197              
198             sub field_is_desynced {
199 0     0 0 0 my $self = shift;
200 0         0 my ($field) = @_;
201              
202 0 0       0 croak "You must specify a field name" unless @_;
203              
204 0 0       0 my $desync = $self->row_data->{+DESYNC} or return 0;
205 0   0     0 return $desync->{$field} // 0;
206             }
207              
208             sub _field {
209 85     85   828 my $self = shift;
210 85         210 my $meth = shift;
211 85 50       5528 my $field = shift or croak "Must specify a field name";
212              
213 85 100       546 croak "This row does not have a '$field' field" unless $self->has_field($field);
214              
215 84         4386 my $row_data = $self->row_data;
216              
217 82 100       331 if (@_) {
218 4 50       42 $self->check_pk if $row_data->{+STORED}; # We can set a field if the row has not been inserted yet, or if it has a pk
219 3         53 $row_data->{+PENDING}->{$field} = shift;
220             }
221              
222 81 100 100     361 return $self->$meth($row_data->{+PENDING}, $field) if $row_data->{+PENDING} && exists $row_data->{+PENDING}->{$field};
223              
224 72 50       277 if (my $st = $row_data->{+STORED}) {
225 72 100       296 unless (exists $st->{$field}) {
226 1         6 my $data = $self->connection->handle($self->source, where => $self->primary_key_hashref, fields => [$field])->data_only->one;
227 1         13 $st->{$field} = $data->{$field};
228             }
229              
230 72         389 return $self->$meth($st, $field);
231             }
232              
233 0         0 return undef;
234             }
235              
236             sub _fields {
237 0     0   0 my $self = shift;
238 0         0 my $meth = shift;
239              
240 0         0 my %out;
241 0         0 for my $hr (@_) {
242 0 0       0 next unless $hr;
243              
244 0         0 for my $field (keys %$hr) {
245 0   0     0 $out{$field} //= $self->$meth($hr, $field);
246             }
247             }
248              
249 0         0 return \%out;
250             }
251              
252             sub _inflated_field {
253 81     81   246 my $self = shift;
254 81         252 my ($from, $field) = @_;
255              
256 81 50       319 croak "This row does not have a '$field' field" unless $self->has_field($field);
257              
258 81 50       262 return undef unless $from;
259 81 50       275 return undef unless exists $from->{$field};
260              
261 81         213 my $val = $from->{$field};
262              
263 81 100       286 return $val if ref($val); # Inflated already
264              
265 77 100       8281 if (my $type = $self->source->field_type($field)) {
266 13         442 return $from->{$field} = $type->qorm_inflate($self->conflate_args($field, $val));
267             }
268              
269 64         1226 return $from->{$field};
270             }
271              
272             sub _raw_field {
273 27     27   65 my $self = shift;
274 27         107 my ($from, $field) = @_;
275              
276 27 50       125 croak "This row does not have a '$field' field" unless $self->has_field($field);
277              
278 27 100       130 return undef unless $from;
279 26 50       100 return undef unless exists $from->{$field};
280 26         76 my $val = $from->{$field};
281              
282 26 50 33     106 return $val->qorm_deflate($self->conflate_args($field, $val))
283             if blessed($val) && $val->can('qorm_deflate');
284              
285 26 50       78 if (my $type = $self->source->field_type($field)) {
286 0         0 return $type->qorm_deflate($self->conflate_args($field, $val));
287             }
288              
289 26         283 return $val;
290             }
291              
292             #####################
293             # }}} Field methods #
294             #####################
295              
296             1;