leaderboard 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -1
- data/CHANGELOG.markdown +5 -1
- data/README.markdown +75 -62
- data/Rakefile +3 -39
- data/lib/leaderboard.rb +181 -135
- data/lib/leaderboard/version.rb +1 -1
- data/spec/leaderboard_spec.rb +167 -147
- data/spec/reverse_leaderboard_spec.rb +111 -91
- data/spec/version_spec.rb +1 -1
- metadata +5 -9
- data/spec/db/.gitkeep +0 -0
- data/spec/test.conf +0 -8
data/.rspec
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,8 +1,12 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## leaderboard 2.5.0 (2012-10-12)
|
4
|
+
|
5
|
+
* Added `members_from_rank_range` and `members_from_rank_range_in` methods to be able to retrieve members from a leaderboard in a given rank range.
|
6
|
+
|
3
7
|
## leaderboard 2.4.0 (2012-07-30)
|
4
8
|
|
5
|
-
* Added `all_leaders` and `all_leaders_from` methods to
|
9
|
+
* Added `all_leaders` and `all_leaders_from` methods to retrieve all members from a leaderboard. You may also use the aliases `all_members` or `all_members_from`.
|
6
10
|
|
7
11
|
## leaderboard 2.3.0 (2012-07-09)
|
8
12
|
|
data/README.markdown
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# leaderboard
|
2
2
|
|
3
|
-
Leaderboards backed by Redis
|
3
|
+
Leaderboards backed by [Redis](http://redis.io) in Ruby.
|
4
4
|
|
5
5
|
Builds off ideas proposed in http://blog.agoragames.com/2011/01/01/creating-high-score-tables-leaderboards-using-redis/.
|
6
6
|
|
@@ -14,33 +14,39 @@ or in your `Gemfile`
|
|
14
14
|
gem 'leaderboard'
|
15
15
|
```
|
16
16
|
|
17
|
-
Make sure your redis server is running! Redis configuration is outside the scope of this README, but
|
18
|
-
check out the Redis documentation
|
17
|
+
Make sure your redis server is running! Redis configuration is outside the scope of this README, but
|
18
|
+
check out the [Redis documentation](http://redis.io/documentation).
|
19
19
|
|
20
20
|
## Compatibility
|
21
21
|
|
22
22
|
The gem has been built and tested under Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3.
|
23
23
|
|
24
24
|
The gem is compatible with Redis 2.4.x and Redis 2.6.x.
|
25
|
-
|
25
|
+
|
26
26
|
## Usage
|
27
27
|
|
28
28
|
### Creating a leaderboard
|
29
29
|
|
30
|
+
Be sure to require the leaderboard library:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
require 'leaderboard'
|
34
|
+
```
|
35
|
+
|
30
36
|
Create a new leaderboard or attach to an existing leaderboard named 'highscores':
|
31
37
|
|
32
38
|
```ruby
|
33
39
|
highscore_lb = Leaderboard.new('highscores')
|
34
40
|
=> #<Leaderboard:0x0000010307b530 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.5)>>
|
35
41
|
```
|
36
|
-
|
42
|
+
|
37
43
|
If you need to pass in options for Redis, you can do this in the initializer:
|
38
44
|
|
39
45
|
```ruby
|
40
46
|
redis_options = {:host => 'localhost', :port => 6379, :db => 1}
|
41
|
-
=> {:host=>"localhost", :port=>6379, :db=>1}
|
47
|
+
=> {:host=>"localhost", :port=>6379, :db=>1}
|
42
48
|
highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
43
|
-
=> #<Leaderboard:0x00000103095200 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/1 (Redis v2.2.5)>>
|
49
|
+
=> #<Leaderboard:0x00000103095200 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/1 (Redis v2.2.5)>>
|
44
50
|
```
|
45
51
|
|
46
52
|
### Defining leaderboard options
|
@@ -54,29 +60,29 @@ DEFAULT_OPTIONS = {
|
|
54
60
|
}
|
55
61
|
```
|
56
62
|
|
57
|
-
The `DEFAULT_PAGE_SIZE` is 25.
|
63
|
+
The `DEFAULT_PAGE_SIZE` is 25.
|
58
64
|
|
59
|
-
You would use the option, `:reverse => true`, if you wanted a leaderboard sorted from lowest-to-highest score. You
|
65
|
+
You would use the option, `:reverse => true`, if you wanted a leaderboard sorted from lowest-to-highest score. You
|
60
66
|
may also set the `reverse` option on a leaderboard after you have created a new instance of a leaderboard.
|
61
67
|
|
62
68
|
You can pass in an existing connection to Redis using `:redis_connection` in the Redis options hash:
|
63
69
|
|
64
70
|
```ruby
|
65
71
|
redis = Redis.new
|
66
|
-
=> #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
|
72
|
+
=> #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
|
67
73
|
redis_options = {:redis_connection => redis}
|
68
|
-
=> {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
|
74
|
+
=> {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
|
69
75
|
highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
70
|
-
=> #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
|
76
|
+
=> #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
|
71
77
|
```
|
72
|
-
|
78
|
+
|
73
79
|
To use the same connection for multiple leaderboards, reset the options hash before instantiating more leaderboards:
|
74
80
|
|
75
81
|
```ruby
|
76
82
|
redis = Redis.new
|
77
|
-
=> #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
|
83
|
+
=> #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
|
78
84
|
redis_options = {:redis_connection => redis}
|
79
|
-
=> {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
|
85
|
+
=> {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
|
80
86
|
highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
81
87
|
redis_options = {:redis_connection => redis}
|
82
88
|
other_highscore_lb = Leaderboard.new('other_highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
@@ -86,9 +92,9 @@ You can set the page size to something other than the default page size (25):
|
|
86
92
|
|
87
93
|
```ruby
|
88
94
|
highscore_lb.page_size = 5
|
89
|
-
=> 5
|
95
|
+
=> 5
|
90
96
|
highscore_lb
|
91
|
-
=> #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=5, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
|
97
|
+
=> #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=5, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
|
92
98
|
```
|
93
99
|
|
94
100
|
### Ranking members in the leaderboard
|
@@ -99,7 +105,7 @@ Add members to your leaderboard using `rank_member`:
|
|
99
105
|
1.upto(10) do |index|
|
100
106
|
highscore_lb.rank_member("member_#{index}", index)
|
101
107
|
end
|
102
|
-
=> 1
|
108
|
+
=> 1
|
103
109
|
```
|
104
110
|
|
105
111
|
You can call `rank_member` with the same member and the leaderboard will be updated automatically.
|
@@ -108,76 +114,76 @@ Get some information about your leaderboard:
|
|
108
114
|
|
109
115
|
```ruby
|
110
116
|
highscore_lb.total_members
|
111
|
-
=> 10
|
117
|
+
=> 10
|
112
118
|
highscore_lb.total_pages
|
113
|
-
=> 1
|
119
|
+
=> 1
|
114
120
|
```
|
115
121
|
|
116
|
-
The `rank_member` call will also accept an optional hash of member data that could
|
117
|
-
be used to store other information about a given member in the leaderboard. This
|
118
|
-
may be useful in situations where you are storing member IDs in the leaderboard and
|
122
|
+
The `rank_member` call will also accept an optional hash of member data that could
|
123
|
+
be used to store other information about a given member in the leaderboard. This
|
124
|
+
may be useful in situations where you are storing member IDs in the leaderboard and
|
119
125
|
you want to be able to store a member name for display. Example:
|
120
126
|
|
121
127
|
```ruby
|
122
128
|
highscore_lb.rank_member('84849292', 1, {'username' => 'member_name'})
|
123
129
|
```
|
124
130
|
|
125
|
-
You can retrieve, update and remove the optional member data using the
|
126
|
-
`member_data_for`, `update_member_data` and `remove_member_data` calls. Example:
|
131
|
+
You can retrieve, update and remove the optional member data using the
|
132
|
+
`member_data_for`, `update_member_data` and `remove_member_data` calls. Example:
|
127
133
|
|
128
134
|
```ruby
|
129
135
|
highscore_lb.member_data_for('84849292')
|
130
136
|
=> {"username"=>"member_name"}
|
131
137
|
|
132
138
|
highscore_lb.update_member_data('84849292', {'last_updated' => Time.now, 'username' => 'updated_member_name'})
|
133
|
-
=> "OK"
|
139
|
+
=> "OK"
|
134
140
|
highscore_lb.member_data_for('84849292')
|
135
141
|
=> {"username"=>"updated_member_name", "last_updated"=>"2012-06-09 09:11:06 -0400"}
|
136
142
|
|
137
143
|
highscore_lb.remove_member_data('84849292')
|
138
144
|
```
|
139
|
-
|
145
|
+
|
140
146
|
Get some information about a specific member(s) in the leaderboard:
|
141
147
|
|
142
148
|
```ruby
|
143
149
|
highscore_lb.score_for('member_4')
|
144
|
-
=> 4.0
|
150
|
+
=> 4.0
|
145
151
|
highscore_lb.rank_for('member_4')
|
146
|
-
=> 7
|
152
|
+
=> 7
|
147
153
|
highscore_lb.rank_for('member_10')
|
148
|
-
=> 1
|
154
|
+
=> 1
|
149
155
|
```
|
150
156
|
|
151
|
-
### Retrieving members from the leaderboard
|
157
|
+
### Retrieving members from the leaderboard
|
152
158
|
|
153
159
|
Get page 1 in the leaderboard:
|
154
160
|
|
155
161
|
```ruby
|
156
162
|
highscore_lb.leaders(1)
|
157
|
-
=> [{:member=>"member_10", :rank=>1, :score=>10.0}, {:member=>"member_9", :rank=>2, :score=>9.0}, {:member=>"member_8", :rank=>3, :score=>8.0}, {:member=>"member_7", :rank=>4, :score=>7.0}, {:member=>"member_6", :rank=>5, :score=>6.0}, {:member=>"member_5", :rank=>6, :score=>5.0}, {:member=>"member_4", :rank=>7, :score=>4.0}, {:member=>"member_3", :rank=>8, :score=>3.0}, {:member=>"member_2", :rank=>9, :score=>2.0}, {:member=>"member_1", :rank=>10, :score=>1.0}]
|
163
|
+
=> [{:member=>"member_10", :rank=>1, :score=>10.0}, {:member=>"member_9", :rank=>2, :score=>9.0}, {:member=>"member_8", :rank=>3, :score=>8.0}, {:member=>"member_7", :rank=>4, :score=>7.0}, {:member=>"member_6", :rank=>5, :score=>6.0}, {:member=>"member_5", :rank=>6, :score=>5.0}, {:member=>"member_4", :rank=>7, :score=>4.0}, {:member=>"member_3", :rank=>8, :score=>3.0}, {:member=>"member_2", :rank=>9, :score=>2.0}, {:member=>"member_1", :rank=>10, :score=>1.0}]
|
158
164
|
```
|
159
|
-
|
160
|
-
You can pass various options to the calls `leaders`, `around_me` and `ranked_in_list`.
|
161
|
-
Valid options are `:with_scores`, `:with_rank`, `:with_member_data`, `:use_zero_index_for_rank`
|
162
|
-
and `:page_size`. Below is an example of retrieving the first page in the leaderboard
|
165
|
+
|
166
|
+
You can pass various options to the calls `leaders`, `around_me` and `ranked_in_list`.
|
167
|
+
Valid options are `:with_scores`, `:with_rank`, `:with_member_data`, `:use_zero_index_for_rank`
|
168
|
+
and `:page_size`. Below is an example of retrieving the first page in the leaderboard
|
163
169
|
without ranks:
|
164
170
|
|
165
171
|
```ruby
|
166
172
|
highscore_lb.leaders(1, :with_rank => false)
|
167
|
-
=> [{:member=>"member_10", :score=>9.0}, {:member=>"member_9", :score=>7.0}, {:member=>"member_8", :score=>5.0}, {:member=>"member_7", :score=>3.0}, {:member=>"member_6", :score=>1.0}, {:member=>"member_5", :score=>0.0}, {:member=>"member_4", :score=>0.0}, {:member=>"member_3", :score=>0.0}, {:member=>"member_2", :score=>0.0}, {:member=>"member_1", :score=>0.0}]
|
173
|
+
=> [{:member=>"member_10", :score=>9.0}, {:member=>"member_9", :score=>7.0}, {:member=>"member_8", :score=>5.0}, {:member=>"member_7", :score=>3.0}, {:member=>"member_6", :score=>1.0}, {:member=>"member_5", :score=>0.0}, {:member=>"member_4", :score=>0.0}, {:member=>"member_3", :score=>0.0}, {:member=>"member_2", :score=>0.0}, {:member=>"member_1", :score=>0.0}]
|
168
174
|
```
|
169
175
|
|
170
176
|
Below is an example of retrieving the first page in the leaderboard without scores or ranks:
|
171
177
|
|
172
178
|
```ruby
|
173
179
|
highscore_lb.leaders(1, :with_scores => false, :with_rank => false)
|
174
|
-
=> [{:member=>"member_10"}, {:member=>"member_9"}, {:member=>"member_8"}, {:member=>"member_7"}, {:member=>"member_6"}, {:member=>"member_5"}, {:member=>"member_4"}, {:member=>"member_3"}, {:member=>"member_2"}, {:member=>"member_1"}]
|
180
|
+
=> [{:member=>"member_10"}, {:member=>"member_9"}, {:member=>"member_8"}, {:member=>"member_7"}, {:member=>"member_6"}, {:member=>"member_5"}, {:member=>"member_4"}, {:member=>"member_3"}, {:member=>"member_2"}, {:member=>"member_1"}]
|
175
181
|
```
|
176
182
|
|
177
183
|
You can also use the `members` and `members_in` methods as aliases for the `leaders` and `leaders_in` methods.
|
178
184
|
|
179
|
-
There are also a few convenience methods to be able to retrieve all leaders from a given leaderboard. They are `all_leaders` and `all_leaders_from`. You
|
180
|
-
may also use the aliases `all_members` or `all_members_from`. Use any of these methods sparingly as all the information in the leaderboard will be returned.
|
185
|
+
There are also a few convenience methods to be able to retrieve all leaders from a given leaderboard. They are `all_leaders` and `all_leaders_from`. You
|
186
|
+
may also use the aliases `all_members` or `all_members_from`. Use any of these methods sparingly as all the information in the leaderboard will be returned.
|
181
187
|
|
182
188
|
Add more members to your leaderboard:
|
183
189
|
|
@@ -185,18 +191,18 @@ Add more members to your leaderboard:
|
|
185
191
|
50.upto(95) do |index|
|
186
192
|
highscore_lb.rank_member("member_#{index}", index)
|
187
193
|
end
|
188
|
-
=> 50
|
194
|
+
=> 50
|
189
195
|
highscore_lb.total_pages
|
190
|
-
=> 3
|
196
|
+
=> 3
|
191
197
|
```
|
192
|
-
|
198
|
+
|
193
199
|
Get an "Around Me" leaderboard page for a given member, which pulls members above and below the given member:
|
194
200
|
|
195
201
|
```ruby
|
196
202
|
highscore_lb.around_me('member_53')
|
197
|
-
=> [{:member=>"member_65", :rank=>31, :score=>65.0}, {:member=>"member_64", :rank=>32, :score=>64.0}, {:member=>"member_63", :rank=>33, :score=>63.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_61", :rank=>35, :score=>61.0}, {:member=>"member_60", :rank=>36, :score=>60.0}, {:member=>"member_59", :rank=>37, :score=>59.0}, {:member=>"member_58", :rank=>38, :score=>58.0}, {:member=>"member_57", :rank=>39, :score=>57.0}, {:member=>"member_56", :rank=>40, :score=>56.0}, {:member=>"member_55", :rank=>41, :score=>55.0}, {:member=>"member_54", :rank=>42, :score=>54.0}, {:member=>"member_53", :rank=>43, :score=>53.0}, {:member=>"member_52", :rank=>44, :score=>52.0}, {:member=>"member_51", :rank=>45, :score=>51.0}, {:member=>"member_50", :rank=>46, :score=>50.0}, {:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}, {:member=>"member_3", :rank=>54, :score=>3.0}, {:member=>"member_2", :rank=>55, :score=>2.0}]
|
203
|
+
=> [{:member=>"member_65", :rank=>31, :score=>65.0}, {:member=>"member_64", :rank=>32, :score=>64.0}, {:member=>"member_63", :rank=>33, :score=>63.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_61", :rank=>35, :score=>61.0}, {:member=>"member_60", :rank=>36, :score=>60.0}, {:member=>"member_59", :rank=>37, :score=>59.0}, {:member=>"member_58", :rank=>38, :score=>58.0}, {:member=>"member_57", :rank=>39, :score=>57.0}, {:member=>"member_56", :rank=>40, :score=>56.0}, {:member=>"member_55", :rank=>41, :score=>55.0}, {:member=>"member_54", :rank=>42, :score=>54.0}, {:member=>"member_53", :rank=>43, :score=>53.0}, {:member=>"member_52", :rank=>44, :score=>52.0}, {:member=>"member_51", :rank=>45, :score=>51.0}, {:member=>"member_50", :rank=>46, :score=>50.0}, {:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}, {:member=>"member_3", :rank=>54, :score=>3.0}, {:member=>"member_2", :rank=>55, :score=>2.0}]
|
198
204
|
```
|
199
|
-
|
205
|
+
|
200
206
|
Get rank and score for an arbitrary list of members (e.g. friends) from the leaderboard:
|
201
207
|
|
202
208
|
```ruby
|
@@ -208,7 +214,7 @@ Retrieve members from the leaderboard in a given score range:
|
|
208
214
|
|
209
215
|
```ruby
|
210
216
|
members = highscore_lb.members_from_score_range(4, 19)
|
211
|
-
=> [{:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}]
|
217
|
+
=> [{:member=>"member_10", :rank=>47, :score=>10.0}, {:member=>"member_9", :rank=>48, :score=>9.0}, {:member=>"member_8", :rank=>49, :score=>8.0}, {:member=>"member_7", :rank=>50, :score=>7.0}, {:member=>"member_6", :rank=>51, :score=>6.0}, {:member=>"member_5", :rank=>52, :score=>5.0}, {:member=>"member_4", :rank=>53, :score=>4.0}]
|
212
218
|
```
|
213
219
|
|
214
220
|
Retrieve a single member from the leaderboard at a given position:
|
@@ -218,7 +224,14 @@ members = highscore_lb.member_at(4)
|
|
218
224
|
=> {:member=>"member_92", :rank=>4, :score=>92.0}
|
219
225
|
```
|
220
226
|
|
221
|
-
|
227
|
+
Retrieve a range of members from the leaderboard within a given rank range:
|
228
|
+
|
229
|
+
```ruby
|
230
|
+
members = highscore_lb.members_from_rank_range(1, 5)
|
231
|
+
=> [{:member=>"member_95", :rank=>1, :score=>95.0}, {:member=>"member_94", :rank=>2, :score=>94.0}, {:member=>"member_93", :rank=>3, :score=>93.0}, {:member=>"member_92", :rank=>4, :score=>92.0}, {:member=>"member_91", :rank=>5, :score=>91.0}]
|
232
|
+
```
|
233
|
+
|
234
|
+
### Ranking multiple members in a leaderboard at once
|
222
235
|
|
223
236
|
Insert multiple data items for members and their associated scores:
|
224
237
|
|
@@ -245,7 +258,7 @@ Use this method to do bulk insert of data, but be mindful of the amount of data
|
|
245
258
|
remove_member_data(member): Remove the optional member data for a given member in the leaderboard
|
246
259
|
remove_member(member): Remove a member from the leaderboard
|
247
260
|
total_members: Total # of members in the leaderboard
|
248
|
-
total_pages: Total # of pages in the leaderboard given the leaderboard's page_size
|
261
|
+
total_pages: Total # of pages in the leaderboard given the leaderboard's page_size
|
249
262
|
total_members_in_score_range(min_score, max_score): Count the number of members within a score range in the leaderboard
|
250
263
|
change_score_for(member, delta): Change the score for a member by some amount delta (delta could be positive or negative)
|
251
264
|
rank_for(member): Retrieve the rank for a given member in the leaderboard
|
@@ -270,7 +283,7 @@ Check the [online documentation](http://rubydoc.info/github/agoragames/leaderboa
|
|
270
283
|
|
271
284
|
```ruby
|
272
285
|
highscore_lb = Leaderboard.new('highscores')
|
273
|
-
=> #<Leaderboard:0x0000010205fc50 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.5)>>
|
286
|
+
=> #<Leaderboard:0x0000010205fc50 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/0 (Redis v2.2.5)>>
|
274
287
|
|
275
288
|
insert_time = Benchmark.measure do
|
276
289
|
1.upto(10000000) do |index|
|
@@ -279,25 +292,25 @@ Check the [online documentation](http://rubydoc.info/github/agoragames/leaderboa
|
|
279
292
|
end
|
280
293
|
=> 323.070000 148.560000 471.630000 (942.068307)
|
281
294
|
```
|
282
|
-
|
295
|
+
|
283
296
|
Average time to request an arbitrary page from the leaderboard:
|
284
297
|
|
285
298
|
```ruby
|
286
299
|
requests_to_make = 50000
|
287
|
-
=> 50000
|
300
|
+
=> 50000
|
288
301
|
lb_request_time = 0
|
289
|
-
=> 0
|
302
|
+
=> 0
|
290
303
|
1.upto(requests_to_make) do
|
291
304
|
lb_request_time += Benchmark.measure do
|
292
305
|
highscore_lb.leaders(rand(highscore_lb.total_pages))
|
293
306
|
end.total
|
294
307
|
end
|
295
|
-
=> 1
|
308
|
+
=> 1
|
296
309
|
p lb_request_time / requests_to_make
|
297
310
|
0.001513999999999998
|
298
|
-
=> 0.001513999999999998
|
311
|
+
=> 0.001513999999999998
|
299
312
|
```
|
300
|
-
|
313
|
+
|
301
314
|
10 million random scores insert:
|
302
315
|
|
303
316
|
```ruby
|
@@ -308,7 +321,7 @@ Average time to request an arbitrary page from the leaderboard:
|
|
308
321
|
end
|
309
322
|
=> 338.480000 155.200000 493.680000 (2188.702475)
|
310
323
|
```
|
311
|
-
|
324
|
+
|
312
325
|
Average time to request an arbitrary page from the leaderboard:
|
313
326
|
|
314
327
|
```ruby
|
@@ -317,10 +330,10 @@ Average time to request an arbitrary page from the leaderboard:
|
|
317
330
|
highscore_lb.leaders(rand(highscore_lb.total_pages))
|
318
331
|
end.total
|
319
332
|
end
|
320
|
-
=> 1
|
333
|
+
=> 1
|
321
334
|
p lb_request_time / requests_to_make
|
322
335
|
0.0014615999999999531
|
323
|
-
=> 0.0014615999999999531
|
336
|
+
=> 0.0014615999999999531
|
324
337
|
```
|
325
338
|
|
326
339
|
### Bulk insert performance
|
@@ -340,12 +353,12 @@ Ranking multiple members at once:
|
|
340
353
|
|
341
354
|
```ruby
|
342
355
|
member_data = []
|
343
|
-
=> []
|
356
|
+
=> []
|
344
357
|
1.upto(1000000) do |index|
|
345
358
|
member_data << "member_#{index}"
|
346
359
|
member_data << index
|
347
360
|
end
|
348
|
-
=> 1
|
361
|
+
=> 1
|
349
362
|
insert_time = Benchmark.measure do
|
350
363
|
highscore_lb.rank_members(member_data)
|
351
364
|
end
|
@@ -361,9 +374,9 @@ The following ports have been made of the leaderboard gem.
|
|
361
374
|
* PHP: https://github.com/agoragames/php-leaderboard
|
362
375
|
* Python: https://github.com/agoragames/python-leaderboard
|
363
376
|
* Scala: https://github.com/agoragames/scala-leaderboard
|
364
|
-
|
377
|
+
|
365
378
|
## Contributing to leaderboard
|
366
|
-
|
379
|
+
|
367
380
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
368
381
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
369
382
|
* Fork the project
|
data/Rakefile
CHANGED
@@ -1,6 +1,5 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
|
3
|
-
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake'
|
4
3
|
require 'rspec/core/rake_task'
|
5
4
|
|
6
5
|
RSpec::Core::RakeTask.new(:spec) do |spec|
|
@@ -9,43 +8,8 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
|
|
9
8
|
# spec.ruby_opts = ['-w']
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
REDIS_CNF = File.join(REDIS_DIR, "test.conf")
|
14
|
-
REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
|
15
|
-
REDIS_LOCATION = ENV['REDIS_LOCATION']
|
16
|
-
|
17
|
-
task :default => :run
|
18
|
-
|
19
|
-
desc "Run tests and manage server start/stop"
|
20
|
-
task :run => [:start, :spec, :stop]
|
21
|
-
|
22
|
-
desc "Start the Redis server"
|
23
|
-
task :start do
|
24
|
-
redis_running = \
|
25
|
-
begin
|
26
|
-
File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
|
27
|
-
rescue Errno::ESRCH
|
28
|
-
FileUtils.rm REDIS_PID
|
29
|
-
false
|
30
|
-
end
|
31
|
-
|
32
|
-
if REDIS_LOCATION
|
33
|
-
system "#{REDIS_LOCATION}/redis-server #{REDIS_CNF}" unless redis_running
|
34
|
-
else
|
35
|
-
system "redis-server #{REDIS_CNF}" unless redis_running
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
desc "Stop the Redis server"
|
40
|
-
task :stop do
|
41
|
-
if File.exists?(REDIS_PID)
|
42
|
-
Process.kill "INT", File.read(REDIS_PID).to_i
|
43
|
-
FileUtils.rm REDIS_PID
|
44
|
-
end
|
45
|
-
end
|
11
|
+
task :default => :spec
|
46
12
|
|
47
13
|
task :test_rubies do
|
48
|
-
Rake::Task['start'].execute
|
49
14
|
system "rvm 1.8.7@leaderboard_gem,1.9.2@leaderboard_gem,1.9.3@leaderboard_gem do rake spec"
|
50
|
-
Rake::Task['stop'].execute
|
51
15
|
end
|
data/lib/leaderboard.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
require 'redis'
|
2
2
|
require 'leaderboard/version'
|
3
3
|
|
4
|
-
class Leaderboard
|
4
|
+
class Leaderboard
|
5
5
|
# Default page size: 25
|
6
6
|
DEFAULT_PAGE_SIZE = 25
|
7
|
-
|
7
|
+
|
8
8
|
# Default options when creating a leaderboard. Page size is 25 and reverse
|
9
9
|
# is set to false, meaning various methods will return results in
|
10
10
|
# highest-to-lowest order.
|
@@ -15,17 +15,17 @@ class Leaderboard
|
|
15
15
|
|
16
16
|
# Default Redis host: localhost
|
17
17
|
DEFAULT_REDIS_HOST = 'localhost'
|
18
|
-
|
18
|
+
|
19
19
|
# Default Redis post: 6379
|
20
|
-
DEFAULT_REDIS_PORT = 6379
|
21
|
-
|
20
|
+
DEFAULT_REDIS_PORT = 6379
|
21
|
+
|
22
22
|
# Default Redis options when creating a connection to Redis. The
|
23
23
|
# +DEFAULT_REDIS_HOST+ and +DEFAULT_REDIS_PORT+ will be passed.
|
24
24
|
DEFAULT_REDIS_OPTIONS = {
|
25
25
|
:host => DEFAULT_REDIS_HOST,
|
26
26
|
:port => DEFAULT_REDIS_PORT
|
27
27
|
}
|
28
|
-
|
28
|
+
|
29
29
|
# Default options when requesting data from a leaderboard.
|
30
30
|
# +:with_scores+ true: Return scores along with the member names.
|
31
31
|
# +:with_rank+ true: Return ranks along with the member names.
|
@@ -33,26 +33,26 @@ class Leaderboard
|
|
33
33
|
# +:use_zero_index_for_rank+ false: If you want to 0-index ranks.
|
34
34
|
# +:page_size+ nil: The default page size will be used.
|
35
35
|
DEFAULT_LEADERBOARD_REQUEST_OPTIONS = {
|
36
|
-
:with_scores => true,
|
37
|
-
:with_rank => true,
|
36
|
+
:with_scores => true,
|
37
|
+
:with_rank => true,
|
38
38
|
:with_member_data => false,
|
39
39
|
:use_zero_index_for_rank => false,
|
40
40
|
:page_size => nil
|
41
41
|
}
|
42
|
-
|
42
|
+
|
43
43
|
# Name of the leaderboard.
|
44
44
|
attr_reader :leaderboard_name
|
45
45
|
|
46
|
-
# Page size to be used when paging through the leaderboard.
|
46
|
+
# Page size to be used when paging through the leaderboard.
|
47
47
|
attr_reader :page_size
|
48
48
|
|
49
49
|
# Determines whether or not various leaderboard methods return their
|
50
|
-
# data in highest-to-lowest (+:reverse+ false) or
|
50
|
+
# data in highest-to-lowest (+:reverse+ false) or
|
51
51
|
# lowest-to-highest (+:reverse+ true)
|
52
52
|
attr_accessor :reverse
|
53
|
-
|
53
|
+
|
54
54
|
# Create a new instance of a leaderboard.
|
55
|
-
#
|
55
|
+
#
|
56
56
|
# @param leaderboard [String] Name of the leaderboard.
|
57
57
|
# @param options [Hash] Options for the leaderboard such as +:page_size+.
|
58
58
|
# @param redis_options [Hash] Options for configuring Redis.
|
@@ -63,51 +63,51 @@ class Leaderboard
|
|
63
63
|
# leaderboard = Leaderboard.new('highscores', {:page_size => 10})
|
64
64
|
def initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS)
|
65
65
|
@leaderboard_name = leaderboard_name
|
66
|
-
|
66
|
+
|
67
67
|
@reverse = options[:reverse]
|
68
68
|
@page_size = options[:page_size]
|
69
69
|
if @page_size.nil? || @page_size < 1
|
70
70
|
@page_size = DEFAULT_PAGE_SIZE
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
@redis_connection = redis_options[:redis_connection]
|
74
74
|
unless @redis_connection.nil?
|
75
75
|
redis_options.delete(:redis_connection)
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
@redis_connection = Redis.new(redis_options) if @redis_connection.nil?
|
79
79
|
end
|
80
|
-
|
81
|
-
# Set the page size to be used when paging through the leaderboard. This method
|
82
|
-
# also has the side effect of setting the page size to the +DEFAULT_PAGE_SIZE+
|
80
|
+
|
81
|
+
# Set the page size to be used when paging through the leaderboard. This method
|
82
|
+
# also has the side effect of setting the page size to the +DEFAULT_PAGE_SIZE+
|
83
83
|
# if the page size is less than 1.
|
84
|
-
#
|
84
|
+
#
|
85
85
|
# @param page_size [int] Page size.
|
86
86
|
def page_size=(page_size)
|
87
87
|
page_size = DEFAULT_PAGE_SIZE if page_size < 1
|
88
88
|
|
89
89
|
@page_size = page_size
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
# Disconnect the Redis connection.
|
93
93
|
def disconnect
|
94
94
|
@redis_connection.client.disconnect
|
95
95
|
end
|
96
|
-
|
96
|
+
|
97
97
|
# Delete the current leaderboard.
|
98
98
|
def delete_leaderboard
|
99
99
|
delete_leaderboard_named(@leaderboard_name)
|
100
100
|
end
|
101
|
-
|
101
|
+
|
102
102
|
# Delete the named leaderboard.
|
103
103
|
#
|
104
104
|
# @param leaderboard_name [String] Name of the leaderboard.
|
105
105
|
def delete_leaderboard_named(leaderboard_name)
|
106
106
|
@redis_connection.del(leaderboard_name)
|
107
107
|
end
|
108
|
-
|
108
|
+
|
109
109
|
# Rank a member in the leaderboard.
|
110
|
-
#
|
110
|
+
#
|
111
111
|
# @param member [String] Member name.
|
112
112
|
# @param score [float] Member score.
|
113
113
|
# @param member_data [Hash] Optional member data.
|
@@ -116,7 +116,7 @@ class Leaderboard
|
|
116
116
|
end
|
117
117
|
|
118
118
|
# Rank a member in the named leaderboard.
|
119
|
-
#
|
119
|
+
#
|
120
120
|
# @param leaderboard_name [String] Name of the leaderboard.
|
121
121
|
# @param member [String] Member name.
|
122
122
|
# @param score [float] Member score.
|
@@ -150,7 +150,7 @@ class Leaderboard
|
|
150
150
|
end
|
151
151
|
|
152
152
|
# Update the optional member data for a given member in the leaderboard.
|
153
|
-
#
|
153
|
+
#
|
154
154
|
# @param member [String] Member name.
|
155
155
|
# @param member_data [Hash] Optional member data.
|
156
156
|
def update_member_data(member, member_data)
|
@@ -182,14 +182,14 @@ class Leaderboard
|
|
182
182
|
end
|
183
183
|
|
184
184
|
# Rank an array of members in the leaderboard.
|
185
|
-
#
|
185
|
+
#
|
186
186
|
# @param members_and_scores [Splat or Array] Variable list of members and scores
|
187
187
|
def rank_members(*members_and_scores)
|
188
188
|
rank_members_in(@leaderboard_name, *members_and_scores)
|
189
189
|
end
|
190
190
|
|
191
191
|
# Rank an array of members in the named leaderboard.
|
192
|
-
#
|
192
|
+
#
|
193
193
|
# @param leaderboard_name [String] Name of the leaderboard.
|
194
194
|
# @param members_and_scores [Splat or Array] Variable list of members and scores
|
195
195
|
def rank_members_in(leaderboard_name, *members_and_scores)
|
@@ -203,16 +203,16 @@ class Leaderboard
|
|
203
203
|
end
|
204
204
|
end
|
205
205
|
end
|
206
|
-
|
206
|
+
|
207
207
|
# Remove a member from the leaderboard.
|
208
208
|
#
|
209
209
|
# @param member [String] Member name.
|
210
210
|
def remove_member(member)
|
211
211
|
remove_member_from(@leaderboard_name, member)
|
212
212
|
end
|
213
|
-
|
213
|
+
|
214
214
|
# Remove a member from the named leaderboard.
|
215
|
-
#
|
215
|
+
#
|
216
216
|
# @param leaderboard_name [String] Name of the leaderboard.
|
217
217
|
# @param member [String] Member name.
|
218
218
|
def remove_member_from(leaderboard_name, member)
|
@@ -221,23 +221,23 @@ class Leaderboard
|
|
221
221
|
transaction.del(member_data_key(leaderboard_name, member))
|
222
222
|
end
|
223
223
|
end
|
224
|
-
|
224
|
+
|
225
225
|
# Retrieve the total number of members in the leaderboard.
|
226
|
-
#
|
226
|
+
#
|
227
227
|
# @return total number of members in the leaderboard.
|
228
228
|
def total_members
|
229
229
|
total_members_in(@leaderboard_name)
|
230
230
|
end
|
231
|
-
|
231
|
+
|
232
232
|
# Retrieve the total number of members in the named leaderboard.
|
233
|
-
#
|
233
|
+
#
|
234
234
|
# @param leaderboard_name [String] Name of the leaderboard.
|
235
235
|
#
|
236
236
|
# @return the total number of members in the named leaderboard.
|
237
237
|
def total_members_in(leaderboard_name)
|
238
238
|
@redis_connection.zcard(leaderboard_name)
|
239
239
|
end
|
240
|
-
|
240
|
+
|
241
241
|
# Retrieve the total number of pages in the leaderboard.
|
242
242
|
#
|
243
243
|
# @param page_size [int, nil] Page size to be used when calculating the total number of pages.
|
@@ -246,18 +246,18 @@ class Leaderboard
|
|
246
246
|
def total_pages(page_size = nil)
|
247
247
|
total_pages_in(@leaderboard_name, page_size)
|
248
248
|
end
|
249
|
-
|
249
|
+
|
250
250
|
# Retrieve the total number of pages in the named leaderboard.
|
251
|
-
#
|
251
|
+
#
|
252
252
|
# @param leaderboard_name [String] Name of the leaderboard.
|
253
253
|
# @param page_size [int, nil] Page size to be used when calculating the total number of pages.
|
254
|
-
#
|
254
|
+
#
|
255
255
|
# @return the total number of pages in the named leaderboard.
|
256
256
|
def total_pages_in(leaderboard_name, page_size = nil)
|
257
257
|
page_size ||= @page_size.to_f
|
258
258
|
(total_members_in(leaderboard_name) / page_size.to_f).ceil
|
259
259
|
end
|
260
|
-
|
260
|
+
|
261
261
|
# Retrieve the total members in a given score range from the leaderboard.
|
262
262
|
#
|
263
263
|
# @param min_score [float] Minimum score.
|
@@ -267,7 +267,7 @@ class Leaderboard
|
|
267
267
|
def total_members_in_score_range(min_score, max_score)
|
268
268
|
total_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
269
269
|
end
|
270
|
-
|
270
|
+
|
271
271
|
# Retrieve the total members in a given score range from the named leaderboard.
|
272
272
|
#
|
273
273
|
# @param leaderboard_name Name of the leaderboard.
|
@@ -278,15 +278,15 @@ class Leaderboard
|
|
278
278
|
def total_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
279
279
|
@redis_connection.zcount(leaderboard_name, min_score, max_score)
|
280
280
|
end
|
281
|
-
|
281
|
+
|
282
282
|
# Change the score for a member in the leaderboard by a score delta which can be positive or negative.
|
283
283
|
#
|
284
284
|
# @param member [String] Member name.
|
285
285
|
# @param delta [float] Score change.
|
286
|
-
def change_score_for(member, delta)
|
286
|
+
def change_score_for(member, delta)
|
287
287
|
change_score_for_member_in(@leaderboard_name, member, delta)
|
288
288
|
end
|
289
|
-
|
289
|
+
|
290
290
|
# Change the score for a member in the named leaderboard by a delta which can be positive or negative.
|
291
291
|
#
|
292
292
|
# @param leaderboard_name [String] Name of the leaderboard.
|
@@ -295,23 +295,23 @@ class Leaderboard
|
|
295
295
|
def change_score_for_member_in(leaderboard_name, member, delta)
|
296
296
|
@redis_connection.zincrby(leaderboard_name, delta, member)
|
297
297
|
end
|
298
|
-
|
298
|
+
|
299
299
|
# Retrieve the rank for a member in the leaderboard.
|
300
|
-
#
|
300
|
+
#
|
301
301
|
# @param member [String] Member name.
|
302
302
|
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
303
|
-
#
|
303
|
+
#
|
304
304
|
# @return the rank for a member in the leaderboard.
|
305
305
|
def rank_for(member, use_zero_index_for_rank = false)
|
306
306
|
rank_for_in(@leaderboard_name, member, use_zero_index_for_rank)
|
307
307
|
end
|
308
|
-
|
308
|
+
|
309
309
|
# Retrieve the rank for a member in the named leaderboard.
|
310
|
-
#
|
310
|
+
#
|
311
311
|
# @param leaderboard_name [String] Name of the leaderboard.
|
312
312
|
# @param member [String] Member name.
|
313
313
|
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
314
|
-
#
|
314
|
+
#
|
315
315
|
# @return the rank for a member in the leaderboard.
|
316
316
|
def rank_for_in(leaderboard_name, member, use_zero_index_for_rank = false)
|
317
317
|
if @reverse
|
@@ -328,18 +328,18 @@ class Leaderboard
|
|
328
328
|
end
|
329
329
|
end
|
330
330
|
end
|
331
|
-
|
331
|
+
|
332
332
|
# Retrieve the score for a member in the leaderboard.
|
333
|
-
#
|
333
|
+
#
|
334
334
|
# @param member Member name.
|
335
335
|
#
|
336
336
|
# @return the score for a member in the leaderboard.
|
337
337
|
def score_for(member)
|
338
338
|
score_for_in(@leaderboard_name, member)
|
339
339
|
end
|
340
|
-
|
340
|
+
|
341
341
|
# Retrieve the score for a member in the named leaderboard.
|
342
|
-
#
|
342
|
+
#
|
343
343
|
# @param leaderboard_name Name of the leaderboard.
|
344
344
|
# @param member [String] Member name.
|
345
345
|
#
|
@@ -356,7 +356,7 @@ class Leaderboard
|
|
356
356
|
def check_member?(member)
|
357
357
|
check_member_in?(@leaderboard_name, member)
|
358
358
|
end
|
359
|
-
|
359
|
+
|
360
360
|
# Check to see if a member exists in the named leaderboard.
|
361
361
|
#
|
362
362
|
# @param leaderboard_name [String] Name of the leaderboard.
|
@@ -366,7 +366,7 @@ class Leaderboard
|
|
366
366
|
def check_member_in?(leaderboard_name, member)
|
367
367
|
!@redis_connection.zscore(leaderboard_name, member).nil?
|
368
368
|
end
|
369
|
-
|
369
|
+
|
370
370
|
# Retrieve the score and rank for a member in the leaderboard.
|
371
371
|
#
|
372
372
|
# @param member [String] Member name.
|
@@ -393,15 +393,15 @@ class Leaderboard
|
|
393
393
|
transaction.zrevrank(leaderboard_name, member)
|
394
394
|
end
|
395
395
|
end
|
396
|
-
|
396
|
+
|
397
397
|
responses[0] = responses[0].to_f
|
398
398
|
if !use_zero_index_for_rank
|
399
399
|
responses[1] = responses[1] + 1 rescue nil
|
400
400
|
end
|
401
|
-
|
402
|
-
{:member => member, :score => responses[0], :rank => responses[1]}
|
401
|
+
|
402
|
+
{:member => member, :score => responses[0], :rank => responses[1]}
|
403
403
|
end
|
404
|
-
|
404
|
+
|
405
405
|
# Remove members from the leaderboard in a given score range.
|
406
406
|
#
|
407
407
|
# @param min_score [float] Minimum score.
|
@@ -409,7 +409,7 @@ class Leaderboard
|
|
409
409
|
def remove_members_in_score_range(min_score, max_score)
|
410
410
|
remove_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
411
411
|
end
|
412
|
-
|
412
|
+
|
413
413
|
# Remove members from the named leaderboard in a given score range.
|
414
414
|
#
|
415
415
|
# @param leaderboard_name [String] Name of the leaderboard.
|
@@ -418,30 +418,30 @@ class Leaderboard
|
|
418
418
|
def remove_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
419
419
|
@redis_connection.zremrangebyscore(leaderboard_name, min_score, max_score)
|
420
420
|
end
|
421
|
-
|
421
|
+
|
422
422
|
# Retrieve the percentile for a member in the leaderboard.
|
423
|
-
#
|
423
|
+
#
|
424
424
|
# @param member [String] Member name.
|
425
|
-
#
|
425
|
+
#
|
426
426
|
# @return the percentile for a member in the leaderboard. Return +nil+ for a non-existent member.
|
427
427
|
def percentile_for(member)
|
428
428
|
percentile_for_in(@leaderboard_name, member)
|
429
429
|
end
|
430
|
-
|
430
|
+
|
431
431
|
# Retrieve the percentile for a member in the named leaderboard.
|
432
|
-
#
|
432
|
+
#
|
433
433
|
# @param leaderboard_name [String] Name of the leaderboard.
|
434
434
|
# @param member [String] Member name.
|
435
|
-
#
|
435
|
+
#
|
436
436
|
# @return the percentile for a member in the named leaderboard.
|
437
437
|
def percentile_for_in(leaderboard_name, member)
|
438
438
|
return nil unless check_member_in?(leaderboard_name, member)
|
439
439
|
|
440
440
|
responses = @redis_connection.multi do |transaction|
|
441
|
-
transaction.zcard(leaderboard_name)
|
441
|
+
transaction.zcard(leaderboard_name)
|
442
442
|
transaction.zrevrank(leaderboard_name, member)
|
443
443
|
end
|
444
|
-
|
444
|
+
|
445
445
|
percentile = ((responses[0] - responses[1] - 1).to_f / responses[0].to_f * 100).ceil
|
446
446
|
if @reverse
|
447
447
|
100 - percentile
|
@@ -468,8 +468,8 @@ class Leaderboard
|
|
468
468
|
#
|
469
469
|
# @return the page where a member falls in the leaderboard.
|
470
470
|
def page_for_in(leaderboard_name, member, page_size = DEFAULT_PAGE_SIZE)
|
471
|
-
rank_for_member = @reverse ?
|
472
|
-
@redis_connection.zrank(leaderboard_name, member) :
|
471
|
+
rank_for_member = @reverse ?
|
472
|
+
@redis_connection.zrank(leaderboard_name, member) :
|
473
473
|
@redis_connection.zrevrank(leaderboard_name, member)
|
474
474
|
|
475
475
|
if rank_for_member.nil?
|
@@ -478,11 +478,11 @@ class Leaderboard
|
|
478
478
|
rank_for_member += 1
|
479
479
|
end
|
480
480
|
|
481
|
-
(rank_for_member.to_f / page_size.to_f).ceil
|
481
|
+
(rank_for_member.to_f / page_size.to_f).ceil
|
482
482
|
end
|
483
483
|
|
484
|
-
# Expire the current leaderboard in a set number of seconds. Do not use this with
|
485
|
-
# leaderboards that utilize member data as there is no facility to cascade the
|
484
|
+
# Expire the current leaderboard in a set number of seconds. Do not use this with
|
485
|
+
# leaderboards that utilize member data as there is no facility to cascade the
|
486
486
|
# expiration out to the keys for the member data.
|
487
487
|
#
|
488
488
|
# @param seconds [int] Number of seconds after which the leaderboard will be expired.
|
@@ -490,8 +490,8 @@ class Leaderboard
|
|
490
490
|
expire_leaderboard_for(@leaderboard_name, seconds)
|
491
491
|
end
|
492
492
|
|
493
|
-
# Expire the given leaderboard in a set number of seconds. Do not use this with
|
494
|
-
# leaderboards that utilize member data as there is no facility to cascade the
|
493
|
+
# Expire the given leaderboard in a set number of seconds. Do not use this with
|
494
|
+
# leaderboards that utilize member data as there is no facility to cascade the
|
495
495
|
# expiration out to the keys for the member data.
|
496
496
|
#
|
497
497
|
# @param leaderboard_name [String] Name of the leaderboard.
|
@@ -500,8 +500,8 @@ class Leaderboard
|
|
500
500
|
@redis_connection.expire(leaderboard_name, seconds)
|
501
501
|
end
|
502
502
|
|
503
|
-
# Expire the current leaderboard at a specific UNIX timestamp. Do not use this with
|
504
|
-
# leaderboards that utilize member data as there is no facility to cascade the
|
503
|
+
# Expire the current leaderboard at a specific UNIX timestamp. Do not use this with
|
504
|
+
# leaderboards that utilize member data as there is no facility to cascade the
|
505
505
|
# expiration out to the keys for the member data.
|
506
506
|
#
|
507
507
|
# @param timestamp [int] UNIX timestamp at which the leaderboard will be expired.
|
@@ -509,8 +509,8 @@ class Leaderboard
|
|
509
509
|
expire_leaderboard_at_for(@leaderboard_name, timestamp)
|
510
510
|
end
|
511
511
|
|
512
|
-
# Expire the given leaderboard at a specific UNIX timestamp. Do not use this with
|
513
|
-
# leaderboards that utilize member data as there is no facility to cascade the
|
512
|
+
# Expire the given leaderboard at a specific UNIX timestamp. Do not use this with
|
513
|
+
# leaderboards that utilize member data as there is no facility to cascade the
|
514
514
|
# expiration out to the keys for the member data.
|
515
515
|
#
|
516
516
|
# @param leaderboard_name [String] Name of the leaderboard.
|
@@ -518,12 +518,12 @@ class Leaderboard
|
|
518
518
|
def expire_leaderboard_at_for(leaderboard_name, timestamp)
|
519
519
|
@redis_connection.expireat(leaderboard_name, timestamp)
|
520
520
|
end
|
521
|
-
|
521
|
+
|
522
522
|
# Retrieve a page of leaders from the leaderboard.
|
523
|
-
#
|
523
|
+
#
|
524
524
|
# @param current_page [int] Page to retrieve from the leaderboard.
|
525
525
|
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
526
|
-
#
|
526
|
+
#
|
527
527
|
# @return a page of leaders from the leaderboard.
|
528
528
|
def leaders(current_page, options = {})
|
529
529
|
leaders_in(@leaderboard_name, current_page, options)
|
@@ -532,39 +532,39 @@ class Leaderboard
|
|
532
532
|
alias_method :members, :leaders
|
533
533
|
|
534
534
|
# Retrieve a page of leaders from the named leaderboard.
|
535
|
-
#
|
535
|
+
#
|
536
536
|
# @param leaderboard_name [String] Name of the leaderboard.
|
537
537
|
# @param current_page [int] Page to retrieve from the named leaderboard.
|
538
538
|
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
539
|
-
#
|
539
|
+
#
|
540
540
|
# @return a page of leaders from the named leaderboard.
|
541
541
|
def leaders_in(leaderboard_name, current_page, options = {})
|
542
542
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
543
543
|
leaderboard_options.merge!(options)
|
544
|
-
|
544
|
+
|
545
545
|
if current_page < 1
|
546
546
|
current_page = 1
|
547
547
|
end
|
548
|
-
|
548
|
+
|
549
549
|
page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size
|
550
|
-
|
550
|
+
|
551
551
|
if current_page > total_pages_in(leaderboard_name, page_size)
|
552
552
|
current_page = total_pages_in(leaderboard_name, page_size)
|
553
553
|
end
|
554
|
-
|
554
|
+
|
555
555
|
index_for_redis = current_page - 1
|
556
556
|
|
557
557
|
starting_offset = (index_for_redis * page_size)
|
558
558
|
if starting_offset < 0
|
559
559
|
starting_offset = 0
|
560
560
|
end
|
561
|
-
|
561
|
+
|
562
562
|
ending_offset = (starting_offset + page_size) - 1
|
563
|
-
|
563
|
+
|
564
564
|
if @reverse
|
565
|
-
raw_leader_data = @redis_connection.zrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
|
566
|
-
else
|
567
|
-
raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
|
565
|
+
raw_leader_data = @redis_connection.zrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
|
566
|
+
else
|
567
|
+
raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
|
568
568
|
end
|
569
569
|
|
570
570
|
if raw_leader_data
|
@@ -635,7 +635,7 @@ class Leaderboard
|
|
635
635
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
636
636
|
leaderboard_options.merge!(options)
|
637
637
|
|
638
|
-
raw_leader_data = @reverse ?
|
638
|
+
raw_leader_data = @reverse ?
|
639
639
|
@redis_connection.zrangebyscore(leaderboard_name, minimum_score, maximum_score) :
|
640
640
|
@redis_connection.zrevrangebyscore(leaderboard_name, maximum_score, minimum_score)
|
641
641
|
|
@@ -646,22 +646,68 @@ class Leaderboard
|
|
646
646
|
end
|
647
647
|
end
|
648
648
|
|
649
|
+
# Retrieve members from the leaderboard within a given rank range.
|
650
|
+
#
|
651
|
+
# @param starting_rank [int] Starting rank (inclusive).
|
652
|
+
# @param ending_rank [int] Ending rank (inclusive).
|
653
|
+
# @param options [Hash] Options to be used when retrieving the data from the leaderboard.
|
654
|
+
#
|
655
|
+
# @return members from the leaderboard that fall within the given rank range.
|
656
|
+
def members_from_rank_range(starting_rank, ending_rank, options = {})
|
657
|
+
members_from_rank_range_in(@leaderboard_name, starting_rank, ending_rank, options)
|
658
|
+
end
|
659
|
+
|
660
|
+
# Retrieve members from the named leaderboard within a given rank range.
|
661
|
+
#
|
662
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
663
|
+
# @param starting_rank [int] Starting rank (inclusive).
|
664
|
+
# @param ending_rank [int] Ending rank (inclusive).
|
665
|
+
# @param options [Hash] Options to be used when retrieving the data from the leaderboard.
|
666
|
+
#
|
667
|
+
# @return members from the leaderboard that fall within the given rank range.
|
668
|
+
def members_from_rank_range_in(leaderboard_name, starting_rank, ending_rank, options = {})
|
669
|
+
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
670
|
+
leaderboard_options.merge!(options)
|
671
|
+
|
672
|
+
starting_rank -= 1
|
673
|
+
if starting_rank < 0
|
674
|
+
starting_rank = 0
|
675
|
+
end
|
676
|
+
|
677
|
+
ending_rank -= 1
|
678
|
+
if ending_rank > total_members_in(leaderboard_name)
|
679
|
+
ending_rank = total_members_in(leaderboard_name) - 1
|
680
|
+
end
|
681
|
+
|
682
|
+
if @reverse
|
683
|
+
raw_leader_data = @redis_connection.zrange(leaderboard_name, starting_rank, ending_rank, :with_scores => false)
|
684
|
+
else
|
685
|
+
raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_rank, ending_rank, :with_scores => false)
|
686
|
+
end
|
687
|
+
|
688
|
+
if raw_leader_data
|
689
|
+
return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
|
690
|
+
else
|
691
|
+
return []
|
692
|
+
end
|
693
|
+
end
|
694
|
+
|
649
695
|
# Retrieve a member at the specified index from the leaderboard.
|
650
|
-
#
|
696
|
+
#
|
651
697
|
# @param position [int] Position in leaderboard.
|
652
698
|
# @param options [Hash] Options to be used when retrieving the member from the leaderboard.
|
653
|
-
#
|
699
|
+
#
|
654
700
|
# @return a member from the leaderboard.
|
655
701
|
def member_at(position, options = {})
|
656
702
|
member_at_in(@leaderboard_name, position, options)
|
657
703
|
end
|
658
704
|
|
659
705
|
# Retrieve a member at the specified index from the leaderboard.
|
660
|
-
#
|
706
|
+
#
|
661
707
|
# @param leaderboard_name [String] Name of the leaderboard.
|
662
708
|
# @param position [int] Position in named leaderboard.
|
663
709
|
# @param options [Hash] Options to be used when retrieving the member from the named leaderboard.
|
664
|
-
#
|
710
|
+
#
|
665
711
|
# @return a page of leaders from the named leaderboard.
|
666
712
|
def member_at_in(leaderboard_name, position, options = {})
|
667
713
|
if position <= total_members_in(leaderboard_name)
|
@@ -675,44 +721,44 @@ class Leaderboard
|
|
675
721
|
leaders[offset] if leaders
|
676
722
|
end
|
677
723
|
end
|
678
|
-
|
724
|
+
|
679
725
|
# Retrieve a page of leaders from the leaderboard around a given member.
|
680
726
|
#
|
681
727
|
# @param member [String] Member name.
|
682
728
|
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
683
|
-
#
|
729
|
+
#
|
684
730
|
# @return a page of leaders from the leaderboard around a given member.
|
685
731
|
def around_me(member, options = {})
|
686
732
|
around_me_in(@leaderboard_name, member, options)
|
687
733
|
end
|
688
|
-
|
734
|
+
|
689
735
|
# Retrieve a page of leaders from the named leaderboard around a given member.
|
690
736
|
#
|
691
737
|
# @param leaderboard_name [String] Name of the leaderboard.
|
692
738
|
# @param member [String] Member name.
|
693
739
|
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
694
|
-
#
|
740
|
+
#
|
695
741
|
# @return a page of leaders from the named leaderboard around a given member. Returns an empty array for a non-existent member.
|
696
742
|
def around_me_in(leaderboard_name, member, options = {})
|
697
743
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
698
744
|
leaderboard_options.merge!(options)
|
699
|
-
|
700
|
-
reverse_rank_for_member = @reverse ?
|
701
|
-
@redis_connection.zrank(leaderboard_name, member) :
|
745
|
+
|
746
|
+
reverse_rank_for_member = @reverse ?
|
747
|
+
@redis_connection.zrank(leaderboard_name, member) :
|
702
748
|
@redis_connection.zrevrank(leaderboard_name, member)
|
703
749
|
|
704
750
|
return [] unless reverse_rank_for_member
|
705
|
-
|
751
|
+
|
706
752
|
page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size
|
707
|
-
|
753
|
+
|
708
754
|
starting_offset = reverse_rank_for_member - (page_size / 2)
|
709
755
|
if starting_offset < 0
|
710
756
|
starting_offset = 0
|
711
757
|
end
|
712
|
-
|
758
|
+
|
713
759
|
ending_offset = (starting_offset + page_size) - 1
|
714
|
-
|
715
|
-
raw_leader_data = @reverse ?
|
760
|
+
|
761
|
+
raw_leader_data = @reverse ?
|
716
762
|
@redis_connection.zrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false) :
|
717
763
|
@redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
|
718
764
|
|
@@ -722,30 +768,30 @@ class Leaderboard
|
|
722
768
|
return []
|
723
769
|
end
|
724
770
|
end
|
725
|
-
|
771
|
+
|
726
772
|
# Retrieve a page of leaders from the leaderboard for a given list of members.
|
727
|
-
#
|
773
|
+
#
|
728
774
|
# @param members [Array] Member names.
|
729
775
|
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
730
|
-
#
|
776
|
+
#
|
731
777
|
# @return a page of leaders from the leaderboard for a given list of members.
|
732
778
|
def ranked_in_list(members, options = {})
|
733
779
|
ranked_in_list_in(@leaderboard_name, members, options)
|
734
780
|
end
|
735
|
-
|
781
|
+
|
736
782
|
# Retrieve a page of leaders from the named leaderboard for a given list of members.
|
737
|
-
#
|
783
|
+
#
|
738
784
|
# @param leaderboard_name [String] Name of the leaderboard.
|
739
785
|
# @param members [Array] Member names.
|
740
786
|
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
741
|
-
#
|
787
|
+
#
|
742
788
|
# @return a page of leaders from the named leaderboard for a given list of members.
|
743
789
|
def ranked_in_list_in(leaderboard_name, members, options = {})
|
744
790
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
745
791
|
leaderboard_options.merge!(options)
|
746
|
-
|
792
|
+
|
747
793
|
ranks_for_members = []
|
748
|
-
|
794
|
+
|
749
795
|
responses = @redis_connection.multi do |transaction|
|
750
796
|
members.each do |member|
|
751
797
|
if @reverse
|
@@ -756,7 +802,7 @@ class Leaderboard
|
|
756
802
|
transaction.zscore(leaderboard_name, member) if leaderboard_options[:with_scores]
|
757
803
|
end
|
758
804
|
end
|
759
|
-
|
805
|
+
|
760
806
|
members.each_with_index do |member, index|
|
761
807
|
data = {}
|
762
808
|
data[:member] = member
|
@@ -767,10 +813,10 @@ class Leaderboard
|
|
767
813
|
else
|
768
814
|
data[:rank] = responses[index * 2] + 1 rescue nil
|
769
815
|
end
|
770
|
-
|
816
|
+
|
771
817
|
data[:score] = responses[index * 2 + 1].to_f
|
772
818
|
else
|
773
|
-
data[:score] = responses[index].to_f
|
819
|
+
data[:score] = responses[index].to_f
|
774
820
|
end
|
775
821
|
else
|
776
822
|
if leaderboard_options[:with_rank]
|
@@ -785,41 +831,41 @@ class Leaderboard
|
|
785
831
|
if leaderboard_options[:with_member_data]
|
786
832
|
data[:member_data] = member_data_for_in(leaderboard_name, member)
|
787
833
|
end
|
788
|
-
|
834
|
+
|
789
835
|
ranks_for_members << data
|
790
836
|
end
|
791
|
-
|
837
|
+
|
792
838
|
ranks_for_members
|
793
839
|
end
|
794
|
-
|
840
|
+
|
795
841
|
# Merge leaderboards given by keys with this leaderboard into a named destination leaderboard.
|
796
842
|
#
|
797
843
|
# @param destination [String] Destination leaderboard name.
|
798
844
|
# @param keys [Array] Leaderboards to be merged with the current leaderboard.
|
799
|
-
# @param options [Hash] Options for merging the leaderboards.
|
845
|
+
# @param options [Hash] Options for merging the leaderboards.
|
800
846
|
def merge_leaderboards(destination, keys, options = {:aggregate => :sum})
|
801
847
|
@redis_connection.zunionstore(destination, keys.insert(0, @leaderboard_name), options)
|
802
848
|
end
|
803
|
-
|
849
|
+
|
804
850
|
# Intersect leaderboards given by keys with this leaderboard into a named destination leaderboard.
|
805
|
-
#
|
851
|
+
#
|
806
852
|
# @param destination [String] Destination leaderboard name.
|
807
853
|
# @param keys [Array] Leaderboards to be merged with the current leaderboard.
|
808
854
|
# @param options [Hash] Options for intersecting the leaderboards.
|
809
855
|
def intersect_leaderboards(destination, keys, options = {:aggregate => :sum})
|
810
856
|
@redis_connection.zinterstore(destination, keys.insert(0, @leaderboard_name), options)
|
811
857
|
end
|
812
|
-
|
813
|
-
private
|
814
|
-
|
858
|
+
|
859
|
+
private
|
860
|
+
|
815
861
|
# Key for retrieving optional member data.
|
816
862
|
#
|
817
863
|
# @param leaderboard_name [String] Name of the leaderboard.
|
818
864
|
# @param member [String] Member name.
|
819
|
-
#
|
865
|
+
#
|
820
866
|
# @return a key in the form of +leaderboard_name:data:member+
|
821
867
|
def member_data_key(leaderboard_name, member)
|
822
|
-
"#{leaderboard_name}:member_data:#{member}"
|
868
|
+
"#{leaderboard_name}:member_data:#{member}"
|
823
869
|
end
|
824
870
|
|
825
871
|
# Validate and return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1.
|
@@ -831,7 +877,7 @@ class Leaderboard
|
|
831
877
|
if page_size && page_size < 1
|
832
878
|
page_size = DEFAULT_PAGE_SIZE
|
833
879
|
end
|
834
|
-
|
880
|
+
|
835
881
|
page_size
|
836
882
|
end
|
837
883
|
end
|