File Coverage

blib/lib/Starch/State.pm
Criterion Covered Total %
statement 102 110 92.7
branch 24 28 85.7
condition 2 2 100.0
subroutine 25 26 96.1
pod 12 13 92.3
total 165 179 92.1


line stmt bran cond sub pod time code
1             package Starch::State;
2             our $VERSION = '0.14';
3              
4             =encoding utf8
5              
6             =head1 NAME
7              
8             Starch::State - The Starch state object.
9              
10             =head1 SYNOPSIS
11              
12             my $state = $starch->state();
13             $state->data->{foo} = 'bar';
14             $state->save();
15             $state = $starch->state( $state->id() );
16             print $state->data->{foo}; # bar
17              
18             =head1 DESCRIPTION
19              
20             This is the state class used by L.
21              
22             =cut
23              
24 13     13   4825 use Starch::Util qw( croak );
  13         38  
  13         714  
25 13     13   5781 use Types::Common::String -types;
  13         1165299  
  13         174  
26 13     13   18755 use Types::Standard -types;
  13         32  
  13         75  
27              
28 13     13   61222 use Moo;
  13         33144  
  13         74  
29 13     13   5271 use strictures 2;
  13         117  
  13         609  
30 13     13   2817 use namespace::clean;
  13         69  
  13         112  
31              
32             with 'Starch::Role::Log';
33              
34             # Declare BUILD so roles can apply method modifiers to it.
35       395 0   sub BUILD { }
36              
37             =head1 REQUIRED ARGUMENTS
38              
39             =head2 manager
40              
41             The L object that glues everything together. The state
42             object needs this to get at configuration information and the stores.
43             This argument is automatically set by L.
44              
45             =cut
46              
47             has manager => (
48             is => 'ro',
49             isa => InstanceOf[ 'Starch::Manager' ],
50             required => 1,
51             );
52              
53             =head1 OPTIONAL ARGUMENTS
54              
55             =head2 id
56              
57             The state ID. If one is not specified then one will be built and
58             the state will be considered new.
59              
60             =cut
61              
62             has _existing_id => (
63             is => 'ro',
64             init_arg => 'id',
65             predicate => 1,
66             clearer => '_clear_existing_id',
67             );
68              
69             has id => (
70             is => 'lazy',
71             init_arg => undef,
72             clearer => '_clear_id',
73             );
74             sub _build_id {
75 256     256   8454 my ($self) = @_;
76 256 100       2068 return $self->_existing_id() if $self->_has_existing_id();
77 121         802 return $self->manager->generate_state_id();
78             }
79              
80             =head1 ATTRIBUTES
81              
82             =head2 original_data
83              
84             The state data at the point it was when the state object was first instantiated.
85              
86             =cut
87              
88             has original_data => (
89             is => 'lazy',
90             isa => HashRef,
91             init_arg => undef,
92             writer => '_set_original_data',
93             clearer => '_clear_original_data',
94             predicate => 'is_loaded',
95             );
96             sub _build_original_data {
97 246     246   2101 my ($self) = @_;
98              
99 246 100       3861 return {} if !$self->in_store();
100              
101 120         365 my $manager = $self->manager();
102 120         1926 my $data = $manager->store->get( $self->id(), $manager->namespace() );
103              
104 120 100       1825 return $data if $data;
105              
106 36         114 $self->_set_in_store( 0 );
107 36         615 return {};
108             }
109              
110             =head2 data
111              
112             The state data which is meant to be modified.
113              
114             =cut
115              
116             has data => (
117             is => 'lazy',
118             init_arg => undef,
119             writer => '_set_data',
120             clearer => '_clear_data',
121             );
122             sub _build_data {
123 209     209   3104 my ($self) = @_;
124 209         3705 return $self->manager->clone_data( $self->original_data() );
125             }
126              
127             =head2 expires
128              
129             This defaults to L and is stored in the L
130             under the L key.
131              
132             =cut
133              
134             has expires => (
135             is => 'lazy',
136             init_arg => undef,
137             clearer => '_clear_expires',
138             writer => '_set_expires',
139             );
140             sub _build_expires {
141 128     128   1104 my ($self) = @_;
142              
143 128         282 my $manager = $self->manager();
144 128         1996 my $expires = $self->original_data->{ $manager->expires_state_key() };
145              
146 128 100       1926 $expires = $manager->expires() if !defined $expires;
147              
148 128         665 return $expires;
149             }
150              
151             =head2 modified
152              
153             Whenever the state is Ld this will be updated and stored in
154             L under the L.
155              
156             =cut
157              
158             has modified => (
159             is => 'lazy',
160             init_arg => undef,
161             clearer => '_clear_modified',
162             );
163             sub _build_modified {
164 19     19   329 my ($self) = @_;
165              
166             my $modified = $self->original_data->{
167 19         302 $self->manager->modified_state_key()
168             };
169              
170 19 100       611 $modified = $self->created() if !defined $modified;
171              
172 19         292 return $modified;
173             }
174              
175             =head2 created
176              
177             When the state is created this is set and stored in L
178             under the L.
179              
180             =cut
181              
182             has created => (
183             is => 'lazy',
184             init_arg => undef,
185             clearer => '_clear_created',
186             );
187             sub _build_created {
188 121     121   994 my ($self) = @_;
189              
190             my $created = $self->original_data->{
191 121         1858 $self->manager->created_state_key()
192             };
193              
194 121 100       1450 $created = time() if !defined $created;
195              
196 121         551 return $created;
197             }
198              
199             =head2 in_store
200              
201             Returns true if the state is expected to exist in the store
202             (AKA, if the L argument was specified or L was called).
203              
204             Note that the value of this attribute may change after L
205             is called which will set this to false if the store did not have
206             the data for the state.
207              
208             =cut
209              
210             has in_store => (
211             is => 'lazy',
212             writer => '_set_in_store',
213             init_arg => undef,
214             );
215             sub _build_in_store {
216 266     266   2020 my ($self) = @_;
217 266 100       2947 return( $self->_has_existing_id() ? 1 : 0 );
218             }
219              
220             =head2 is_loaded
221              
222             This returns true if the L has been loaded up from
223             the store. Note that L will be automatically
224             loaded if L, L, or any methods that call them,
225             are called.
226              
227             =cut
228              
229             # This is provided by the original_data attribute via its predicate.
230              
231             =head2 is_saved
232              
233             Returns true if the state is L and is not L.
234              
235             =cut
236              
237             sub is_saved {
238 28     28 1 69 my ($self) = @_;
239 28 100       529 return 0 if !$self->in_store();
240 14 50       149 return 0 if $self->is_dirty();
241 14         98 return 1;
242             }
243              
244             =head2 is_deleted
245              
246             Returns true if L has been called on this state.
247              
248             =cut
249              
250             has is_deleted => (
251             is => 'ro',
252             writer => '_set_is_deleted',
253             init_arg => undef,
254             default => 0,
255             );
256              
257             =head2 is_dirty
258              
259             Returns true if the state data has changed (if L
260             and L are different).
261              
262             =cut
263              
264             sub is_dirty {
265 228     228 1 509 my ($self) = @_;
266              
267             # If we haven't even loaded the data from the store then
268             # there is no way we're dirty.
269 228 100       802 return 0 if !$self->is_loaded();
270              
271 213         4191 return $self->manager->is_data_diff( $self->original_data(), $self->data() );
272             }
273              
274             =head1 METHODS
275              
276             =head2 save
277              
278             Saves this state in the L if it L and
279             not L.
280              
281             =cut
282              
283             sub save {
284 123     123 1 652 my ($self) = @_;
285              
286 123 100       392 return if !$self->is_dirty();
287 114 50       420 return if $self->is_deleted();
288              
289 114         252 my $manager = $self->manager();
290 114         2589 my $data = $self->data();
291              
292 114         2455 $data->{ $manager->created_state_key() } = $self->created();
293 114         576 $data->{ $manager->modified_state_key() } = time();
294 114         1900 $data->{ $manager->expires_state_key() } = $self->expires();
295              
296 114         2126 $self->_clear_modified();
297              
298 114         2169 $manager->store->set(
299             $self->id(),
300             $manager->namespace(),
301             $data,
302             $self->expires(),
303             );
304              
305             # This will cause is_saved to return true.
306 113         678 $self->_set_in_store( 1 );
307 113         588 $self->mark_clean();
308              
309 113         328 return;
310             }
311              
312             =head2 delete
313              
314             Deletes the state from the L and sets
315             L.
316              
317             =cut
318              
319             sub delete {
320 32     32 1 158 my ($self) = @_;
321              
322 32 50       594 if ($self->in_store()) {
323 32         218 my $manager = $self->manager();
324 32         856 $manager->store->remove( $self->id(), $manager->namespace() );
325             }
326              
327 32         177 $self->_set_is_deleted( 1 );
328 32         79 $self->_set_in_store( 0 );
329              
330 32         73 return;
331             }
332              
333             =head2 reload
334              
335             Clears L and L so that the next call to these
336             will reload the state data from the store. This method is potentially
337             destructive as you will loose any changes to the data that have not
338             been saved.
339              
340             =cut
341              
342             sub reload {
343 15     15 1 82 my ($self) = @_;
344              
345 15         295 $self->_clear_original_data();
346 15         321 $self->_clear_data();
347              
348 15         86 return;
349             }
350              
351             =head2 rollback
352              
353             Sets L to L.
354              
355             =cut
356              
357             sub rollback {
358 15     15 1 92 my ($self) = @_;
359              
360 15         312 $self->_set_data(
361             $self->manager->clone_data( $self->original_data() ),
362             );
363              
364 15         48 return;
365             }
366              
367             =head2 clear
368              
369             Empties L and L, and calls L.
370              
371             =cut
372              
373             sub clear {
374 0     0 1 0 my ($self) = @_;
375              
376             # Make sure we retain these values.
377 0         0 $self->expires();
378 0         0 $self->modified();
379 0         0 $self->created();
380              
381 0         0 $self->_set_original_data( {} );
382 0         0 $self->_set_data( {} );
383 0         0 $self->mark_dirty();
384              
385 0         0 return;
386             }
387              
388             =head2 mark_clean
389              
390             Marks the state as not L by setting L to
391             L. This is a potentially destructive method as L will
392             silentfly not save if the state is not L.
393              
394             =cut
395              
396             sub mark_clean {
397 128     128 1 466 my ($self) = @_;
398              
399 128         2286 $self->_set_original_data(
400             $self->manager->clone_data( $self->data() ),
401             );
402              
403 128         3337 return;
404             }
405              
406             =head2 mark_dirty
407              
408             Increments the L value in L,
409             which causes the state to be considered dirty.
410              
411             =cut
412              
413             sub mark_dirty {
414 48     48 1 3233 my ($self) = @_;
415              
416 48         391 my $key = $self->manager->dirty_state_key();
417              
418 48         1798 my $counter = $self->data->{ $key };
419 48   100     409 $counter = ($counter || 0) + 1;
420 48         1070 $self->data->{ $key } = $counter;
421              
422 48         450 return;
423             }
424              
425             =head2 set_expires
426              
427             # Extend this state's expires duration by two hours.
428             $state->set_expires( $state->expires() + (2 * 60 * 60) );
429              
430             Use this to set the state's expires to a duration different than the
431             global expires set by L. This is useful for,
432             for example, to support a "Remember Me" checkbox that many login
433             forms provide where the difference between the user checking it or not
434             is just a matter of what the state's expires duration is set to.
435              
436             Remember that the "expires" duration is a measurement, in seconds, of
437             how long the state will live in the store since the last modification,
438             and how long the cookie (if you are using cookies) will live since the
439             last request.
440              
441             The expires duration can be more than or less than the global expires,
442             there is no artificial constraint.
443              
444             =cut
445              
446             sub set_expires {
447 21     21 1 95 my ($self, $expires) = @_;
448              
449 21         83 $self->_set_expires( $expires );
450 21         460 $self->data->{ $self->manager->expires_state_key() } = $expires;
451              
452 21         71 return;
453             }
454              
455             =head2 reset_expires
456              
457             Sets this state's expires to L, overriding
458             and custom expires set on this state.
459              
460             =cut
461              
462             sub reset_expires {
463 7     7 1 50 my ($self) = @_;
464              
465 7         69 $self->set_expires( $self->manager->expires() );
466              
467 7         20 return;
468             }
469              
470             =head2 reset_id
471              
472             This re-generates a new L and marks the L as dirty.
473             Often this is used to avoid
474             L
475             as part of authentication and de-authentication (login/logout).
476              
477             =cut
478              
479             sub reset_id {
480 7     7 1 42 my ($self) = @_;
481              
482             # Remove the data for the current state ID.
483 7 50       127 $self->manager->state( $self->id() )->delete()
484             if $self->in_store();
485              
486             # Ensure that future calls to id generate a new one.
487 7         170 $self->_clear_existing_id();
488 7         160 $self->_clear_id();
489              
490 7         144 $self->_set_original_data( {} );
491 7         182 $self->_set_in_store( 0 );
492              
493 7         18 return;
494             }
495              
496             1;
497             __END__