File Coverage

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


line stmt bran cond sub pod time code
1             package Class::Accessor::TrackDirty;
2 10     10   444748 use 5.008_001;
  10         99  
3 10     10   45 use strict;
  10         18  
  10         186  
4 10     10   39 use warnings;
  10         16  
  10         341  
5 10     10   4839 use List::MoreUtils qw(any);
  10         122681  
  10         65  
6 10     10   16639 use Storable qw(dclone freeze);
  10         29802  
  10         3354  
7             our $VERSION = '0.12';
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   67 my $package = shift;
23 48   100     252 $package_info{$package} ||= {tracked_fields => {}, fields => {}};
24             }
25             }
26              
27             sub _is_different_deeply($$) {
28 7     7   12 my ($ref_x, $ref_y) = @_;
29 7         10 local $Storable::canonical = 1; # avoiding Hash Randomization
30 7         15 (freeze $ref_x) ne (freeze $ref_y);
31             }
32              
33             sub _is_different($$) {
34 88     88   187 my ($x, $y) = @_;
35 88 100 100     275 if (defined $x && defined $y) {
36 67 100 66     150 if (ref $x && ref $y) {
37 7         12 return _is_different_deeply $x, $y;
38             } else {
39 60   66     376 return ref $x || ref $y || $x ne $y;
40             }
41             } else {
42 21   66     130 return defined $x || defined $y;
43             }
44             }
45              
46             sub _make_tracked_accessor($$) {
47 10     10   97 no strict 'refs';
  10         21  
  10         1846  
48 32     32   72 my ($package, $name) = @_;
49              
50 32         172 *{"$package\::$name"} = sub {
51 222     222   10605 my $self = shift;
52              
53             # getter
54 222         269 my $value;
55 222 100       558 if (exists $self->{$name}) {
    100          
56 82         121 $value = $self->{$name};
57             } elsif (defined $self->{$RESERVED_FIELD}) {
58 94         143 $value = $self->{$RESERVED_FIELD}{$name};
59              
60             # Defensive copying
61 94 100       389 $value = ($self->{$name} = dclone $value) if ref $value;
62             }
63              
64             # setter
65 222 100       422 $self->{$name} = $_[0] if @_;
66              
67 222         534 return $value;
68 32         105 };
69             }
70              
71             sub _make_accessor($$) {
72 10     10   74 no strict 'refs';
  10         20  
  10         1922  
73 16     16   35 my ($package, $name) = @_;
74              
75 16         64 *{"$package\::$name"} = sub {
76 71     71   101 my $self = shift;
77 71         114 my $value = $self->{$name};
78 71 100       148 $self->{$name} = $_[0] if @_;
79 71         112 $value;
80 16         43 };
81             }
82              
83             sub _mk_tracked_accessors($@) {
84 16     16   28 my $package = shift;
85 16         50 _make_tracked_accessor $package => $_ for @_;
86 16         42 @{(_package_info $package)->{tracked_fields}}{@_} = (1,) x @_;
  16         39  
87             }
88              
89             sub _mk_helpers($) {
90 10     10   84 no strict 'refs';
  10         17  
  10         7492  
91 16     16   36 my $package = shift;
92             my ($tracked_fields, $fields) =
93 16         24 @{_package_info $package}{qw(tracked_fields fields)};
  16         33  
94              
95             # cleate helper methods
96 16         66 *{"$package\::$FROM_HASH"} = sub {
97 22     22   8873 my $package = shift;
98 22 100       69 my %modified = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  18         61  
99              
100 22         42 my %origin;
101 22         48 for my $name (keys %$tracked_fields) {
102 44 100       115 $origin{$name} = delete $modified{$name} if exists $modified{$name};
103             }
104              
105 22         54 $modified{$RESERVED_FIELD} = \%origin;
106 22         50 bless \%modified, $package;
107 16         66 };
108              
109 16         54 *{"$package\::$RAW"} = sub {
110 49     49   464 my ($self) = @_;
111              
112             my %hash = (
113             (map {
114             # Don't store undefined values.
115 49         129 my $v = $self->$_;
  147         270  
116 147 100       352 defined $v ? ($_ => $v) : ();
117             } keys %$tracked_fields, keys %$fields),
118             );
119              
120 49         143 return \%hash;
121 16         54 };
122              
123 16         58 *{"$package\::$TO_HASH"} = sub {
124 31     31   1707 my ($self) = @_;
125 31         84 my $raw = $self->$RAW;
126              
127             # Move published data for cleaning.
128 31   100     125 $self->{$RESERVED_FIELD} ||= {};
129             $self->{$RESERVED_FIELD}{$_} = delete $self->{$_}
130 31         64 for grep { exists $self->{$_} } keys %$tracked_fields;
  62         198  
131              
132 31         77 return $raw;
133 16         42 };
134              
135 16         57 *{"$package\::$IS_MODIFIED"} = sub {
136 396     396   5138 my ($self, $field) = @_;
137 396 100   178   1111 return any { $self->$IS_MODIFIED($_) } keys %$tracked_fields
  178         690  
138             unless $field;
139              
140 292 100       505 return unless $tracked_fields->{$field};
141 286 100       727 return defined $self->{$field} unless defined $self->{$RESERVED_FIELD};
142              
143             exists $self->{$field} &&
144 206 100       658 _is_different $self->{$field}, $self->{$RESERVED_FIELD}{$field};
145 16         60 };
146              
147 16         167 *{"$package\::$MODIFIED_FIELDS"} = sub {
148 36     36   539 my $self = shift;
149 36         97 grep { $self->$IS_MODIFIED($_) } keys %$tracked_fields;
  72         149  
150 16         54 };
151              
152 16         49 *{"$package\::$IS_NEW"} = sub {
153 60     60   100 my $self = shift;
154 60 100       232 exists $self->{$RESERVED_FIELD} ? 0 : 1;
155 16         42 };
156              
157 16         63 *{"$package\::$REVERT"} = sub {
158 17     17   428 my $self = shift;
159 17         66 delete $self->{$_} for keys %$tracked_fields;
160 16         40 };
161             }
162              
163             sub _mk_accessors($@) {
164 16     16   24 my $package = shift;
165 16         45 _make_accessor $package => $_ for @_;
166 16         33 @{(_package_info $package)->{fields}}{@_} = (1,) x @_;
  16         29  
167             }
168              
169             sub _mk_new($) {
170 10     10   71 no strict 'refs';
  10         21  
  10         2869  
171 11     11   21 my $package = shift;
172              
173 11         54 *{"$package\::$NEW"} = sub {
174 17     17   3681 my $package = shift;
175 17 100       67 my %modified = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  15         52  
176              
177 17         58 bless \%modified => $package;
178 11         29 };
179             }
180              
181             sub mk_tracked_accessors {
182 11     11 1 4034 (undef, my @tracked_fields) = @_;
183 11         31 my $package = caller(0);
184 11         44 _mk_tracked_accessors $package => @tracked_fields;
185 11         27 _mk_helpers $package;
186             }
187              
188             sub mk_accessors {
189 16     16 1 67 (undef, my @fields) = @_;
190 16         31 my $package = caller(0);
191 16         36 _mk_accessors $package => @fields;
192             }
193              
194             sub mk_new {
195 6     6 1 25 my $package = caller(0);
196 6         23 _mk_new $package;
197             }
198              
199             sub mk_new_and_tracked_accessors {
200 5     5 1 310 (undef, my @tracked_fields) = @_;
201 5         14 my $package = caller(0);
202 5         17 _mk_tracked_accessors $package => @tracked_fields;
203 5         15 _mk_helpers $package;
204 5         12 _mk_new $package;
205             }
206              
207             1;
208             __END__