File Coverage

blib/lib/Toggle.pm
Criterion Covered Total %
statement 132 132 100.0
branch 13 14 92.8
condition 10 12 83.3
subroutine 45 45 100.0
pod 15 26 57.6
total 215 229 93.8


line stmt bran cond sub pod time code
1             # ABSTRACT: Feature toggles for Perl
2             package Toggle;
3             $Toggle::VERSION = '0.003';
4 1     1   3039 use Moo;
  1         12821  
  1         6  
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 423 my ( $self, $feature ) = @_;
18              
19 10         20 $self->activate_percentage( $feature, 100 );
20             }
21              
22             sub deactivate {
23 5     5 1 48 my ( $self, $feature ) = @_;
24              
25             $self->_with_feature(
26             $feature,
27             sub {
28 5     5   13 shift->clear();
29             }
30 5         18 );
31             }
32              
33             sub activate_group {
34 23     23 1 500 my ( $self, $feature, $group ) = @_;
35              
36             $self->_with_feature(
37             $feature,
38             sub {
39 23     23   51 shift->add_group($group);
40             }
41 23         74 );
42             }
43              
44             sub deactivate_group {
45 4     4 1 43 my ( $self, $feature, $group ) = @_;
46              
47             $self->_with_feature(
48             $feature,
49             sub {
50 4     4   9 shift->remove_group($group);
51             }
52 4         16 );
53             }
54              
55             sub activate_user {
56 17     17 1 4374 my ( $self, $feature, $user ) = @_;
57              
58             $self->_with_feature(
59             $feature,
60             sub {
61 17     17   37 shift->add_user($user);
62             }
63 17         56 );
64             }
65              
66             sub deactivate_user {
67 4     4 1 652 my ( $self, $feature, $user ) = @_;
68              
69             $self->_with_feature(
70             $feature,
71             sub {
72 4     4   9 shift->remove_user($user);
73             }
74 4         14 );
75             }
76              
77             sub define_group {
78 10     10 1 1380 my ( $self, $group, $coderef ) = @_;
79              
80 10         35 $self->groups->{$group} = $coderef;
81             }
82              
83             sub is_active {
84 1522     1522 1 269363 my ( $self, $feature, $user ) = @_;
85              
86 1522         2911 $feature = $self->get($feature);
87 1522         25822 return $feature->is_active( $self, $user );
88             }
89              
90             sub activate_percentage {
91 23     23 1 755 my ( $self, $feature, $percentage ) = @_;
92              
93             $self->_with_feature(
94             $feature,
95             sub {
96 23     23   56 shift->percentage($percentage);
97             }
98 23         91 );
99             }
100              
101             sub deactivate_percentage {
102 1     1 1 12 my ( $self, $feature ) = @_;
103              
104 1         3 $self->activate_percentage( $feature, 0 );
105             }
106              
107             sub is_active_in_group {
108 6     6 0 9 my ( $self, $group, $user ) = @_;
109              
110 6   100 1   30 my $g = $self->groups->{$group} || sub {0};
  1         5  
111 6         15 return $g->($user);
112             }
113              
114             sub get {
115 1900     1900 0 2439 my ( $self, $feature ) = @_;
116              
117 1900         4249 my $string = $self->storage->get( _key($feature) );
118              
119 1900 100       9354 if ($string) {
120 1864         38993 return Toggle::Feature->new( name => $feature, string => $string );
121             }
122             else {
123 36         755 my $f = Toggle::Feature->new( name => $feature );
124 36         174 $self->_save($f);
125 36         217 return $f;
126             }
127             }
128              
129             sub add_feature {
130 118     118 1 602 my ( $self, $feature ) = @_;
131              
132 118         209 my @features = $self->features();
133 118 100       883 if ( !grep { $_ eq $feature } @features ) {
  79         266  
134 39         65 push @features, $feature;
135             }
136              
137 118         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         6 $self->storage->del( _key($feature) );
144              
145 2         11 my @features = grep { $_ ne $feature } $self->features();
  2         14  
146 2         15 $self->storage->set( _features_key(), join ",", @features );
147             }
148              
149             sub features {
150 131     131 1 1814 my $self = shift;
151              
152 131   100     263 return split ',', ( $self->storage->get( _features_key() ) || "" );
153             }
154              
155             sub set_variants {
156 2     2 1 239 my ( $self, $feature, $variants ) = @_;
157              
158 2         6 $feature = $self->get($feature);
159              
160 2         7 $feature->variants($variants);
161              
162 2         6 $self->_save($feature);
163             }
164              
165             sub variant {
166 300     300 1 46958 my ( $self, $feature, $user ) = @_;
167              
168 300         553 return $self->get($feature)->variant($user);
169             }
170              
171             sub _key {
172 2016     2016   2583 my $name = shift;
173 2016         6644 return "feature:$name";
174             }
175              
176             sub _features_key {
177 251     251   707 return "feature:__features__";
178             }
179              
180             sub _with_feature {
181 76     76   109 my ( $self, $feature, $coderef ) = @_;
182              
183 76         138 my $f = $self->get($feature);
184 76         804 $coderef->($f);
185 76         718 $self->_save($f);
186             }
187              
188             sub _save {
189 114     114   146 my ( $self, $feature ) = @_;
190              
191 114         291 $self->storage->set( _key( $feature->name ), $feature->serialize() );
192 114         702 $self->add_feature( $feature->name );
193             }
194              
195             package Toggle::Feature;
196             $Toggle::Feature::VERSION = '0.003';
197 1     1   2454 use Moo;
  1         2  
  1         5  
198 1     1   880 use String::CRC32;
  1         378  
  1         57  
199 1     1   6 use Scalar::Util qw(blessed);
  1         1  
  1         936  
200              
201             has name => ( is => 'rw' );
202             has percentage => ( is => 'rw', default => sub { 0 } );
203             has users => ( is => 'rw', default => sub { {} } );
204             has groups => ( is => 'rw', default => sub { {} } );
205             has variants => ( is => 'rw', default => sub { [] } );
206              
207             sub BUILDARGS {
208 1900     1900 0 16295 my ( $class, %args ) = @_;
209              
210 1900 100       4606 if ( $args{string} ) {
211             my ( $raw_percentage, $raw_users, $raw_groups, $raw_variants )
212 1864         6601 = split /\|/, $args{string};
213              
214 1864         3486 $args{percentage} = $raw_percentage;
215 1864         2575 @{ $args{users} }{ split /,/, $raw_users } = ();
  1864         3747  
216 1864         2466 @{ $args{groups} }{ split /,/, $raw_groups } = ();
  1864         3068  
217 1864   100     5946 @{ $args{variants} } = split /,/, $raw_variants || '';
  1864         5151  
218             }
219              
220 1900         36983 return \%args;
221             }
222              
223             sub serialize {
224 114     114 0 152 my $self = shift;
225              
226             return join '|',
227             $self->percentage,
228 114         289 join( ',', keys %{ $self->users } ),
229 114         259 join( ',', keys %{ $self->groups } ),
230 114         178 join( ',', @{ $self->variants } );
  114         489  
231             }
232              
233             sub add_user {
234 17     17 0 23 my ( $self, $user ) = @_;
235              
236 17         40 $self->users->{ _user_id($user) } = ();
237             }
238              
239             sub remove_user {
240 4     4 0 7 my ( $self, $user ) = @_;
241              
242 4         11 delete $self->users->{ _user_id($user) };
243             }
244              
245             sub add_group {
246 23     23 0 31 my ( $self, $group ) = @_;
247              
248 23         59 $self->groups->{$group} = ();
249             }
250              
251             sub remove_group {
252 4     4 0 7 my ( $self, $group ) = @_;
253              
254 4         11 delete $self->groups->{$group};
255             }
256              
257             sub clear {
258 5     5 0 10 my $self = shift;
259              
260 5         13 $self->users( {} );
261 5         13 $self->groups( {} );
262 5         24 $self->percentage(0);
263 5         21 $self->variants( [] );
264             }
265              
266             sub variant {
267 300     300 0 4559 my ( $self, $user ) = @_;
268              
269 300         336 my $percentage = 0;
270 300         479 my $user_percentage = crc32( _user_id($user) ) % 100;
271 300         8137 my @variants = @{ $self->variants };
  300         972  
272              
273 300         745 for ( my $i = 0; $i < @variants; $i += 2 ) {
274 548         898 $percentage += $variants[ $i + 1 ];
275              
276 548 100       2328 return $variants[$i] if $user_percentage < $percentage;
277             }
278              
279 117         698 return '';
280             }
281              
282             sub is_active {
283 1522     1522 0 2048 my ( $self, $toggle, $user ) = @_;
284              
285 1522 100       2738 if ( !defined $user ) {
286 2         17 return $self->percentage == 100;
287             }
288             else {
289             return
290 1520   100     2673 $self->_is_user_in_percentage($user)
291             || $self->_is_user_in_active_users($user)
292             || $self->_is_user_in_active_group( $user, $toggle );
293             }
294             }
295              
296             sub _is_user_in_percentage {
297 1520     1520   1667 my ( $self, $user ) = @_;
298              
299 1520         2563 return crc32( _user_id($user) ) % 100 < $self->percentage;
300             }
301              
302             sub _is_user_in_active_users {
303 1255     1255   40339 my ( $self, $user ) = @_;
304              
305 1255         2700 return exists $self->users->{ _user_id($user) };
306             }
307              
308             sub _is_user_in_active_group {
309 1252     1252   34658 my ( $self, $user, $toggle ) = @_;
310              
311 1252         1515 for my $group ( keys %{ $self->groups } ) {
  1252         3667  
312 6 100       13 return 1 if $toggle->is_active_in_group( $group, $user );
313             }
314              
315 1249         8194 return;
316             }
317              
318             sub _user_id {
319 3096     3096   3350 my $user = shift;
320              
321 3096 50 33     21599 return blessed $user && $user->can('id') ? $user->id : $user;
322             }
323              
324             1;