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 CHANGED
@@ -1,2 +1,3 @@
1
1
  --color
2
- --format nested
2
+ --format nested
3
+ --order random
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 retreive all members from a leaderboard. You may also use the aliases `all_members` or `all_members_from`.
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 in Ruby, http://redis.io.
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, http://redis.io/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
- ### Ranking multiple members in a leaderboard at once
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
- Bundler::GemHelper.install_tasks
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
- REDIS_DIR = File.expand_path(File.join("..", "spec"), __FILE__)
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