leaderboard 1.0.1 → 1.0.2
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/CHANGELOG.markdown +6 -0
- data/README.rdoc +3 -1
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/leaderboard.gemspec +4 -4
- data/lib/leaderboard.rb +90 -24
- data/test/test_leaderboard.rb +47 -2
- metadata +6 -6
data/CHANGELOG.markdown
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# leaderboard 1.0.2 (2011-02-25)
|
2
|
+
|
3
|
+
* 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
|
4
|
+
* Added `merge_leaderboards(destination, keys, options = {:aggregate => :min})` method to merge leaderboards given by keys with this leaderboard into destination
|
5
|
+
* Added `intersect_leaderboards(destination, keys, options = {:aggregate => :min})` method to intersect leaderboards given by keys with this leaderboard into destination
|
6
|
+
|
1
7
|
# leaderboard 1.0.1 (2011-02-16)
|
2
8
|
|
3
9
|
* `redis_options` can be passed in the initializer to pass options for the connection to Redis
|
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= leaderboard
|
2
2
|
|
3
|
-
Leaderboards backed by Redis, http://redis.io.
|
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
|
|
@@ -91,6 +91,8 @@ Get rank and score for an arbitrary list of members (e.g. friends):
|
|
91
91
|
check_member?(member): Check to see whether member is in the leaderboard
|
92
92
|
score_and_rank_for(member, use_zero_index_for_rank = false): Retrieve the score and rank for a member in a single call
|
93
93
|
remove_members_in_score_range(min_score, max_score): Remove members from the leaderboard within a score range
|
94
|
+
merge_leaderboards(destination, keys, options = {:aggregate => :min}): Merge leaderboards given by keys with this leaderboard into destination
|
95
|
+
intersect_leaderboards(destination, keys, options = {:aggregate => :min}): Intersect leaderboards given by keys with this leaderboard into destination
|
94
96
|
|
95
97
|
== Performance Metrics
|
96
98
|
|
data/Rakefile
CHANGED
@@ -15,8 +15,8 @@ Jeweler::Tasks.new do |gem|
|
|
15
15
|
gem.name = "leaderboard"
|
16
16
|
gem.homepage = "http://github.com/agoragames/leaderboard"
|
17
17
|
gem.license = "MIT"
|
18
|
-
gem.summary = %Q{Leaderboards backed by Redis}
|
19
|
-
gem.description = %Q{Leaderboards backed by Redis}
|
18
|
+
gem.summary = %Q{Leaderboards backed by Redis in Ruby}
|
19
|
+
gem.description = %Q{Leaderboards backed by Redis in Ruby}
|
20
20
|
gem.email = "dczarnecki@agoragames.com"
|
21
21
|
gem.authors = ["David Czarnecki"]
|
22
22
|
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.2
|
data/leaderboard.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{leaderboard}
|
8
|
-
s.version = "1.0.
|
8
|
+
s.version = "1.0.2"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["David Czarnecki"]
|
12
|
-
s.date = %q{2011-02-
|
13
|
-
s.description = %q{Leaderboards backed by Redis}
|
12
|
+
s.date = %q{2011-02-25}
|
13
|
+
s.description = %q{Leaderboards backed by Redis in Ruby}
|
14
14
|
s.email = %q{dczarnecki@agoragames.com}
|
15
15
|
s.extra_rdoc_files = [
|
16
16
|
"LICENSE.txt",
|
@@ -36,7 +36,7 @@ Gem::Specification.new do |s|
|
|
36
36
|
s.licenses = ["MIT"]
|
37
37
|
s.require_paths = ["lib"]
|
38
38
|
s.rubygems_version = %q{1.3.7}
|
39
|
-
s.summary = %q{Leaderboards backed by Redis}
|
39
|
+
s.summary = %q{Leaderboards backed by Redis in Ruby}
|
40
40
|
s.test_files = [
|
41
41
|
"test/helper.rb",
|
42
42
|
"test/test_leaderboard.rb"
|
data/lib/leaderboard.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'redis'
|
2
2
|
|
3
3
|
class Leaderboard
|
4
|
-
VERSION = '1.0.
|
4
|
+
VERSION = '1.0.2'.freeze
|
5
5
|
|
6
6
|
DEFAULT_PAGE_SIZE = 25
|
7
7
|
DEFAULT_REDIS_HOST = 'localhost'
|
@@ -33,54 +33,102 @@ class Leaderboard
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def add_member(member, score)
|
36
|
-
|
36
|
+
add_member_to(@leaderboard_name, member, score)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_member_to(leaderboard_name, member, score)
|
40
|
+
@redis_connection.zadd(leaderboard_name, score, member)
|
37
41
|
end
|
38
42
|
|
39
43
|
def remove_member(member)
|
40
|
-
|
44
|
+
remove_member_from(@leaderboard_name, member)
|
45
|
+
end
|
46
|
+
|
47
|
+
def remove_member_from(leaderboard_name, member)
|
48
|
+
@redis_connection.zrem(leaderboard_name, member)
|
41
49
|
end
|
42
50
|
|
43
51
|
def total_members
|
44
|
-
|
52
|
+
total_members_in(@leaderboard_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def total_members_in(leaderboard_name)
|
56
|
+
@redis_connection.zcard(leaderboard_name)
|
45
57
|
end
|
46
58
|
|
47
59
|
def total_pages
|
48
|
-
(
|
60
|
+
total_pages_in(@leaderboard_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
def total_pages_in(leaderboard_name)
|
64
|
+
(total_members_in(leaderboard_name) / @page_size.to_f).ceil
|
49
65
|
end
|
50
66
|
|
51
67
|
def total_members_in_score_range(min_score, max_score)
|
52
|
-
|
68
|
+
total_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
53
69
|
end
|
54
70
|
|
55
|
-
def
|
56
|
-
@redis_connection.
|
71
|
+
def total_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
72
|
+
@redis_connection.zcount(leaderboard_name, min_score, max_score)
|
57
73
|
end
|
58
74
|
|
59
|
-
def
|
75
|
+
def change_score_for(member, delta)
|
76
|
+
change_score_for_member_in(@leaderboard_name, member, delta)
|
77
|
+
end
|
78
|
+
|
79
|
+
def change_score_for_member_in(leaderboard_name, member, delta)
|
80
|
+
@redis_connection.zincrby(leaderboard_name, delta, member)
|
81
|
+
end
|
82
|
+
|
83
|
+
def rank_for(member, use_zero_index_for_rank = false)
|
84
|
+
rank_for_in(@leaderboard_name, member, use_zero_index_for_rank)
|
85
|
+
end
|
86
|
+
|
87
|
+
def rank_for_in(leaderboard_name, member, use_zero_index_for_rank = false)
|
60
88
|
if use_zero_index_for_rank
|
61
|
-
return @redis_connection.zrevrank(
|
89
|
+
return @redis_connection.zrevrank(leaderboard_name, member)
|
62
90
|
else
|
63
|
-
return @redis_connection.zrevrank(
|
91
|
+
return @redis_connection.zrevrank(leaderboard_name, member) + 1 rescue nil
|
64
92
|
end
|
65
93
|
end
|
66
94
|
|
67
95
|
def score_for(member)
|
68
|
-
|
96
|
+
score_for_in(@leaderboard_name, member)
|
69
97
|
end
|
70
98
|
|
99
|
+
def score_for_in(leaderboard_name, member)
|
100
|
+
@redis_connection.zscore(leaderboard_name, member).to_f
|
101
|
+
end
|
102
|
+
|
71
103
|
def check_member?(member)
|
72
|
-
|
104
|
+
check_member_in?(@leaderboard_name, member)
|
105
|
+
end
|
106
|
+
|
107
|
+
def check_member_in?(leaderboard_name, member)
|
108
|
+
!@redis_connection.zscore(leaderboard_name, member).nil?
|
73
109
|
end
|
74
110
|
|
75
111
|
def score_and_rank_for(member, use_zero_index_for_rank = false)
|
76
|
-
|
112
|
+
score_and_rank_for_in(@leaderboard_name, member, use_zero_index_for_rank)
|
113
|
+
end
|
114
|
+
|
115
|
+
def score_and_rank_for_in(leaderboard_name, member, use_zero_index_for_rank = false)
|
116
|
+
{:member => member, :score => score_for_in(leaderboard_name, member), :rank => rank_for_in(leaderboard_name, member, use_zero_index_for_rank)}
|
77
117
|
end
|
78
118
|
|
79
119
|
def remove_members_in_score_range(min_score, max_score)
|
80
|
-
|
120
|
+
remove_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
121
|
+
end
|
122
|
+
|
123
|
+
def remove_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
124
|
+
@redis_connection.zremrangebyscore(leaderboard_name, min_score, max_score)
|
81
125
|
end
|
82
126
|
|
83
127
|
def leaders(current_page, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
|
128
|
+
leaders_in(@leaderboard_name, current_page, with_scores, with_rank, use_zero_index_for_rank)
|
129
|
+
end
|
130
|
+
|
131
|
+
def leaders_in(leaderboard_name, current_page, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
|
84
132
|
if current_page < 1
|
85
133
|
current_page = 1
|
86
134
|
end
|
@@ -98,16 +146,20 @@ class Leaderboard
|
|
98
146
|
|
99
147
|
ending_offset = (starting_offset + @page_size) - 1
|
100
148
|
|
101
|
-
raw_leader_data = @redis_connection.zrevrange(
|
149
|
+
raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
|
102
150
|
if raw_leader_data
|
103
|
-
massage_leader_data(raw_leader_data, with_rank, use_zero_index_for_rank)
|
151
|
+
massage_leader_data(leaderboard_name, raw_leader_data, with_rank, use_zero_index_for_rank)
|
104
152
|
else
|
105
153
|
return nil
|
106
154
|
end
|
107
155
|
end
|
108
156
|
|
109
157
|
def around_me(member, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
|
110
|
-
|
158
|
+
around_me_in(@leaderboard_name, member, with_scores, with_rank, use_zero_index_for_rank)
|
159
|
+
end
|
160
|
+
|
161
|
+
def around_me_in(leaderboard_name, member, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
|
162
|
+
reverse_rank_for_member = @redis_connection.zrevrank(leaderboard_name, member)
|
111
163
|
|
112
164
|
starting_offset = reverse_rank_for_member - (@page_size / 2)
|
113
165
|
if starting_offset < 0
|
@@ -116,22 +168,26 @@ class Leaderboard
|
|
116
168
|
|
117
169
|
ending_offset = (starting_offset + @page_size) - 1
|
118
170
|
|
119
|
-
raw_leader_data = @redis_connection.zrevrange(
|
171
|
+
raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
|
120
172
|
if raw_leader_data
|
121
|
-
massage_leader_data(raw_leader_data, with_rank, use_zero_index_for_rank)
|
173
|
+
massage_leader_data(leaderboard_name, raw_leader_data, with_rank, use_zero_index_for_rank)
|
122
174
|
else
|
123
175
|
return nil
|
124
176
|
end
|
125
177
|
end
|
126
178
|
|
127
179
|
def ranked_in_list(members, with_scores = true, use_zero_index_for_rank = false)
|
180
|
+
ranked_in_list_in(@leaderboard_name, members, with_scores, use_zero_index_for_rank)
|
181
|
+
end
|
182
|
+
|
183
|
+
def ranked_in_list_in(leaderboard_name, members, with_scores = true, use_zero_index_for_rank = false)
|
128
184
|
ranks_for_members = []
|
129
185
|
|
130
186
|
members.each do |member|
|
131
187
|
data = {}
|
132
188
|
data[:member] = member
|
133
|
-
data[:rank] =
|
134
|
-
data[:score] =
|
189
|
+
data[:rank] = rank_for_in(leaderboard_name, member, use_zero_index_for_rank)
|
190
|
+
data[:score] = score_for_in(leaderboard_name, member) if with_scores
|
135
191
|
|
136
192
|
ranks_for_members << data
|
137
193
|
end
|
@@ -139,9 +195,19 @@ class Leaderboard
|
|
139
195
|
ranks_for_members
|
140
196
|
end
|
141
197
|
|
198
|
+
# Merge leaderboards given by keys with this leaderboard into destination
|
199
|
+
def merge_leaderboards(destination, keys, options = {:aggregate => :sum})
|
200
|
+
@redis_connection.zunionstore(destination, keys.insert(0, @leaderboard_name), options)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Intersect leaderboards given by keys with this leaderboard into destination
|
204
|
+
def intersect_leaderboards(destination, keys, options = {:aggregate => :sum})
|
205
|
+
@redis_connection.zinterstore(destination, keys.insert(0, @leaderboard_name), options)
|
206
|
+
end
|
207
|
+
|
142
208
|
private
|
143
209
|
|
144
|
-
def massage_leader_data(leaders, with_rank, use_zero_index_for_rank)
|
210
|
+
def massage_leader_data(leaderboard_name, leaders, with_rank, use_zero_index_for_rank)
|
145
211
|
member_attribute = true
|
146
212
|
leader_data = []
|
147
213
|
|
@@ -151,7 +217,7 @@ class Leaderboard
|
|
151
217
|
data[:member] = leader_data_item
|
152
218
|
else
|
153
219
|
data[:score] = leader_data_item.to_f
|
154
|
-
data[:rank] =
|
220
|
+
data[:rank] = rank_for_in(leaderboard_name, data[:member], use_zero_index_for_rank) if with_rank
|
155
221
|
leader_data << data
|
156
222
|
data = {}
|
157
223
|
end
|
data/test/test_leaderboard.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestLeaderboard < Test::Unit::TestCase
|
4
|
-
def setup
|
4
|
+
def setup
|
5
5
|
@leaderboard = Leaderboard.new('name')
|
6
6
|
@redis_connection = Redis.new
|
7
7
|
end
|
@@ -11,7 +11,7 @@ class TestLeaderboard < Test::Unit::TestCase
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def test_version
|
14
|
-
assert_equal '1.0.
|
14
|
+
assert_equal '1.0.2', Leaderboard::VERSION
|
15
15
|
end
|
16
16
|
|
17
17
|
def test_initialize_with_defaults
|
@@ -204,6 +204,51 @@ class TestLeaderboard < Test::Unit::TestCase
|
|
204
204
|
end
|
205
205
|
end
|
206
206
|
|
207
|
+
def test_merge_leaderboards
|
208
|
+
foo = Leaderboard.new('foo')
|
209
|
+
bar = Leaderboard.new('bar')
|
210
|
+
|
211
|
+
foo.add_member('foo_1', 1)
|
212
|
+
foo.add_member('foo_2', 2)
|
213
|
+
bar.add_member('bar_1', 3)
|
214
|
+
bar.add_member('bar_2', 4)
|
215
|
+
bar.add_member('bar_3', 5)
|
216
|
+
|
217
|
+
foobar_keys = foo.merge_leaderboards('foobar', ['bar'])
|
218
|
+
assert_equal 5, foobar_keys
|
219
|
+
|
220
|
+
foobar = Leaderboard.new('foobar')
|
221
|
+
assert_equal 5, foobar.total_members
|
222
|
+
|
223
|
+
first_leader_in_foobar = foobar.leaders(1).first
|
224
|
+
assert_equal 1, first_leader_in_foobar[:rank]
|
225
|
+
assert_equal 'bar_3', first_leader_in_foobar[:member]
|
226
|
+
assert_equal 5, first_leader_in_foobar[:score]
|
227
|
+
end
|
228
|
+
|
229
|
+
def test_intersect_leaderboards
|
230
|
+
foo = Leaderboard.new('foo')
|
231
|
+
bar = Leaderboard.new('bar')
|
232
|
+
|
233
|
+
foo.add_member('foo_1', 1)
|
234
|
+
foo.add_member('foo_2', 2)
|
235
|
+
foo.add_member('bar_3', 6)
|
236
|
+
bar.add_member('bar_1', 3)
|
237
|
+
bar.add_member('foo_1', 4)
|
238
|
+
bar.add_member('bar_3', 5)
|
239
|
+
|
240
|
+
foobar_keys = foo.intersect_leaderboards('foobar', ['bar'], {:aggregate => :max})
|
241
|
+
assert_equal 2, foobar_keys
|
242
|
+
|
243
|
+
foobar = Leaderboard.new('foobar')
|
244
|
+
assert_equal 2, foobar.total_members
|
245
|
+
|
246
|
+
first_leader_in_foobar = foobar.leaders(1).first
|
247
|
+
assert_equal 1, first_leader_in_foobar[:rank]
|
248
|
+
assert_equal 'bar_3', first_leader_in_foobar[:member]
|
249
|
+
assert_equal 6, first_leader_in_foobar[:score]
|
250
|
+
end
|
251
|
+
|
207
252
|
private
|
208
253
|
|
209
254
|
def add_members_to_leaderboard(members_to_add = 5)
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: leaderboard
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 1.0.
|
9
|
+
- 2
|
10
|
+
version: 1.0.2
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- David Czarnecki
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2011-02-
|
18
|
+
date: 2011-02-25 00:00:00 -05:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -80,7 +80,7 @@ dependencies:
|
|
80
80
|
- 0
|
81
81
|
version: "0"
|
82
82
|
requirement: *id004
|
83
|
-
description: Leaderboards backed by Redis
|
83
|
+
description: Leaderboards backed by Redis in Ruby
|
84
84
|
email: dczarnecki@agoragames.com
|
85
85
|
executables: []
|
86
86
|
|
@@ -137,7 +137,7 @@ rubyforge_project:
|
|
137
137
|
rubygems_version: 1.3.7
|
138
138
|
signing_key:
|
139
139
|
specification_version: 3
|
140
|
-
summary: Leaderboards backed by Redis
|
140
|
+
summary: Leaderboards backed by Redis in Ruby
|
141
141
|
test_files:
|
142
142
|
- test/helper.rb
|
143
143
|
- test/test_leaderboard.rb
|