File Coverage

blib/lib/Class/Accessor/TrackDirty.pm
Criterion Covered Total %
statement 133 133 100.0
branch 32 32 100.0
condition 14 16 87.5
subroutine 33 33 100.0
pod 4 4 100.0
total 216 218 99.0


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