File Coverage

blib/lib/Toggle.pm
Criterion Covered Total %
statement 127 127 100.0
branch 12 12 100.0
condition 9 9 100.0
subroutine 43 43 100.0
pod 15 26 57.6
total 206 217 94.9


line stmt bran cond sub pod time code
1             # ABSTRACT: Feature toggles for Perl
2             package Toggle;
3             $Toggle::VERSION = '0.002';
4 1     1   2882 use Moo;
  1         15827  
  1         8  
5              
6             has storage => ( is => 'ro', );
7              
8             has groups => (
9             is => 'rw',
10             default => sub {
11             { 'all' => sub {1}
12             };
13             },
14             );
15              
16             sub activate {
17 10     10 1 352 my ( $self, $feature ) = @_;
18              
19 10         18 $self->activate_percentage( $feature, 100 );
20             }
21              
22             sub deactivate {
23 5     5 1 41 my ( $self, $feature ) = @_;
24              
25             $self->_with_feature(
26             $feature,
27             sub {
28 5     5   10 shift->clear();
29             }
30 5         16 );
31             }
32              
33             sub activate_group {
34 23     23 1 480 my ( $self, $feature, $group ) = @_;
35              
36             $self->_with_feature(
37             $feature,
38             sub {
39 23     23   36 shift->add_group($group);
40             }
41 23         71 );
42             }
43              
44             sub deactivate_group {
45 4     4 1 38 my ( $self, $feature, $group ) = @_;
46              
47             $self->_with_feature(
48             $feature,
49             sub {
50 4     4   13 shift->remove_group($group);
51             }
52 4         14 );
53             }
54              
55             sub activate_user {
56 17     17 1 3809 my ( $self, $feature, $user ) = @_;
57              
58             $self->_with_feature(
59             $feature,
60             sub {
61 17     17   29 shift->add_user($user);
62             }
63 17         58 );
64             }
65              
66             sub deactivate_user {
67 4     4 1 570 my ( $self, $feature, $user ) = @_;
68              
69             $self->_with_feature(
70             $feature,
71             sub {
72 4     4   8 shift->remove_user($user);
73             }
74 4         12 );
75             }
76              
77             sub define_group {
78 10     10 1 1235 my ( $self, $group, $coderef ) = @_;
79              
80 10         31 $self->groups->{$group} = $coderef;
81             }
82              
83             sub is_active {
84 1522     1522 1 235840 my ( $self, $feature, $user ) = @_;
85              
86 1522         2543 $feature = $self->get($feature);
87 1522         22538 return $feature->is_active( $self, $user );
88             }
89              
90             sub activate_percentage {
91 23     23 1 643 my ( $self, $feature, $percentage ) = @_;
92              
93             $self->_with_feature(
94             $feature,
95             sub {
96 23     23   45 shift->percentage($percentage);
97             }
98 23         79 );
99             }
100              
101             sub deactivate_percentage {
102 1     1 1 14 my ( $self, $feature ) = @_;
103              
104 1         3 $self->activate_percentage( $feature, 0 );
105             }
106              
107             sub is_active_in_group {
108 6     6 0 7 my ( $self, $group, $user ) = @_;
109              
110 6   100 1   18 my $g = $self->groups->{$group} || sub {0};
  1         5  
111 6         13 return $g->($user);
112             }
113              
114             sub get {
115 1900     1900 0 1810 my ( $self, $feature ) = @_;
116              
117 1900         4342 my $string = $self->storage->get( _key($feature) );
118              
119 1900 100       7484 if ($string) {
120 1864         41156 return Toggle::Feature->new( name => $feature, string => $string );
121             }
122             else {
123 36         729 my $f = Toggle::Feature->new( name => $feature );
124 36         141 $self->_save($f);
125 36         153 return $f;
126             }
127             }
128              
129             sub add_feature {
130 118     118 1 420 my ( $self, $feature ) = @_;
131              
132 118         165 my @features = $self->features();
133 118 100       724 if ( !grep { $_ eq $feature } @features ) {
  79         191  
134 39         49 push @features, $feature;
135             }
136              
137 118         161 $self->storage->set( _features_key(), join ",", @features );
138             }
139              
140             sub remove_feature {
141 2     2 1 25 my ( $self, $feature ) = @_;
142              
143 2         6 $self->storage->del( _key($feature) );
144              
145 2         8 my @features = grep { $_ ne $feature } $self->features();
  2         10  
146 2         6 $self->storage->set( _features_key(), join ",", @features );
147             }
148              
149             sub features {
150 131     131 1 1449 my $self = shift;
151              
152 131   100     207 return split ',', ( $self->storage->get( _features_key() ) || "" );
153             }
154              
155             sub set_variants {
156 2     2 1 173 my ( $self, $feature, $variants ) = @_;
157              
158 2         6 $feature = $self->get($feature);
159              
160 2         5 $feature->variants($variants);
161              
162 2         5 $self->_save($feature);
163             }
164              
165             sub variant {
166 300     300 1 42429 my ( $self, $feature, $user ) = @_;
167              
168 300         518 return $self->get($feature)->variant($user);
169             }
170              
171             sub _key {
172 2016     2016   1898 my $name = shift;
173 2016         5531 return "feature:$name";
174             }
175              
176             sub _features_key {
177 251     251   525 return "feature:__features__";
178             }
179              
180             sub _with_feature {
181 76     76   79 my ( $self, $feature, $coderef ) = @_;
182              
183 76         114 my $f = $self->get($feature);
184 76         679 $coderef->($f);
185 76         658 $self->_save($f);
186             }
187              
188             sub _save {
189 114     114   112 my ( $self, $feature ) = @_;
190              
191 114         238 $self->storage->set( _key( $feature->name ), $feature->serialize() );
192 114         504 $self->add_feature( $feature->name );
193             }
194              
195             package Toggle::Feature;
196             $Toggle::Feature::VERSION = '0.002';
197 1     1   3587 use Moo;
  1         3  
  1         19  
198 1     1   1158 use String::CRC32;
  1         528  
  1         1261  
199              
200             has name => ( is => 'rw' );
201             has percentage => ( is => 'rw', default => sub { 0 } );
202             has users => ( is => 'rw', default => sub { {} } );
203             has groups => ( is => 'rw', default => sub { {} } );
204             has variants => ( is => 'rw', default => sub { [] } );
205              
206             sub BUILDARGS {
207 1900     1900 0 14299 my ( $class, %args ) = @_;
208              
209 1900 100       3842 if ( $args{string} ) {
210 1864         5167 my ( $raw_percentage, $raw_users, $raw_groups, $raw_variants )
211             = split /\|/, $args{string};
212              
213 1864         2726 $args{percentage} = $raw_percentage;
214 1864         2077 @{ $args{users} }{ split /,/, $raw_users } = ();
  1864         2972  
215 1864         1884 @{ $args{groups} }{ split /,/, $raw_groups } = ();
  1864         2436  
216 1864   100     5690 @{ $args{variants} } = split /,/, $raw_variants || '';
  1864         3354  
217             }
218              
219 1900         32268 return \%args;
220             }
221              
222             sub serialize {
223 114     114 0 85 my $self = shift;
224              
225 114         228 return join '|',
226             $self->percentage,
227 114         181 join( ',', keys %{ $self->users } ),
228 114         350 join( ',', keys %{ $self->groups } ),
229 114         125 join( ',', @{ $self->variants } );
230             }
231              
232             sub add_user {
233 17     17 0 23 my ( $self, $user ) = @_;
234              
235 17         52 $self->users->{ $user->id } = ();
236             }
237              
238             sub remove_user {
239 4     4 0 5 my ( $self, $user ) = @_;
240              
241 4         11 delete $self->users->{ $user->id };
242             }
243              
244             sub add_group {
245 23     23 0 24 my ( $self, $group ) = @_;
246              
247 23         47 $self->groups->{$group} = ();
248             }
249              
250             sub remove_group {
251 4     4 0 6 my ( $self, $group ) = @_;
252              
253 4         14 delete $self->groups->{$group};
254             }
255              
256             sub clear {
257 5     5 0 5 my $self = shift;
258              
259 5         9 $self->users( {} );
260 5         9 $self->groups( {} );
261 5         6 $self->percentage(0);
262 5         8 $self->variants( [] );
263             }
264              
265             sub variant {
266 300     300 0 4249 my ( $self, $user ) = @_;
267              
268 300         255 my $percentage = 0;
269 300         614 my $user_percentage = crc32( $user->id ) % 100;
270 300         7295 my @variants = @{ $self->variants };
  300         800  
271              
272 300         739 for ( my $i = 0; $i < @variants; $i += 2 ) {
273 548         718 $percentage += $variants[ $i + 1 ];
274              
275 548 100       1862 return $variants[$i] if $user_percentage < $percentage;
276             }
277              
278 117         538 return '';
279             }
280              
281             sub is_active {
282 1522     1522 0 1697 my ( $self, $toggle, $user ) = @_;
283              
284 1522 100       2522 if ( !defined $user ) {
285 2         14 return $self->percentage == 100;
286             }
287             else {
288             return
289 1520   100     2080 $self->_is_user_in_percentage($user)
290             || $self->_is_user_in_active_users($user)
291             || $self->_is_user_in_active_group( $user, $toggle );
292             }
293             }
294              
295             sub _is_user_in_percentage {
296 1520     1520   1332 my ( $self, $user ) = @_;
297              
298 1520         3112 return crc32( $user->id ) % 100 < $self->percentage;
299             }
300              
301             sub _is_user_in_active_users {
302 1255     1255   35587 my ( $self, $user ) = @_;
303              
304 1255         2432 return exists $self->users->{ $user->id };
305             }
306              
307             sub _is_user_in_active_group {
308 1252     1252   25371 my ( $self, $user, $toggle ) = @_;
309              
310 1252         994 for my $group ( keys %{ $self->groups } ) {
  1252         3759  
311 6 100       10 return 1 if $toggle->is_active_in_group( $group, $user );
312             }
313              
314 1249         6865 return;
315             }
316              
317             1;