File Coverage

blib/lib/Redis/LeaderBoard.pm
Criterion Covered Total %
statement 14 83 16.8
branch 0 36 0.0
condition 0 3 0.0
subroutine 5 18 27.7
pod 11 12 91.6
total 30 152 19.7


line stmt bran cond sub pod time code
1             package Redis::LeaderBoard;
2 11     11   1364951 use 5.008001;
  11         42  
3             our $VERSION = "1.10";
4 11     11   797 use Mouse;
  11         34238  
  11         95  
5 11     11   4294 use Mouse::Util::TypeConstraints;
  11         35  
  11         84  
6 11     11   7085 use Redis::LeaderBoard::Member;
  11         25  
  11         1599  
7              
8             has key => (
9             is => 'ro',
10             isa => 'Str',
11             required => 1,
12             );
13              
14             has redis => (
15             is => 'ro',
16             isa => 'Object',
17             required => 1,
18             );
19              
20             enum 'Redis::LeaderBoard::Order' => qw/asc desc/;
21             has order => (
22             is => 'ro',
23             isa => 'Redis::LeaderBoard::Order',
24             default => 'desc',
25             );
26              
27             has is_asc => (
28             is => 'ro',
29             isa => 'Bool',
30             lazy => 1,
31             default => sub { shift->order eq 'asc' },
32             );
33              
34             has expire_at => (
35             is => 'ro',
36             isa => 'Int',
37             );
38              
39             has limit => (
40             is => 'ro',
41             isa => 'Int',
42             );
43              
44 11     11   60 no Mouse;
  11         17  
  11         58  
45              
46             sub find_member {
47 0     0 1   my ($self, $member) = @_;
48              
49 0           Redis::LeaderBoard::Member->new(
50             member => $member,
51             leader_board => $self,
52             );
53             }
54              
55             sub set_score {
56 0     0 1   my ($self, @member_and_scores) = @_;
57 0           @member_and_scores = reverse @member_and_scores;
58 0           $self->redis->zadd($self->key, @member_and_scores);
59 0           $self->_set_expire_and_limit;
60             }
61              
62             sub get_score {
63 0     0 1   my ($self, $member) = @_;
64 0           $self->redis->zscore($self->key, $member);
65             }
66              
67             sub incr_score {
68 0     0 1   my ($self, $member, $score) = @_;
69 0 0         $score = defined $score ? $score : 1;
70              
71 0           my $ret = $self->redis->zincrby($self->key, $score, $member);
72 0           $self->_set_expire_and_limit;
73 0           $ret;
74             }
75              
76             sub decr_score {
77 0     0 1   my ($self, $member, $score) = @_;
78 0 0         $score = defined $score ? $score : 1;
79              
80 0           my $ret = $self->redis->zincrby($self->key, -$score, $member);
81 0           $self->_set_expire_and_limit;
82 0           $ret;
83             }
84              
85             sub _set_expire_and_limit {
86 0     0     my $self = shift;
87 0 0         $self->redis->expireat($self->key, $self->expire_at) if $self->expire_at;
88              
89 0 0         if ($self->limit) {
90 0           my $over = $self->member_count - $self->limit;
91 0 0         if ($over > 0) {
92 0           my ($from, $to) = (0, $over - 1);
93 0 0         if ($self->is_asc) {
94 0           ($from, $to) = (-$over, -1)
95             }
96 0           $self->redis->zremrangebyrank($self->key, $from, $to);
97             }
98             }
99             }
100              
101             sub remove {
102 0     0 1   my ($self, @members) = @_;
103              
104 0           $self->redis->zrem($self->key, @members);
105             }
106              
107             sub get_sorted_order {
108 0     0 1   my ($self, $member) = @_;
109              
110 0 0         my $method = $self->is_asc ? 'zrank' : 'zrevrank';
111 0           $self->redis->$method($self->key, $member);
112             }
113              
114             sub get_rank_with_score {
115 0     0 1   my ($self, $member) = @_;
116 0           my $redis = $self->redis;
117              
118 0           my $score = $self->get_score($member);
119 0 0         return unless defined $score;
120              
121 0           my $rank = $self->get_rank_by_score($score);
122 0           ($rank, $score);
123             }
124              
125             sub get_rank {
126 0     0 1   my ($self, $member) = @_;
127              
128 0           my ($rank) = $self->get_rank_with_score($member);
129 0           $rank;
130             }
131              
132             sub rankings {
133 0     0 1   my ($self, %args) = @_;
134 0 0         my $limit = exists $args{limit} ? $args{limit} : $self->member_count;
135 0 0         my $offset = exists $args{offset} ? $args{offset} : 0;
136              
137 0 0         my $range_method = $self->is_asc ? 'zrange' : 'zrevrange';
138              
139 0           my $members_with_scores = $self->redis->$range_method($self->key, $offset, $offset + $limit - 1, 'WITHSCORES');
140 0 0         return [] unless @$members_with_scores;
141              
142 0           my @rankings;
143 0           my ($current_rank, $current_target_score, $same_score_members);
144 0           while (my ($member, $score) = splice @$members_with_scores, 0, 2) {
145 0 0         if (!$current_rank) {
    0          
146 0           $current_rank = $self->get_rank_by_score($score);
147 0           $same_score_members = $offset - $current_rank + 2;
148 0           $current_target_score = $score;
149             }
150             elsif ($score == $current_target_score) {
151 0           $same_score_members++;
152             }
153             else {
154 0           $current_target_score = $score;
155 0           $current_rank = $current_rank + $same_score_members;
156 0           $same_score_members = 1;
157             }
158 0           push @rankings, +{
159             member => $member,
160             score => $score,
161             rank => $current_rank,
162             };
163             }
164              
165 0           \@rankings;
166             }
167              
168             sub get_rank_by_score {
169 0     0 0   my ($self, $score) = @_;
170              
171 0 0         my ($min, $max) = $self->is_asc ? ('-inf', "($score") : ("($score", 'inf');
172 0           return $self->member_count($min, $max) + 1;
173             }
174              
175             sub member_count {
176 0     0 1   my ($self, $from, $to) = @_;
177              
178 0 0 0       if (!$from && !$to) {
179 0           $self->redis->zcard($self->key);
180             }
181             else {
182 0 0         $from = defined $from ? $from : '-inf';
183 0 0         $to = defined $to ? $to : 'inf';
184 0           $self->redis->zcount($self->key, $from, $to);
185             }
186             }
187              
188             1;
189             __END__