File Coverage

blib/lib/Toggle.pm
Criterion Covered Total %
statement 108 108 100.0
branch 10 10 100.0
condition 7 7 100.0
subroutine 40 40 100.0
pod 13 23 56.5
total 178 188 94.6


line stmt bran cond sub pod time code
1             # ABSTRACT: Feature toggles for Perl
2             package Toggle;
3              
4 1     1   5553 use Moo;
  1         51859  
  1         9  
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 554 my ( $self, $feature ) = @_;
18              
19 10         33 $self->activate_percentage( $feature, 100 );
20             }
21              
22             sub deactivate {
23 5     5 1 58 my ( $self, $feature ) = @_;
24              
25             $self->_with_feature(
26             $feature,
27             sub {
28 5     5   17 shift->clear();
29             }
30 5         21 );
31             }
32              
33             sub activate_group {
34 23     23 1 635 my ( $self, $feature, $group ) = @_;
35              
36             $self->_with_feature(
37             $feature,
38             sub {
39 23     23   55 shift->add_group($group);
40             }
41 23         122 );
42             }
43              
44             sub deactivate_group {
45 4     4 1 48 my ( $self, $feature, $group ) = @_;
46              
47             $self->_with_feature(
48             $feature,
49             sub {
50 4     4   10 shift->remove_group($group);
51             }
52 4         16 );
53             }
54              
55             sub activate_user {
56 17     17 1 5392 my ( $self, $feature, $user ) = @_;
57              
58             $self->_with_feature(
59             $feature,
60             sub {
61 17     17   52 shift->add_user($user);
62             }
63 17         112 );
64             }
65              
66             sub deactivate_user {
67 4     4 1 792 my ( $self, $feature, $user ) = @_;
68              
69             $self->_with_feature(
70             $feature,
71             sub {
72 4     4   13 shift->remove_user($user);
73             }
74 4         18 );
75             }
76              
77             sub define_group {
78 10     10 1 1779 my ( $self, $group, $coderef ) = @_;
79              
80 10         47 $self->groups->{$group} = $coderef;
81             }
82              
83             sub is_active {
84 642     642 1 138431 my ( $self, $feature, $user ) = @_;
85              
86 642         1365 $feature = $self->get($feature);
87 642         19403 return $feature->is_active( $self, $user );
88             }
89              
90             sub activate_percentage {
91 23     23 1 913 my ( $self, $feature, $percentage ) = @_;
92              
93             $self->_with_feature(
94             $feature,
95             sub {
96 23     23   74 shift->percentage($percentage);
97             }
98 23         118 );
99             }
100              
101             sub deactivate_percentage {
102 1     1 1 17 my ( $self, $feature ) = @_;
103              
104 1         4 $self->activate_percentage( $feature, 0 );
105             }
106              
107             sub is_active_in_group {
108 6     6 0 13 my ( $self, $group, $user ) = @_;
109              
110 6   100 1   28 my $g = $self->groups->{$group} || sub {0};
  1         6  
111 6         19 return $g->($user);
112             }
113              
114             sub get {
115 718     718 0 974 my ( $self, $feature ) = @_;
116              
117 718         1922 my $string = $self->storage->get( _key($feature) );
118              
119 718 100       4488 if ($string) {
120 684         18707 return Toggle::Feature->new( name => $feature, string => $string );
121             }
122             else {
123 34         921 my $f = Toggle::Feature->new( name => $feature );
124 34         197 $self->_save($f);
125 34         222 return $f;
126             }
127             }
128              
129             sub add_feature {
130 114     114 1 615 my ( $self, $feature ) = @_;
131              
132 114         217 my @features = $self->features();
133 114 100       981 if ( !grep { $_ eq $feature } @features ) {
  77         282  
134 37         64 push @features, $feature;
135             }
136              
137 114         267 $self->storage->set( _features_key(), join ",", @features );
138             }
139              
140             sub remove_feature {
141 2     2 1 16 my ( $self, $feature ) = @_;
142              
143 2         10 $self->storage->del( _key($feature) );
144              
145 2         12 my @features = grep { $_ ne $feature } $self->features();
  2         17  
146 2         8 $self->storage->set( _features_key(), join ",", @features );
147             }
148              
149             sub features {
150 127     127 1 2205 my $self = shift;
151              
152 127   100     379 return split ',', ( $self->storage->get( _features_key() ) || "" );
153             }
154              
155             sub _key {
156 830     830   1125 my $name = shift;
157 830         3050 return "feature:$name";
158             }
159              
160             sub _features_key {
161 243     243   753 return "feature:__features__";
162             }
163              
164             sub _with_feature {
165 76     76   130 my ( $self, $feature, $coderef ) = @_;
166              
167 76         151 my $f = $self->get($feature);
168 76         1563 $coderef->($f);
169 76         934 $self->_save($f);
170             }
171              
172             sub _save {
173 110     110   152 my ( $self, $feature ) = @_;
174              
175 110         330 $self->storage->set( _key( $feature->name ), $feature->serialize() );
176 110         1045 $self->add_feature( $feature->name );
177             }
178              
179             package Toggle::Feature;
180              
181 1     1   4049 use Moo;
  1         3  
  1         12  
182 1     1   1233 use String::CRC32;
  1         2264  
  1         882  
183              
184             has name => ( is => 'rw' );
185             has percentage => ( is => 'rw', default => sub { 0 } );
186             has users => ( is => 'rw', default => sub { {} } );
187             has groups => ( is => 'rw', default => sub { {} } );
188              
189             sub BUILDARGS {
190 718     718 0 8167 my ( $class, %args ) = @_;
191              
192 718 100       1805 if ( $args{string} ) {
193 684         2442 my ( $raw_percentage, $raw_users, $raw_groups ) = split '\|',
194             $args{string};
195 684         1369 $args{percentage} = $raw_percentage;
196 684         1071 @{ $args{users} }{ split ',', $raw_users } = ();
  684         1526  
197 684         1050 @{ $args{groups} }{ split ',', $raw_groups } = ();
  684         1470  
198             }
199              
200 718         17513 return \%args;
201             }
202              
203             sub serialize {
204 110     110 0 131 my $self = shift;
205              
206 110         306 return join '|',
207             $self->percentage,
208 110         606 join( ',', keys %{ $self->users } ),
209 110         198 join( ',', keys %{ $self->groups } );
210             }
211              
212             sub add_user {
213 17     17 0 26 my ( $self, $user ) = @_;
214              
215 17         67 $self->users->{ $user->id } = ();
216             }
217              
218             sub remove_user {
219 4     4 0 7 my ( $self, $user ) = @_;
220              
221 4         30 delete $self->users->{ $user->id };
222             }
223              
224             sub add_group {
225 23     23 0 35 my ( $self, $group ) = @_;
226              
227 23         69 $self->groups->{$group} = ();
228             }
229              
230             sub remove_group {
231 4     4 0 7 my ( $self, $group ) = @_;
232              
233 4         15 delete $self->groups->{$group};
234             }
235              
236             sub clear {
237 5     5 0 8 my $self = shift;
238              
239 5         15 $self->users( {} );
240 5         13 $self->groups( {} );
241 5         13 $self->percentage(0);
242             }
243              
244             sub is_active {
245 642     642 0 1187 my ( $self, $toggle, $user ) = @_;
246              
247 642 100       1206 if ( !defined $user ) {
248 2         20 return $self->percentage == 100;
249             }
250             else {
251             return
252 640   100     1176 $self->_is_user_in_percentage($user)
253             || $self->_is_user_in_active_users($user)
254             || $self->_is_user_in_active_group( $user, $toggle );
255             }
256             }
257              
258             sub _is_user_in_percentage {
259 640     640   966 my ( $self, $user ) = @_;
260              
261 640         1987 return crc32( $user->id ) % 100 < $self->percentage;
262             }
263              
264             sub _is_user_in_active_users {
265 566     566   21517 my ( $self, $user ) = @_;
266              
267 566         1719 return exists $self->users->{ $user->id };
268             }
269              
270             sub _is_user_in_active_group {
271 563     563   20806 my ( $self, $user, $toggle ) = @_;
272              
273 563         621 for my $group ( keys %{ $self->groups } ) {
  563         2193  
274 6 100       21 return 1 if $toggle->is_active_in_group( $group, $user );
275             }
276              
277 560         4825 return;
278             }
279              
280             1;