leaderboard 2.0.1 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +8 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.markdown +16 -9
- data/Gemfile +1 -11
- data/{README.rdoc → README.markdown} +53 -15
- data/Rakefile +6 -45
- data/leaderboard.gemspec +19 -61
- data/lib/leaderboard.rb +211 -7
- data/lib/leaderboard/version.rb +3 -0
- data/test/test_helper.rb +4 -0
- data/test/test_leaderboard.rb +39 -2
- metadata +53 -87
- data/VERSION +0 -1
- data/test/helper.rb +0 -17
data/.gitignore
ADDED
data/.rvmrc
CHANGED
data/CHANGELOG.markdown
CHANGED
@@ -1,9 +1,16 @@
|
|
1
|
-
#
|
1
|
+
# CHANGELOG
|
2
|
+
|
3
|
+
## leaderboard 2.0.2 (2012-02-03)
|
4
|
+
|
5
|
+
* Fix for checking to see if a member actually exists in the leaderboard for the `around_me` calls
|
6
|
+
* Return appropriate `nil` in data returned for calls such as `percentile_for` and `ranked_in_list` for non-existent members
|
7
|
+
|
8
|
+
## leaderboard 2.0.1 (2011-11-07)
|
2
9
|
|
3
10
|
* Allow for only single options to be passed to `leaders`, `around_me` and `ranked_in_list` methods - https://github.com/agoragames/leaderboard/issues/4
|
4
11
|
* Added `percentile_for(member)` and `percentile_for_in(leaderboard_name, member)` methods to calculate percentile for a given member
|
5
12
|
|
6
|
-
|
13
|
+
## leaderboard 2.0.0 (2011-08-05)
|
7
14
|
|
8
15
|
* Change `add_member` to `rank_member` - https://github.com/agoragames/leaderboard/issues/3
|
9
16
|
* Added `delete_leaderboard` and `delete_leaderboard_named` - https://github.com/agoragames/leaderboard/issues/2
|
@@ -12,33 +19,33 @@
|
|
12
19
|
* Updated initializer to take a leaderboard name, `options` hash and `redis_options` hash
|
13
20
|
* Simplified `leaders`, `around_me` and `ranked_in_list` to use an `options` hash with defaults for the previously individual parameters
|
14
21
|
|
15
|
-
|
22
|
+
## leaderboard 1.0.6 (unreleased)
|
16
23
|
|
17
24
|
* Added `disconnect` method
|
18
25
|
* Check for invalid page size when changing
|
19
26
|
|
20
|
-
|
27
|
+
## leaderboard 1.0.5 (2011-05-04)
|
21
28
|
|
22
29
|
* Updated Rakefile to run tests under ruby 1.8.7 and ruby 1.9.2
|
23
30
|
* Added `page_size` parameter to `total_pages_in` to allow for checking what if values in that scenario
|
24
31
|
* Added `page_size` parameter to `leaders` and `around_me` calls
|
25
32
|
|
26
|
-
|
33
|
+
## leaderboard 1.0.4 (2011-04-26)
|
27
34
|
|
28
35
|
* Minor bug fix
|
29
36
|
|
30
|
-
|
37
|
+
## leaderboard 1.0.3 (2011-04-26)
|
31
38
|
|
32
39
|
* Fixing issue using total_pages in leaderboard_in call
|
33
40
|
* Internal `massage_leader_data` method will now respect `with_scores`
|
34
41
|
|
35
|
-
|
42
|
+
## leaderboard 1.0.2 (2011-02-25)
|
36
43
|
|
37
44
|
* Adding `XXX_to`, `XXX_for`, `XXX_in` and `XXX_from` methods that will allow you to set the leaderboard name to interact with outside of creating a new object
|
38
45
|
* Added `merge_leaderboards(destination, keys, options = {:aggregate => :min})` method to merge leaderboards given by keys with this leaderboard into destination
|
39
46
|
* Added `intersect_leaderboards(destination, keys, options = {:aggregate => :min})` method to intersect leaderboards given by keys with this leaderboard into destination
|
40
47
|
|
41
|
-
|
48
|
+
## leaderboard 1.0.1 (2011-02-16)
|
42
49
|
|
43
50
|
* `redis_options` can be passed in the initializer to pass options for the connection to Redis
|
44
51
|
* `page_size` is now settable outside of the initializer
|
@@ -46,6 +53,6 @@
|
|
46
53
|
* `score_and_rank_for(member, use_zero_index_for_rank = false)`: Retrieve the score and rank for a member in a single call
|
47
54
|
* `remove_members_in_score_range(min_score, max_score)`: Remove members from the leaderboard within a score range
|
48
55
|
|
49
|
-
|
56
|
+
## leaderboard 1.0.0
|
50
57
|
|
51
58
|
* Initial release
|
data/Gemfile
CHANGED
@@ -1,13 +1,3 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
|
4
|
-
# Include everything needed to run rake, tests, features, etc.
|
5
|
-
group :development do
|
6
|
-
gem "bundler"
|
7
|
-
gem "jeweler"
|
8
|
-
gem "rcov"
|
9
|
-
gem "rdoc"
|
10
|
-
end
|
11
|
-
|
12
|
-
gem 'redis'
|
13
|
-
|
3
|
+
gemspec
|
@@ -1,114 +1,143 @@
|
|
1
|
-
|
1
|
+
# leaderboard
|
2
2
|
|
3
3
|
Leaderboards backed by Redis in Ruby, http://redis.io.
|
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
|
|
7
|
-
|
7
|
+
## Installation
|
8
8
|
|
9
9
|
Install the gem:
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
```ruby
|
12
|
+
gem "leaderboard"
|
13
|
+
```
|
14
|
+
|
13
15
|
Make sure your redis server is running! Redis configuration is outside the scope of this README, but
|
14
16
|
check out the Redis documentation, http://redis.io/documentation.
|
15
17
|
|
16
|
-
|
18
|
+
## Compatibility
|
17
19
|
|
18
|
-
The gem has been built and tested under Ruby 1.8.7 and Ruby 1.9.
|
20
|
+
The gem has been built and tested under Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3
|
19
21
|
|
20
|
-
|
22
|
+
## Usage
|
21
23
|
|
22
24
|
Create a new leaderboard or attach to an existing leaderboard named 'highscores':
|
23
25
|
|
26
|
+
```ruby
|
24
27
|
ruby-1.9.2-p180 :002 > highscore_lb = Leaderboard.new('highscores')
|
25
28
|
=> #<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)>>
|
29
|
+
```
|
26
30
|
|
27
31
|
If you need to pass in options for Redis, you can do this in the initializer:
|
28
32
|
|
33
|
+
```ruby
|
29
34
|
ruby-1.9.2-p180 :007 > redis_options = {:host => 'localhost', :port => 6379, :db => 1}
|
30
35
|
=> {:host=>"localhost", :port=>6379, :db=>1}
|
31
36
|
ruby-1.9.2-p180 :008 > highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
32
37
|
=> #<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)>>
|
38
|
+
```
|
33
39
|
|
34
40
|
You can pass in an existing connection to Redis using :redis_connection in the Redis options hash:
|
35
41
|
|
42
|
+
```ruby
|
36
43
|
ruby-1.9.2-p180 :009 > redis = Redis.new
|
37
44
|
=> #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
|
38
45
|
ruby-1.9.2-p180 :010 > redis_options = {:redis_connection => redis}
|
39
46
|
=> {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
|
40
47
|
ruby-1.9.2-p180 :011 > highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
|
41
48
|
=> #<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)>>
|
49
|
+
```
|
42
50
|
|
43
51
|
You can set the page size to something other than the default page size (25):
|
44
52
|
|
53
|
+
```ruby
|
45
54
|
ruby-1.9.2-p180 :012 > highscore_lb.page_size = 5
|
46
55
|
=> 5
|
47
56
|
ruby-1.9.2-p180 :013 > highscore_lb
|
48
57
|
=> #<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)>>
|
58
|
+
```
|
49
59
|
|
50
60
|
Add members to your leaderboard using rank_member:
|
51
61
|
|
62
|
+
```ruby
|
52
63
|
ruby-1.9.2-p180 :014 > 1.upto(10) do |index|
|
53
64
|
ruby-1.9.2-p180 :015 > highscore_lb.rank_member("member_#{index}", index)
|
54
65
|
ruby-1.9.2-p180 :016?> end
|
55
66
|
=> 1
|
67
|
+
```
|
56
68
|
|
57
69
|
You can call rank_member with the same member and the leaderboard will be updated automatically.
|
58
70
|
|
59
71
|
Get some information about your leaderboard:
|
60
72
|
|
73
|
+
```ruby
|
61
74
|
ruby-1.9.2-p180 :020 > highscore_lb.total_members
|
62
75
|
=> 10
|
63
76
|
ruby-1.9.2-p180 :021 > highscore_lb.total_pages
|
64
77
|
=> 1
|
78
|
+
```
|
65
79
|
|
66
80
|
Get some information about a specific member(s) in the leaderboard:
|
67
81
|
|
82
|
+
```ruby
|
68
83
|
ruby-1.9.2-p180 :022 > highscore_lb.score_for('member_4')
|
69
84
|
=> 4.0
|
70
85
|
ruby-1.9.2-p180 :023 > highscore_lb.rank_for('member_4')
|
71
86
|
=> 7
|
72
87
|
ruby-1.9.2-p180 :024 > highscore_lb.rank_for('member_10')
|
73
88
|
=> 1
|
89
|
+
```
|
74
90
|
|
75
91
|
Get page 1 in the leaderboard:
|
76
92
|
|
93
|
+
```ruby
|
77
94
|
ruby-1.9.2-p180 :025 > highscore_lb.leaders(1)
|
78
95
|
=> [{: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}]
|
96
|
+
```
|
79
97
|
|
80
98
|
You can pass various options to the calls `leaders`, `around_me` and `ranked_in_list`. Valid options are `:with_scores`, `:with_rank`, `:use_zero_index_for_rank` and `:page_size`.
|
81
99
|
Below is an example of retrieving the first page in the leaderboard without ranks:
|
82
100
|
|
101
|
+
```ruby
|
83
102
|
ruby-1.9.2-p180 :026 > highscore_lb.leaders(1, :with_rank => false)
|
84
103
|
=> [{: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}]
|
104
|
+
```
|
85
105
|
|
86
106
|
Below is an example of retrieving the first page in the leaderboard without scores or ranks:
|
87
107
|
|
108
|
+
```ruby
|
88
109
|
ruby-1.9.2-p180 :028 > highscore_lb.leaders(1, :with_scores => false, :with_rank => false)
|
89
110
|
=> [{: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"}]
|
111
|
+
```
|
90
112
|
|
91
113
|
Add more members to your leaderboard:
|
92
114
|
|
115
|
+
```ruby
|
93
116
|
ruby-1.9.2-p180 :029 > 50.upto(95) do |index|
|
94
117
|
ruby-1.9.2-p180 :030 > highscore_lb.rank_member("member_#{index}", index)
|
95
118
|
ruby-1.9.2-p180 :031?> end
|
96
119
|
=> 50
|
97
120
|
ruby-1.9.2-p180 :032 > highscore_lb.total_pages
|
98
121
|
=> 3
|
122
|
+
```
|
99
123
|
|
100
124
|
Get an "Around Me" leaderboard for a member:
|
101
125
|
|
126
|
+
```ruby
|
102
127
|
ruby-1.9.2-p180 :033 > highscore_lb.around_me('member_53')
|
103
128
|
=> [{: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}]
|
129
|
+
```
|
104
130
|
|
105
131
|
Get rank and score for an arbitrary list of members (e.g. friends):
|
106
132
|
|
133
|
+
```ruby
|
107
134
|
ruby-1.9.2-p180 :034 > highscore_lb.ranked_in_list(['member_1', 'member_62', 'member_67'])
|
108
135
|
=> [{:member=>"member_1", :rank=>56, :score=>1.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_67", :rank=>29, :score=>67.0}]
|
109
|
-
|
110
|
-
|
136
|
+
```
|
137
|
+
|
138
|
+
### Other useful methods
|
111
139
|
|
140
|
+
```
|
112
141
|
delete_leaderboard: Delete the current leaderboard
|
113
142
|
remove_member(member): Remove a member from the leaderboard
|
114
143
|
total_members: Total # of members in the leaderboard
|
@@ -123,13 +152,15 @@ Get rank and score for an arbitrary list of members (e.g. friends):
|
|
123
152
|
percentile_for(member): Calculate the percentile for a given member
|
124
153
|
merge_leaderboards(destination, keys, options = {:aggregate => :min}): Merge leaderboards given by keys with this leaderboard into destination
|
125
154
|
intersect_leaderboards(destination, keys, options = {:aggregate => :min}): Intersect leaderboards given by keys with this leaderboard into destination
|
155
|
+
```
|
126
156
|
|
127
|
-
Check the online documentation
|
157
|
+
Check the [online documentation](http://rubydoc.info/github/agoragames/leaderboard/master/frames) for more detail on each method.
|
128
158
|
|
129
|
-
|
159
|
+
## Performance Metrics
|
130
160
|
|
131
161
|
10 million sequential scores insert:
|
132
162
|
|
163
|
+
```ruby
|
133
164
|
ruby-1.9.2-p180 :003 > highscore_lb = Leaderboard.new('highscores')
|
134
165
|
=> #<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)>>
|
135
166
|
ruby-1.9.2-p180 :004 > insert_time = Benchmark.measure do
|
@@ -138,9 +169,11 @@ Check the online documentation for more detail, http://rubydoc.info/gems/leaderb
|
|
138
169
|
ruby-1.9.2-p180 :007?> end
|
139
170
|
ruby-1.9.2-p180 :008?> end
|
140
171
|
=> 323.070000 148.560000 471.630000 (942.068307)
|
172
|
+
```
|
141
173
|
|
142
174
|
Average time to request an arbitrary page from the leaderboard:
|
143
175
|
|
176
|
+
```ruby
|
144
177
|
ruby-1.9.2-p180 :009 > requests_to_make = 50000
|
145
178
|
=> 50000
|
146
179
|
ruby-1.9.2-p180 :010 > lb_request_time = 0
|
@@ -154,18 +187,22 @@ Average time to request an arbitrary page from the leaderboard:
|
|
154
187
|
ruby-1.9.2-p180 :016 > p lb_request_time / requests_to_make
|
155
188
|
0.001513999999999998
|
156
189
|
=> 0.001513999999999998
|
190
|
+
```
|
157
191
|
|
158
192
|
10 million random scores insert:
|
159
193
|
|
194
|
+
```ruby
|
160
195
|
ruby-1.9.2-p180 :018 > insert_time = Benchmark.measure do
|
161
196
|
ruby-1.9.2-p180 :019 > 1.upto(10000000) do |index|
|
162
197
|
ruby-1.9.2-p180 :020 > highscore_lb.rank_member("member_#{index}", rand(50000000))
|
163
198
|
ruby-1.9.2-p180 :021?> end
|
164
199
|
ruby-1.9.2-p180 :022?> end
|
165
200
|
=> 338.480000 155.200000 493.680000 (2188.702475)
|
201
|
+
```
|
166
202
|
|
167
203
|
Average time to request an arbitrary page from the leaderboard:
|
168
204
|
|
205
|
+
```ruby
|
169
206
|
ruby-1.9.2-p180 :007 > 1.upto(requests_to_make) do
|
170
207
|
ruby-1.9.2-p180 :008 > lb_request_time += Benchmark.measure do
|
171
208
|
ruby-1.9.2-p180 :009 > highscore_lb.leaders(rand(highscore_lb.total_pages))
|
@@ -175,12 +212,13 @@ Average time to request an arbitrary page from the leaderboard:
|
|
175
212
|
ruby-1.9.2-p180 :012 > p lb_request_time / requests_to_make
|
176
213
|
0.0014615999999999531
|
177
214
|
=> 0.0014615999999999531
|
215
|
+
```
|
178
216
|
|
179
|
-
|
217
|
+
## Future Ideas
|
180
218
|
|
181
219
|
* Bulk insert
|
182
220
|
|
183
|
-
|
221
|
+
## Ports
|
184
222
|
|
185
223
|
The following ports have been made of the leaderboard gem.
|
186
224
|
|
@@ -190,7 +228,7 @@ The following ports have been made of the leaderboard gem.
|
|
190
228
|
* Python: https://github.com/agoragames/python-leaderboard
|
191
229
|
* Scala: https://github.com/agoragames/scala-leaderboard
|
192
230
|
|
193
|
-
|
231
|
+
## Contributing to leaderboard
|
194
232
|
|
195
233
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
196
234
|
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
@@ -200,7 +238,7 @@ The following ports have been made of the leaderboard gem.
|
|
200
238
|
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
201
239
|
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
202
240
|
|
203
|
-
|
241
|
+
## Copyright
|
204
242
|
|
205
243
|
Copyright (c) 2011 David Czarnecki. See LICENSE.txt for further details.
|
206
244
|
|
data/Rakefile
CHANGED
@@ -1,31 +1,7 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'bundler'
|
3
|
-
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'rake'
|
11
|
-
|
12
|
-
require 'jeweler'
|
13
|
-
Jeweler::Tasks.new do |gem|
|
14
|
-
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
-
gem.name = "leaderboard"
|
16
|
-
gem.homepage = "http://github.com/agoragames/leaderboard"
|
17
|
-
gem.license = "MIT"
|
18
|
-
gem.summary = %Q{Leaderboards backed by Redis in Ruby}
|
19
|
-
gem.description = %Q{Leaderboards backed by Redis in Ruby}
|
20
|
-
gem.email = "dczarnecki@agoragames.com"
|
21
|
-
gem.authors = ["David Czarnecki"]
|
22
|
-
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
23
|
-
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
24
|
-
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
25
|
-
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
26
|
-
end
|
27
|
-
Jeweler::RubygemsDotOrgTasks.new
|
2
|
+
Bundler::GemHelper.install_tasks
|
28
3
|
|
4
|
+
require 'rake'
|
29
5
|
require 'rake/testtask'
|
30
6
|
Rake::TestTask.new(:test) do |test|
|
31
7
|
test.libs << 'lib' << 'test'
|
@@ -33,23 +9,6 @@ Rake::TestTask.new(:test) do |test|
|
|
33
9
|
test.verbose = true
|
34
10
|
end
|
35
11
|
|
36
|
-
require 'rcov/rcovtask'
|
37
|
-
Rcov::RcovTask.new do |test|
|
38
|
-
test.libs << 'test'
|
39
|
-
test.pattern = 'test/**/test_*.rb'
|
40
|
-
test.verbose = true
|
41
|
-
end
|
42
|
-
|
43
|
-
require 'rdoc/task'
|
44
|
-
RDoc::Task.new do |rdoc|
|
45
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
-
|
47
|
-
rdoc.rdoc_dir = 'rdoc'
|
48
|
-
rdoc.title = "leaderboard #{version}"
|
49
|
-
rdoc.rdoc_files.include('README*')
|
50
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
-
end
|
52
|
-
|
53
12
|
REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
|
54
13
|
REDIS_CNF = File.join(REDIS_DIR, "test.conf")
|
55
14
|
REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
|
@@ -58,7 +17,7 @@ REDIS_LOCATION = ENV['REDIS_LOCATION']
|
|
58
17
|
task :default => :run
|
59
18
|
|
60
19
|
desc "Run tests and manage server start/stop"
|
61
|
-
task :run => [:start, :
|
20
|
+
task :run => [:start, :test, :stop]
|
62
21
|
|
63
22
|
desc "Run rcov and manage server start/stop"
|
64
23
|
task :rcoverage => [:start, :rcov, :stop]
|
@@ -89,5 +48,7 @@ task :stop do
|
|
89
48
|
end
|
90
49
|
|
91
50
|
task :test_rubies do
|
92
|
-
|
51
|
+
Rake::Task['start'].execute
|
52
|
+
system "rvm 1.8.7@leaderboard_gem,1.9.2@leaderboard_gem,1.9.3@leaderboard_gem do rake test"
|
53
|
+
Rake::Task['stop'].execute
|
93
54
|
end
|
data/leaderboard.gemspec
CHANGED
@@ -1,69 +1,27 @@
|
|
1
|
-
# Generated by jeweler
|
2
|
-
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
-
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require 'leaderboard/version'
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
|
-
s.name
|
8
|
-
s.version
|
7
|
+
s.name = "leaderboard"
|
8
|
+
s.version = Leaderboard::VERSION.dup
|
9
|
+
s.authors = ["David Czarnecki"]
|
10
|
+
s.email = ["dczarnecki@agoragames.com"]
|
11
|
+
s.homepage = "https://github.com/agoragames/leaderboard"
|
12
|
+
s.summary = %q{Leaderboards backed by Redis in Ruby}
|
13
|
+
s.description = %q{Leaderboards backed by Redis in Ruby}
|
9
14
|
|
10
|
-
s.
|
11
|
-
s.authors = ["David Czarnecki"]
|
12
|
-
s.date = "2011-11-07"
|
13
|
-
s.description = "Leaderboards backed by Redis in Ruby"
|
14
|
-
s.email = "dczarnecki@agoragames.com"
|
15
|
-
s.extra_rdoc_files = [
|
16
|
-
"LICENSE.txt",
|
17
|
-
"README.rdoc"
|
18
|
-
]
|
19
|
-
s.files = [
|
20
|
-
".document",
|
21
|
-
".rvmrc",
|
22
|
-
"CHANGELOG.markdown",
|
23
|
-
"Gemfile",
|
24
|
-
"LICENSE.txt",
|
25
|
-
"README.rdoc",
|
26
|
-
"Rakefile",
|
27
|
-
"VERSION",
|
28
|
-
"leaderboard.gemspec",
|
29
|
-
"lib/leaderboard.rb",
|
30
|
-
"test/db/.gitkeep",
|
31
|
-
"test/helper.rb",
|
32
|
-
"test/test.conf",
|
33
|
-
"test/test_leaderboard.rb"
|
34
|
-
]
|
35
|
-
s.homepage = "http://github.com/agoragames/leaderboard"
|
36
|
-
s.licenses = ["MIT"]
|
37
|
-
s.require_paths = ["lib"]
|
38
|
-
s.rubygems_version = "1.8.11"
|
39
|
-
s.summary = "Leaderboards backed by Redis in Ruby"
|
40
|
-
s.test_files = [
|
41
|
-
"test/helper.rb",
|
42
|
-
"test/test_leaderboard.rb"
|
43
|
-
]
|
15
|
+
s.rubyforge_project = "leaderboard"
|
44
16
|
|
45
|
-
|
46
|
-
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
47
21
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
s.add_development_dependency(%q<rcov>, [">= 0"])
|
53
|
-
s.add_development_dependency(%q<rdoc>, [">= 0"])
|
54
|
-
else
|
55
|
-
s.add_dependency(%q<redis>, [">= 0"])
|
56
|
-
s.add_dependency(%q<bundler>, [">= 0"])
|
57
|
-
s.add_dependency(%q<jeweler>, [">= 0"])
|
58
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
59
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
60
|
-
end
|
61
|
-
else
|
62
|
-
s.add_dependency(%q<redis>, [">= 0"])
|
63
|
-
s.add_dependency(%q<bundler>, [">= 0"])
|
64
|
-
s.add_dependency(%q<jeweler>, [">= 0"])
|
65
|
-
s.add_dependency(%q<rcov>, [">= 0"])
|
66
|
-
s.add_dependency(%q<rdoc>, [">= 0"])
|
22
|
+
s.add_dependency('redis')
|
23
|
+
s.add_development_dependency('rake')
|
24
|
+
if '1.8.7'.eql?(RUBY_VERSION)
|
25
|
+
s.add_development_dependency('SystemTimer')
|
67
26
|
end
|
68
27
|
end
|
69
|
-
|
data/lib/leaderboard.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
require 'redis'
|
2
|
+
require 'leaderboard/version'
|
2
3
|
|
3
4
|
class Leaderboard
|
4
|
-
VERSION = '2.0.1'.freeze
|
5
|
-
|
6
5
|
DEFAULT_PAGE_SIZE = 25
|
6
|
+
|
7
7
|
DEFAULT_OPTIONS = {
|
8
8
|
:page_size => DEFAULT_PAGE_SIZE
|
9
9
|
}
|
10
10
|
|
11
11
|
DEFAULT_REDIS_HOST = 'localhost'
|
12
|
+
|
12
13
|
DEFAULT_REDIS_PORT = 6379
|
14
|
+
|
13
15
|
DEFAULT_REDIS_OPTIONS = {
|
14
16
|
:host => DEFAULT_REDIS_HOST,
|
15
17
|
:port => DEFAULT_REDIS_PORT
|
@@ -22,9 +24,22 @@ class Leaderboard
|
|
22
24
|
:page_size => nil
|
23
25
|
}
|
24
26
|
|
27
|
+
# Name of the leaderboard.
|
25
28
|
attr_reader :leaderboard_name
|
29
|
+
|
30
|
+
# Page size to be used when paging through the leaderboard.
|
26
31
|
attr_reader :page_size
|
27
32
|
|
33
|
+
# Create a new instance of a leaderboard.
|
34
|
+
#
|
35
|
+
# @param leaderboard [String] Name of the leaderboard.
|
36
|
+
# @param options [Hash] Options for the leaderboard such as +:page_size+.
|
37
|
+
# @param redis_options [Hash] Options for configuring Redis.
|
38
|
+
#
|
39
|
+
# Examples
|
40
|
+
#
|
41
|
+
# leaderboard = Leaderboard.new('highscores')
|
42
|
+
# leaderboard = Leaderboard.new('highscores', {:page_size => 10})
|
28
43
|
def initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS)
|
29
44
|
@leaderboard_name = leaderboard_name
|
30
45
|
|
@@ -40,78 +55,156 @@ class Leaderboard
|
|
40
55
|
|
41
56
|
@redis_connection = Redis.new(redis_options) if @redis_connection.nil?
|
42
57
|
end
|
43
|
-
|
58
|
+
|
59
|
+
# Set the page size to be used when paging through the leaderboard. This method
|
60
|
+
# also has the side effect of setting the page size to the +DEFAULT_PAGE_SIZE+
|
61
|
+
# if the page size is less than 1.
|
62
|
+
#
|
63
|
+
# @param page_size [int] Page size.
|
44
64
|
def page_size=(page_size)
|
45
65
|
page_size = DEFAULT_PAGE_SIZE if page_size < 1
|
46
66
|
|
47
67
|
@page_size = page_size
|
48
68
|
end
|
49
69
|
|
70
|
+
# Disconnect the Redis connection.
|
50
71
|
def disconnect
|
51
72
|
@redis_connection.client.disconnect
|
52
73
|
end
|
53
74
|
|
75
|
+
# Delete the current leaderboard.
|
54
76
|
def delete_leaderboard
|
55
77
|
delete_leaderboard_named(@leaderboard_name)
|
56
78
|
end
|
57
79
|
|
80
|
+
# Delete the named leaderboard.
|
81
|
+
#
|
82
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
58
83
|
def delete_leaderboard_named(leaderboard_name)
|
59
84
|
@redis_connection.del(leaderboard_name)
|
60
85
|
end
|
61
86
|
|
87
|
+
# Rank a member in the leaderboard.
|
88
|
+
#
|
89
|
+
# @param member [String] Member name.
|
90
|
+
# @param score [float] Member score.
|
62
91
|
def rank_member(member, score)
|
63
92
|
rank_member_in(@leaderboard_name, member, score)
|
64
93
|
end
|
65
94
|
|
95
|
+
# Rank a member in the named leaderboard.
|
96
|
+
#
|
97
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
98
|
+
# @param member [String] Member name.
|
99
|
+
# @param score [float] Member score.
|
66
100
|
def rank_member_in(leaderboard_name, member, score)
|
67
101
|
@redis_connection.zadd(leaderboard_name, score, member)
|
68
102
|
end
|
69
103
|
|
104
|
+
# Remove a member from the leaderboard.
|
105
|
+
#
|
106
|
+
# @param member [String] Member name.
|
70
107
|
def remove_member(member)
|
71
108
|
remove_member_from(@leaderboard_name, member)
|
72
109
|
end
|
73
110
|
|
111
|
+
# Remove a member from the named leaderboard.
|
112
|
+
#
|
113
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
114
|
+
# @param member [String] Member name.
|
74
115
|
def remove_member_from(leaderboard_name, member)
|
75
116
|
@redis_connection.zrem(leaderboard_name, member)
|
76
117
|
end
|
77
118
|
|
119
|
+
# Retrieve the total number of members in the leaderboard.
|
120
|
+
#
|
121
|
+
# @return total number of members in the leaderboard.
|
78
122
|
def total_members
|
79
123
|
total_members_in(@leaderboard_name)
|
80
124
|
end
|
81
125
|
|
126
|
+
# Retrieve the total number of members in the named leaderboard.
|
127
|
+
#
|
128
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
129
|
+
#
|
130
|
+
# @return the total number of members in the named leaderboard.
|
82
131
|
def total_members_in(leaderboard_name)
|
83
132
|
@redis_connection.zcard(leaderboard_name)
|
84
133
|
end
|
85
134
|
|
135
|
+
# Retrieve the total number of pages in the leaderboard.
|
136
|
+
#
|
137
|
+
# @return the total number of pages in the leaderboard.
|
86
138
|
def total_pages
|
87
139
|
total_pages_in(@leaderboard_name)
|
88
140
|
end
|
89
141
|
|
142
|
+
# Retrieve the total number of pages in the named leaderboard.
|
143
|
+
#
|
144
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
145
|
+
# @param page_size [int] Page size to be used when paging through the leaderboard.
|
146
|
+
#
|
147
|
+
# @return the total number of pages in the named leaderboard.
|
90
148
|
def total_pages_in(leaderboard_name, page_size = nil)
|
91
149
|
page_size ||= @page_size.to_f
|
92
150
|
(total_members_in(leaderboard_name) / page_size.to_f).ceil
|
93
151
|
end
|
94
152
|
|
153
|
+
# Retrieve the total members in a given score range from the leaderboard.
|
154
|
+
#
|
155
|
+
# @param min_score [float] Minimum score.
|
156
|
+
# @param max_score [float] Maximum score.
|
157
|
+
#
|
158
|
+
# @return the total members in a given score range from the leaderboard.
|
95
159
|
def total_members_in_score_range(min_score, max_score)
|
96
160
|
total_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
97
161
|
end
|
98
162
|
|
163
|
+
# Retrieve the total members in a given score range from the named leaderboard.
|
164
|
+
#
|
165
|
+
# @param leaderboard_name Name of the leaderboard.
|
166
|
+
# @param min_score [float] Minimum score.
|
167
|
+
# @param max_score [float] Maximum score.
|
168
|
+
#
|
169
|
+
# @return the total members in a given score range from the named leaderboard.
|
99
170
|
def total_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
100
171
|
@redis_connection.zcount(leaderboard_name, min_score, max_score)
|
101
172
|
end
|
102
173
|
|
174
|
+
# Change the score for a member in the leaderboard by a score delta which can be positive or negative.
|
175
|
+
#
|
176
|
+
# @param member [String] Member name.
|
177
|
+
# @param delta [float] Score change.
|
103
178
|
def change_score_for(member, delta)
|
104
179
|
change_score_for_member_in(@leaderboard_name, member, delta)
|
105
180
|
end
|
106
181
|
|
182
|
+
# Change the score for a member in the named leaderboard by a delta which can be positive or negative.
|
183
|
+
#
|
184
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
185
|
+
# @param member [String] Member name.
|
186
|
+
# @param delta [float] Score change.
|
107
187
|
def change_score_for_member_in(leaderboard_name, member, delta)
|
108
188
|
@redis_connection.zincrby(leaderboard_name, delta, member)
|
109
189
|
end
|
110
190
|
|
191
|
+
# Retrieve the rank for a member in the leaderboard.
|
192
|
+
#
|
193
|
+
# @param member [String] Member name.
|
194
|
+
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
195
|
+
#
|
196
|
+
# @return the rank for a member in the leaderboard.
|
111
197
|
def rank_for(member, use_zero_index_for_rank = false)
|
112
198
|
rank_for_in(@leaderboard_name, member, use_zero_index_for_rank)
|
113
199
|
end
|
114
200
|
|
201
|
+
# Retrieve the rank for a member in the named leaderboard.
|
202
|
+
#
|
203
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
204
|
+
# @param member [String] Member name.
|
205
|
+
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
206
|
+
#
|
207
|
+
# @return the rank for a member in the leaderboard.
|
115
208
|
def rank_for_in(leaderboard_name, member, use_zero_index_for_rank = false)
|
116
209
|
if use_zero_index_for_rank
|
117
210
|
return @redis_connection.zrevrank(leaderboard_name, member)
|
@@ -120,26 +213,61 @@ class Leaderboard
|
|
120
213
|
end
|
121
214
|
end
|
122
215
|
|
216
|
+
# Retrieve the score for a member in the leaderboard.
|
217
|
+
#
|
218
|
+
# @param member Member name.
|
219
|
+
#
|
220
|
+
# @return the score for a member in the leaderboard.
|
123
221
|
def score_for(member)
|
124
222
|
score_for_in(@leaderboard_name, member)
|
125
223
|
end
|
126
224
|
|
225
|
+
# Retrieve the score for a member in the named leaderboard.
|
226
|
+
#
|
227
|
+
# @param leaderboard_name Name of the leaderboard.
|
228
|
+
# @param member [String] Member name.
|
229
|
+
#
|
230
|
+
# @return the score for a member in the leaderboard.
|
127
231
|
def score_for_in(leaderboard_name, member)
|
128
232
|
@redis_connection.zscore(leaderboard_name, member).to_f
|
129
233
|
end
|
130
234
|
|
235
|
+
# Check to see if a member exists in the leaderboard.
|
236
|
+
#
|
237
|
+
# @param member [String] Member name.
|
238
|
+
#
|
239
|
+
# @return +true+ if the member exists in the leaderboard, +false+ otherwise.
|
131
240
|
def check_member?(member)
|
132
241
|
check_member_in?(@leaderboard_name, member)
|
133
242
|
end
|
134
243
|
|
244
|
+
# Check to see if a member exists in the named leaderboard.
|
245
|
+
#
|
246
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
247
|
+
# @param member [String] Member name.
|
248
|
+
#
|
249
|
+
# @return +true+ if the member exists in the named leaderboard, +false+ otherwise.
|
135
250
|
def check_member_in?(leaderboard_name, member)
|
136
251
|
!@redis_connection.zscore(leaderboard_name, member).nil?
|
137
252
|
end
|
138
253
|
|
254
|
+
# Retrieve the score and rank for a member in the leaderboard.
|
255
|
+
#
|
256
|
+
# @param member [String] Member name.
|
257
|
+
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
258
|
+
#
|
259
|
+
# @return the score and rank for a member in the leaderboard as a Hash.
|
139
260
|
def score_and_rank_for(member, use_zero_index_for_rank = false)
|
140
261
|
score_and_rank_for_in(@leaderboard_name, member, use_zero_index_for_rank)
|
141
262
|
end
|
142
263
|
|
264
|
+
# Retrieve the score and rank for a member in the named leaderboard.
|
265
|
+
#
|
266
|
+
# @param leaderboard_name [String]Name of the leaderboard.
|
267
|
+
# @param member [String] Member name.
|
268
|
+
# @param use_zero_index_for_rank [boolean, false] If the returned rank should be 0-indexed.
|
269
|
+
#
|
270
|
+
# @return the score and rank for a member in the named leaderboard as a Hash.
|
143
271
|
def score_and_rank_for_in(leaderboard_name, member, use_zero_index_for_rank = false)
|
144
272
|
responses = @redis_connection.multi do |transaction|
|
145
273
|
transaction.zscore(leaderboard_name, member)
|
@@ -154,19 +282,41 @@ class Leaderboard
|
|
154
282
|
{:member => member, :score => responses[0], :rank => responses[1]}
|
155
283
|
end
|
156
284
|
|
285
|
+
# Remove members from the leaderboard in a given score range.
|
286
|
+
#
|
287
|
+
# @param min_score [float] Minimum score.
|
288
|
+
# @param max_score [float] Maximum score.
|
157
289
|
def remove_members_in_score_range(min_score, max_score)
|
158
290
|
remove_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
159
291
|
end
|
160
292
|
|
293
|
+
# Remove members from the named leaderboard in a given score range.
|
294
|
+
#
|
295
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
296
|
+
# @param min_score [float] Minimum score.
|
297
|
+
# @param max_score [float] Maximum score.
|
161
298
|
def remove_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
162
299
|
@redis_connection.zremrangebyscore(leaderboard_name, min_score, max_score)
|
163
300
|
end
|
164
301
|
|
302
|
+
# Retrieve the percentile for a member in the leaderboard.
|
303
|
+
#
|
304
|
+
# @param member [String] Member name.
|
305
|
+
#
|
306
|
+
# @return the percentile for a member in the leaderboard. Return +nil+ for a non-existent member.
|
165
307
|
def percentile_for(member)
|
166
308
|
percentile_for_in(@leaderboard_name, member)
|
167
309
|
end
|
168
310
|
|
311
|
+
# Retrieve the percentile for a member in the named leaderboard.
|
312
|
+
#
|
313
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
314
|
+
# @param member [String] Member name.
|
315
|
+
#
|
316
|
+
# @return the percentile for a member in the named leaderboard.
|
169
317
|
def percentile_for_in(leaderboard_name, member)
|
318
|
+
return nil unless check_member_in?(leaderboard_name, member)
|
319
|
+
|
170
320
|
responses = @redis_connection.multi do |transaction|
|
171
321
|
transaction.zcard(leaderboard_name)
|
172
322
|
transaction.zrevrank(leaderboard_name, member)
|
@@ -175,10 +325,23 @@ class Leaderboard
|
|
175
325
|
((responses[0] - responses[1] - 1).to_f / responses[0].to_f * 100).ceil
|
176
326
|
end
|
177
327
|
|
328
|
+
# Retrieve a page of leaders from the leaderboard.
|
329
|
+
#
|
330
|
+
# @param current_page [int] Page to retrieve from the leaderboard.
|
331
|
+
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
332
|
+
#
|
333
|
+
# @return a page of leaders from the leaderboard.
|
178
334
|
def leaders(current_page, options = {})
|
179
335
|
leaders_in(@leaderboard_name, current_page, options)
|
180
336
|
end
|
181
337
|
|
338
|
+
# Retrieve a page of leaders from the named leaderboard.
|
339
|
+
#
|
340
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
341
|
+
# @param current_page [int] Page to retrieve from the named leaderboard.
|
342
|
+
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
343
|
+
#
|
344
|
+
# @return a page of leaders from the named leaderboard.
|
182
345
|
def leaders_in(leaderboard_name, current_page, options = {})
|
183
346
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
184
347
|
leaderboard_options.merge!(options)
|
@@ -210,15 +373,30 @@ class Leaderboard
|
|
210
373
|
end
|
211
374
|
end
|
212
375
|
|
376
|
+
# Retrieve a page of leaders from the leaderboard around a given member.
|
377
|
+
#
|
378
|
+
# @param member [String] Member name.
|
379
|
+
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
380
|
+
#
|
381
|
+
# @return a page of leaders from the leaderboard around a given member.
|
213
382
|
def around_me(member, options = {})
|
214
383
|
around_me_in(@leaderboard_name, member, options)
|
215
384
|
end
|
216
385
|
|
386
|
+
# Retrieve a page of leaders from the named leaderboard around a given member.
|
387
|
+
#
|
388
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
389
|
+
# @param member [String] Member name.
|
390
|
+
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
391
|
+
#
|
392
|
+
# @return a page of leaders from the named leaderboard around a given member. Returns an empty array for a non-existent member.
|
217
393
|
def around_me_in(leaderboard_name, member, options = {})
|
218
394
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
219
395
|
leaderboard_options.merge!(options)
|
220
396
|
|
221
397
|
reverse_rank_for_member = @redis_connection.zrevrank(leaderboard_name, member)
|
398
|
+
|
399
|
+
return [] unless reverse_rank_for_member
|
222
400
|
|
223
401
|
page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size
|
224
402
|
|
@@ -237,10 +415,23 @@ class Leaderboard
|
|
237
415
|
end
|
238
416
|
end
|
239
417
|
|
418
|
+
# Retrieve a page of leaders from the leaderboard for a given list of members.
|
419
|
+
#
|
420
|
+
# @param members [Array] Member names.
|
421
|
+
# @param options [Hash] Options to be used when retrieving the page from the leaderboard.
|
422
|
+
#
|
423
|
+
# @return a page of leaders from the leaderboard for a given list of members.
|
240
424
|
def ranked_in_list(members, options = {})
|
241
425
|
ranked_in_list_in(@leaderboard_name, members, options)
|
242
426
|
end
|
243
427
|
|
428
|
+
# Retrieve a page of leaders from the named leaderboard for a given list of members.
|
429
|
+
#
|
430
|
+
# @param leaderboard_name [String] Name of the leaderboard.
|
431
|
+
# @param members [Array] Member names.
|
432
|
+
# @param options [Hash] Options to be used when retrieving the page from the named leaderboard.
|
433
|
+
#
|
434
|
+
# @return a page of leaders from the named leaderboard for a given list of members.
|
244
435
|
def ranked_in_list_in(leaderboard_name, members, options = {})
|
245
436
|
leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
|
246
437
|
leaderboard_options.merge!(options)
|
@@ -262,7 +453,7 @@ class Leaderboard
|
|
262
453
|
if leaderboard_options[:use_zero_index_for_rank]
|
263
454
|
data[:rank] = responses[index * 2]
|
264
455
|
else
|
265
|
-
data[:rank] = responses[index * 2] + 1
|
456
|
+
data[:rank] = responses[index * 2] + 1 rescue nil
|
266
457
|
end
|
267
458
|
|
268
459
|
data[:score] = responses[index * 2 + 1].to_f
|
@@ -274,7 +465,7 @@ class Leaderboard
|
|
274
465
|
if leaderboard_options[:use_zero_index_for_rank]
|
275
466
|
data[:rank] = responses[index]
|
276
467
|
else
|
277
|
-
data[:rank] = responses[index] + 1
|
468
|
+
data[:rank] = responses[index] + 1 rescue nil
|
278
469
|
end
|
279
470
|
end
|
280
471
|
end
|
@@ -285,18 +476,31 @@ class Leaderboard
|
|
285
476
|
ranks_for_members
|
286
477
|
end
|
287
478
|
|
288
|
-
# Merge leaderboards given by keys with this leaderboard into destination
|
479
|
+
# Merge leaderboards given by keys with this leaderboard into a named destination leaderboard.
|
480
|
+
#
|
481
|
+
# @param destination [String] Destination leaderboard name.
|
482
|
+
# @param keys [Array] Leaderboards to be merged with the current leaderboard.
|
483
|
+
# @param options [Hash] Options for merging the leaderboards.
|
289
484
|
def merge_leaderboards(destination, keys, options = {:aggregate => :sum})
|
290
485
|
@redis_connection.zunionstore(destination, keys.insert(0, @leaderboard_name), options)
|
291
486
|
end
|
292
487
|
|
293
|
-
# Intersect leaderboards given by keys with this leaderboard into destination
|
488
|
+
# Intersect leaderboards given by keys with this leaderboard into a named destination leaderboard.
|
489
|
+
#
|
490
|
+
# @param destination [String] Destination leaderboard name.
|
491
|
+
# @param keys [Array] Leaderboards to be merged with the current leaderboard.
|
492
|
+
# @param options [Hash] Options for intersecting the leaderboards.
|
294
493
|
def intersect_leaderboards(destination, keys, options = {:aggregate => :sum})
|
295
494
|
@redis_connection.zinterstore(destination, keys.insert(0, @leaderboard_name), options)
|
296
495
|
end
|
297
496
|
|
298
497
|
private
|
299
498
|
|
499
|
+
# Validate and return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1.
|
500
|
+
#
|
501
|
+
# @param page_size [int] Page size.
|
502
|
+
#
|
503
|
+
# @return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1.
|
300
504
|
def validate_page_size(page_size)
|
301
505
|
if page_size && page_size < 1
|
302
506
|
page_size = DEFAULT_PAGE_SIZE
|
data/test/test_helper.rb
ADDED
data/test/test_leaderboard.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'test_helper'
|
2
2
|
|
3
3
|
class TestLeaderboard < Test::Unit::TestCase
|
4
4
|
def setup
|
@@ -13,7 +13,7 @@ class TestLeaderboard < Test::Unit::TestCase
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def test_version
|
16
|
-
assert_equal '2.0.
|
16
|
+
assert_equal '2.0.2', Leaderboard::VERSION
|
17
17
|
end
|
18
18
|
|
19
19
|
def test_initialize_with_defaults
|
@@ -418,7 +418,44 @@ class TestLeaderboard < Test::Unit::TestCase
|
|
418
418
|
assert_equal 25, @leaderboard.percentile_for('member_4')
|
419
419
|
assert_equal 92, @leaderboard.percentile_for('member_12')
|
420
420
|
end
|
421
|
+
|
422
|
+
def test_around_me_for_invalid_member
|
423
|
+
rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)
|
424
|
+
|
425
|
+
leaders_around_me = @leaderboard.around_me('jones', Leaderboard::DEFAULT_LEADERBOARD_REQUEST_OPTIONS.merge({:page_size => 3}))
|
426
|
+
assert_equal 0, leaders_around_me.size
|
427
|
+
end
|
428
|
+
|
429
|
+
def test_score_and_rank_for_non_existent_member
|
430
|
+
score_and_rank_for_member = @leaderboard.score_and_rank_for('jones')
|
421
431
|
|
432
|
+
assert_equal 'jones', score_and_rank_for_member[:member]
|
433
|
+
assert_equal 0.0, score_and_rank_for_member[:score]
|
434
|
+
assert_nil score_and_rank_for_member[:rank]
|
435
|
+
end
|
436
|
+
|
437
|
+
def test_ranked_in_list_for_non_existent_member
|
438
|
+
rank_members_in_leaderboard
|
439
|
+
|
440
|
+
members = ['member_1', 'member_5', 'jones']
|
441
|
+
ranked_members = @leaderboard.ranked_in_list(members)
|
442
|
+
|
443
|
+
assert_equal 3, ranked_members.size
|
444
|
+
assert_nil ranked_members[2][:rank]
|
445
|
+
end
|
446
|
+
|
447
|
+
def test_percentile_for_non_existent_member
|
448
|
+
percentile = @leaderboard.percentile_for('jones')
|
449
|
+
|
450
|
+
assert_nil percentile
|
451
|
+
end
|
452
|
+
|
453
|
+
def test_change_score_for_non_existent_member
|
454
|
+
assert_equal 0.0, @leaderboard.score_for('jones')
|
455
|
+
@leaderboard.change_score_for('jones', 5)
|
456
|
+
assert_equal 5.0, @leaderboard.score_for('jones')
|
457
|
+
end
|
458
|
+
|
422
459
|
private
|
423
460
|
|
424
461
|
def rank_members_in_leaderboard(members_to_add = 5)
|
metadata
CHANGED
@@ -1,126 +1,92 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: leaderboard
|
3
|
-
version: !ruby/object:Gem::Version
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 2.0.2
|
4
5
|
prerelease:
|
5
|
-
version: 2.0.1
|
6
6
|
platform: ruby
|
7
|
-
authors:
|
7
|
+
authors:
|
8
8
|
- David Czarnecki
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-02-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
16
15
|
name: redis
|
17
|
-
requirement: &
|
16
|
+
requirement: &70115900454580 !ruby/object:Gem::Requirement
|
18
17
|
none: false
|
19
|
-
requirements:
|
20
|
-
- -
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version:
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
23
22
|
type: :runtime
|
24
23
|
prerelease: false
|
25
|
-
version_requirements: *
|
26
|
-
- !ruby/object:Gem::Dependency
|
27
|
-
name:
|
28
|
-
requirement: &
|
24
|
+
version_requirements: *70115900454580
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70115900454140 !ruby/object:Gem::Requirement
|
29
28
|
none: false
|
30
|
-
requirements:
|
31
|
-
- -
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version:
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
34
33
|
type: :development
|
35
34
|
prerelease: false
|
36
|
-
version_requirements: *
|
37
|
-
- !ruby/object:Gem::Dependency
|
38
|
-
name: jeweler
|
39
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
40
|
-
none: false
|
41
|
-
requirements:
|
42
|
-
- - ">="
|
43
|
-
- !ruby/object:Gem::Version
|
44
|
-
version: "0"
|
45
|
-
type: :development
|
46
|
-
prerelease: false
|
47
|
-
version_requirements: *id003
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: rcov
|
50
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
51
|
-
none: false
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: "0"
|
56
|
-
type: :development
|
57
|
-
prerelease: false
|
58
|
-
version_requirements: *id004
|
59
|
-
- !ruby/object:Gem::Dependency
|
60
|
-
name: rdoc
|
61
|
-
requirement: &id005 !ruby/object:Gem::Requirement
|
62
|
-
none: false
|
63
|
-
requirements:
|
64
|
-
- - ">="
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: "0"
|
67
|
-
type: :development
|
68
|
-
prerelease: false
|
69
|
-
version_requirements: *id005
|
35
|
+
version_requirements: *70115900454140
|
70
36
|
description: Leaderboards backed by Redis in Ruby
|
71
|
-
email:
|
37
|
+
email:
|
38
|
+
- dczarnecki@agoragames.com
|
72
39
|
executables: []
|
73
|
-
|
74
40
|
extensions: []
|
75
|
-
|
76
|
-
|
77
|
-
- LICENSE.txt
|
78
|
-
- README.rdoc
|
79
|
-
files:
|
41
|
+
extra_rdoc_files: []
|
42
|
+
files:
|
80
43
|
- .document
|
44
|
+
- .gitignore
|
81
45
|
- .rvmrc
|
82
46
|
- CHANGELOG.markdown
|
83
47
|
- Gemfile
|
84
48
|
- LICENSE.txt
|
85
|
-
- README.
|
49
|
+
- README.markdown
|
86
50
|
- Rakefile
|
87
|
-
- VERSION
|
88
51
|
- leaderboard.gemspec
|
89
52
|
- lib/leaderboard.rb
|
53
|
+
- lib/leaderboard/version.rb
|
90
54
|
- test/db/.gitkeep
|
91
|
-
- test/helper.rb
|
92
55
|
- test/test.conf
|
56
|
+
- test/test_helper.rb
|
93
57
|
- test/test_leaderboard.rb
|
94
|
-
homepage:
|
95
|
-
licenses:
|
96
|
-
- MIT
|
58
|
+
homepage: https://github.com/agoragames/leaderboard
|
59
|
+
licenses: []
|
97
60
|
post_install_message:
|
98
61
|
rdoc_options: []
|
99
|
-
|
100
|
-
require_paths:
|
62
|
+
require_paths:
|
101
63
|
- lib
|
102
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
65
|
none: false
|
104
|
-
requirements:
|
105
|
-
- -
|
106
|
-
- !ruby/object:Gem::Version
|
107
|
-
|
108
|
-
segments:
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
segments:
|
109
71
|
- 0
|
110
|
-
|
111
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
hash: 822397651931818712
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
74
|
none: false
|
113
|
-
requirements:
|
114
|
-
- -
|
115
|
-
- !ruby/object:Gem::Version
|
116
|
-
version:
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
hash: 822397651931818712
|
117
82
|
requirements: []
|
118
|
-
|
119
|
-
|
120
|
-
rubygems_version: 1.8.11
|
83
|
+
rubyforge_project: leaderboard
|
84
|
+
rubygems_version: 1.8.10
|
121
85
|
signing_key:
|
122
86
|
specification_version: 3
|
123
87
|
summary: Leaderboards backed by Redis in Ruby
|
124
|
-
test_files:
|
125
|
-
- test/
|
88
|
+
test_files:
|
89
|
+
- test/db/.gitkeep
|
90
|
+
- test/test.conf
|
91
|
+
- test/test_helper.rb
|
126
92
|
- test/test_leaderboard.rb
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2.0.1
|
data/test/helper.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'bundler'
|
3
|
-
begin
|
4
|
-
Bundler.setup(:default, :development)
|
5
|
-
rescue Bundler::BundlerError => e
|
6
|
-
$stderr.puts e.message
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
-
exit e.status_code
|
9
|
-
end
|
10
|
-
require 'test/unit'
|
11
|
-
|
12
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
-
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
-
require 'leaderboard'
|
15
|
-
|
16
|
-
class Test::Unit::TestCase
|
17
|
-
end
|