leaderboard 2.4.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|