File Coverage

blib/lib/Class/Accessor/TrackDirty.pm
Criterion Covered Total %
statement 132 132 100.0
branch 32 32 100.0
condition 13 16 81.2
subroutine 33 33 100.0
pod 4 4 100.0
total 214 217 98.6


line stmt bran cond sub pod time code
1             package Class::Accessor::TrackDirty;
2 10     10   418596 use 5.008_001;
  10         95  
3 10     10   48 use strict;
  10         18  
  10         206  
4 10     10   43 use warnings;
  10         14  
  10         333  
5 10     10   4798 use List::MoreUtils qw(any);
  10         118451  
  10         55  
6 10     10   14485 use Storable qw(dclone freeze);
  10         27246  
  10         2992  
7             our $VERSION = '0.110.1';
8              
9             our $RESERVED_FIELD = '_original';
10             our $NEW = 'new';
11             our $FROM_HASH = 'from_hash';
12             our $RAW = 'raw';
13             our $TO_HASH = 'to_hash';
14             our $IS_MODIFIED = 'is_dirty';
15             our $MODIFIED_FIELDS = 'dirty_fields';
16             our $IS_NEW = 'is_new';
17             our $REVERT = 'revert';
18              
19             {
20             my %package_info;
21             sub _package_info($) {
22 48     48   66 my $package = shift;
23 48   100     251 $package_info{$package} ||= {tracked_fields => {}, fields => {}};
24             }
25             }
26              
27             sub _is_different_deeply($$) {
28 6     6   13 my ($ref_x, $ref_y) = @_;
29 6         27 (freeze $ref_x) ne (freeze $ref_y);
30             }
31              
32             sub _is_different($$) {
33 87     87   184 my ($x, $y) = @_;
34 87 100 100     297 if (defined $x && defined $y) {
35 64 100 66     135 if (ref $x && ref $y) {
36 6         19 return _is_different_deeply $x, $y;
37             } else {
38 58   66     365 return ref $x || ref $y || $x ne $y;
39             }
40             } else {
41 23   66     140 return defined $x || defined $y;
42             }
43             }
44              
45             sub _make_tracked_accessor($$) {
46 10     10   79 no strict 'refs';
  10         26  
  10         1798  
47 32     32   61 my ($package, $name) = @_;
48              
49 32         166 *{"$package\::$name"} = sub {
50 221     221   9790 my $self = shift;
51              
52             # getter
53 221         274 my $value;
54 221 100       568 if (exists $self->{$name}) {
    100          
55 82         115 $value = $self->{$name};
56             } elsif (defined $self->{$RESERVED_FIELD}) {
57 93         151 $value = $self->{$RESERVED_FIELD}{$name};
58              
59             # Defensive copying
60 93 100       420 $value = ($self->{$name} = dclone $value) if ref $value;
61             }
62              
63             # setter
64 221 100       422 $self->{$name} = $_[0] if @_;
65              
66 221         417 return $value;
67 32         106 };
68             }
69              
70             sub _make_accessor($$) {
71 10     10   63 no strict 'refs';
  10         19  
  10         1863  
72 16     16   29 my ($package, $name) = @_;
73              
74 16         63 *{"$package\::$name"} = sub {
75 71     71   98 my $self = shift;
76 71         112 my $value = $self->{$name};
77 71 100       130 $self->{$name} = $_[0] if @_;
78 71         109 $value;
79 16         40 };
80             }
81              
82             sub _mk_tracked_accessors($@) {
83 16     16   30 my $package = shift;
84 16         49 _make_tracked_accessor $package => $_ for @_;
85 16         42 @{(_package_info $package)->{tracked_fields}}{@_} = (1,) x @_;
  16         32  
86             }
87              
88             sub _mk_helpers($) {
89 10     10   65 no strict 'refs';
  10         18  
  10         7560  
90 16     16   26 my $package = shift;
91             my ($tracked_fields, $fields) =
92 16         23 @{_package_info $package}{qw(tracked_fields fields)};
  16         29  
93              
94             # cleate helper methods
95 16         64 *{"$package\::$FROM_HASH"} = sub {
96 21     21   8034 my $package = shift;
97 21 100       73 my %modified = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  18         63  
98              
99 21         35 my %origin;
100 21         50 for my $name (keys %$tracked_fields) {
101 42 100       117 $origin{$name} = delete $modified{$name} if exists $modified{$name};
102             }
103              
104 21         51 $modified{$RESERVED_FIELD} = \%origin;
105 21         56 bless \%modified, $package;
106 16         60 };
107              
108 16         51 *{"$package\::$RAW"} = sub {
109 49     49   355 my ($self) = @_;
110              
111             my %hash = (
112             (map {
113             # Don't store undefined values.
114 49         120 my $v = $self->$_;
  147         266  
115 147 100       336 defined $v ? ($_ => $v) : ();
116             } keys %$tracked_fields, keys %$fields),
117             );
118              
119 49         159 return \%hash;
120 16         48 };
121              
122 16         53 *{"$package\::$TO_HASH"} = sub {
123 31     31   1573 my ($self) = @_;
124 31         74 my $raw = $self->$RAW;
125              
126             # Move published data for cleaning.
127 31   100     115 $self->{$RESERVED_FIELD} ||= {};
128             $self->{$RESERVED_FIELD}{$_} = delete $self->{$_}
129 31         57 for grep { exists $self->{$_} } keys %$tracked_fields;
  62         153  
130              
131 31         66 return $raw;
132 16         45 };
133              
134 16         55 *{"$package\::$IS_MODIFIED"} = sub {
135 400     400   4276 my ($self, $field) = @_;
136 400 100   184   1120 return any { $self->$IS_MODIFIED($_) } keys %$tracked_fields
  184         623  
137             unless $field;
138              
139 298 100       540 return unless $tracked_fields->{$field};
140 292 100       737 return defined $self->{$field} unless defined $self->{$RESERVED_FIELD};
141              
142             exists $self->{$field} &&
143 205 100       676 _is_different $self->{$field}, $self->{$RESERVED_FIELD}{$field};
144 16         49 };
145              
146 16         126 *{"$package\::$MODIFIED_FIELDS"} = sub {
147 36     36   463 my $self = shift;
148 36         82 grep { $self->$IS_MODIFIED($_) } keys %$tracked_fields;
  72         137  
149 16         52 };
150              
151 16         80 *{"$package\::$IS_NEW"} = sub {
152 60     60   105 my $self = shift;
153 60 100       217 exists $self->{$RESERVED_FIELD} ? 0 : 1;
154 16         35 };
155              
156 16         63 *{"$package\::$REVERT"} = sub {
157 17     17   414 my $self = shift;
158 17         67 delete $self->{$_} for keys %$tracked_fields;
159 16         39 };
160             }
161              
162             sub _mk_accessors($@) {
163 16     16   27 my $package = shift;
164 16         42 _make_accessor $package => $_ for @_;
165 16         33 @{(_package_info $package)->{fields}}{@_} = (1,) x @_;
  16         31  
166             }
167              
168             sub _mk_new($) {
169 10     10   67 no strict 'refs';
  10         24  
  10         2756  
170 11     11   19 my $package = shift;
171              
172 11         52 *{"$package\::$NEW"} = sub {
173 17     17   3578 my $package = shift;
174 17 100       67 my %modified = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  15         48  
175              
176 17         58 bless \%modified => $package;
177 11         48 };
178             }
179              
180             sub mk_tracked_accessors {
181 11     11 1 3485 (undef, my @tracked_fields) = @_;
182 11         26 my $package = caller(0);
183 11         33 _mk_tracked_accessors $package => @tracked_fields;
184 11         24 _mk_helpers $package;
185             }
186              
187             sub mk_accessors {
188 16     16 1 81 (undef, my @fields) = @_;
189 16         31 my $package = caller(0);
190 16         38 _mk_accessors $package => @fields;
191             }
192              
193             sub mk_new {
194 6     6 1 23 my $package = caller(0);
195 6         12 _mk_new $package;
196             }
197              
198             sub mk_new_and_tracked_accessors {
199 5     5 1 373 (undef, my @tracked_fields) = @_;
200 5         15 my $package = caller(0);
201 5         15 _mk_tracked_accessors $package => @tracked_fields;
202 5         13 _mk_helpers $package;
203 5         12 _mk_new $package;
204             }
205              
206             1;
207             __END__