multi-leaderboard 1.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.markdown +17 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +170 -0
- data/Rakefile +89 -0
- data/VERSION +1 -0
- data/leaderboard.gemspec +67 -0
- data/lib/leaderboard.rb +235 -0
- data/test/db/.gitkeep +0 -0
- data/test/helper.rb +17 -0
- data/test/test.conf +8 -0
- data/test/test_leaderboard.rb +259 -0
- metadata +144 -0
data/.document
ADDED
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm 1.8.7
|
data/CHANGELOG.markdown
ADDED
@@ -0,0 +1,17 @@
|
|
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
|
+
|
7
|
+
# leaderboard 1.0.1 (2011-02-16)
|
8
|
+
|
9
|
+
* `redis_options` can be passed in the initializer to pass options for the connection to Redis
|
10
|
+
* `page_size` is now settable outside of the initializer
|
11
|
+
* `check_member?(member)`: Check to see whether member is in the leaderboard
|
12
|
+
* `score_and_rank_for(member, use_zero_index_for_rank = false)`: Retrieve the score and rank for a member in a single call
|
13
|
+
* `remove_members_in_score_range(min_score, max_score)`: Remove members from the leaderboard within a score range
|
14
|
+
|
15
|
+
# leaderboard 1.0.0
|
16
|
+
|
17
|
+
* Initial release
|
data/Gemfile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
# Add dependencies to develop your gem here.
|
4
|
+
# Include everything needed to run rake, tests, features, etc.
|
5
|
+
group :development do
|
6
|
+
gem "bundler", "~> 1.0.0"
|
7
|
+
gem "jeweler", "~> 1.5.2"
|
8
|
+
gem "rcov", ">= 0"
|
9
|
+
end
|
10
|
+
|
11
|
+
gem 'redis', "~> 2.1.1"
|
12
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 David Czarnecki
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
= leaderboard
|
2
|
+
|
3
|
+
Leaderboards backed by Redis in Ruby, http://redis.io.
|
4
|
+
|
5
|
+
Builds off ideas proposed in http://blog.agoragames.com/2011/01/01/creating-high-score-tables-leaderboards-using-redis/.
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
Install the gem:
|
10
|
+
|
11
|
+
gem install leaderboard
|
12
|
+
|
13
|
+
Make sure your redis server is running! Redis configuration is outside the scope of this README, but
|
14
|
+
check out the Redis documentation, http://redis.io/documentation.
|
15
|
+
|
16
|
+
== Compatibility
|
17
|
+
|
18
|
+
The gem has been built and tested under Ruby 1.8.7 and Ruby 1.9.2
|
19
|
+
|
20
|
+
== Usage
|
21
|
+
|
22
|
+
Create a new leaderboard or attach to an existing leaderboard named 'highscores':
|
23
|
+
|
24
|
+
ruby-1.8.7-p302 > highscore_lb = Leaderboard.new('highscores')
|
25
|
+
=> #<Leaderboard:0x1018e4250 @page_size=25, @port=6379, @host="localhost", @redis_connection=#<Redis client v2.1.1 connected to redis://localhost:6379/0 (Redis v2.1.10)>, @leaderboard_name="highscores">
|
26
|
+
|
27
|
+
If you need to pass in options for Redis, you can do this with the redis_options parameter:
|
28
|
+
|
29
|
+
redis_options = {:host => 'localhost', :port => 6379, :password => 'password', :db => 'some_redis_db'}
|
30
|
+
highscore_lb = Leaderboard.new('highscores', redis_options[:host], redis_options[:port], Leaderboard::DEFAULT_PAGE_SIZE, redis_options))
|
31
|
+
|
32
|
+
You can set the page size to something other than the default page size (25):
|
33
|
+
|
34
|
+
ruby-1.8.7-p302 > highscore_lb.page_size = 5
|
35
|
+
=> 5
|
36
|
+
ruby-1.8.7-p302 > highscore_lb
|
37
|
+
=> #<Leaderboard:0x1018e2130 @leaderboard_name="highscores", @page_size=5, @port=6379, @redis_connection=#<Redis client v2.1.1 connected to redis://localhost:6379/0 (Redis v2.1.10)>, @host="localhost", @redis_options={:host=>"localhost", :port=>6379}>
|
38
|
+
|
39
|
+
Add members to your leaderboard:
|
40
|
+
|
41
|
+
ruby-1.8.7-p302 > 1.upto(10) do |index|
|
42
|
+
ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
|
43
|
+
ruby-1.8.7-p302 ?> end
|
44
|
+
=> 1
|
45
|
+
|
46
|
+
Get some information about your leaderboard:
|
47
|
+
|
48
|
+
ruby-1.8.7-p302 > highscore_lb.total_members
|
49
|
+
=> 10
|
50
|
+
ruby-1.8.7-p302 > highscore_lb.total_pages
|
51
|
+
=> 1
|
52
|
+
|
53
|
+
Get some information about a specific member(s) in the leaderboard:
|
54
|
+
|
55
|
+
ruby-1.8.7-p302 > highscore_lb.score_for('member_4')
|
56
|
+
=> 4.0
|
57
|
+
ruby-1.8.7-p302 > highscore_lb.rank_for('member_4')
|
58
|
+
=> 7
|
59
|
+
ruby-1.8.7-p302 > highscore_lb.rank_for('member_10')
|
60
|
+
=> 1
|
61
|
+
|
62
|
+
Get page 1 in the leaderboard:
|
63
|
+
|
64
|
+
ruby-1.8.7-p302 > highscore_lb.leaders(1)
|
65
|
+
=> [{:member=>"member_10", :rank=>1, :score=>"10"}, {:member=>"member_9", :rank=>2, :score=>"9"}, {:member=>"member_8", :rank=>3, :score=>"8"}, {:member=>"member_7", :rank=>4, :score=>"7"}, {:member=>"member_6", :rank=>5, :score=>"6"}, {:member=>"member_5", :rank=>6, :score=>"5"}, {:member=>"member_4", :rank=>7, :score=>"4"}, {:member=>"member_3", :rank=>8, :score=>"3"}, {:member=>"member_2", :rank=>9, :score=>"2"}, {:member=>"member_1", :rank=>10, :score=>"1"}]
|
66
|
+
|
67
|
+
Add more members to your leaderboard:
|
68
|
+
|
69
|
+
ruby-1.8.7-p302 > 50.upto(95) do |index|
|
70
|
+
ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
|
71
|
+
ruby-1.8.7-p302 ?> end
|
72
|
+
=> 50
|
73
|
+
ruby-1.8.7-p302 > highscore_lb.total_pages
|
74
|
+
=> 3
|
75
|
+
|
76
|
+
Get an "Around Me" leaderboard for a member:
|
77
|
+
|
78
|
+
ruby-1.8.7-p302 > highscore_lb.around_me('member_53')
|
79
|
+
=> [{:member=>"member_65", :rank=>31, :score=>"65"}, {:member=>"member_64", :rank=>32, :score=>"64"}, {:member=>"member_63", :rank=>33, :score=>"63"}, {:member=>"member_62", :rank=>34, :score=>"62"}, {:member=>"member_61", :rank=>35, :score=>"61"}, {:member=>"member_60", :rank=>36, :score=>"60"}, {:member=>"member_59", :rank=>37, :score=>"59"}, {:member=>"member_58", :rank=>38, :score=>"58"}, {:member=>"member_57", :rank=>39, :score=>"57"}, {:member=>"member_56", :rank=>40, :score=>"56"}, {:member=>"member_55", :rank=>41, :score=>"55"}, {:member=>"member_54", :rank=>42, :score=>"54"}, {:member=>"member_53", :rank=>43, :score=>"53"}, {:member=>"member_52", :rank=>44, :score=>"52"}, {:member=>"member_51", :rank=>45, :score=>"51"}, {:member=>"member_50", :rank=>46, :score=>"50"}, {:member=>"member_10", :rank=>47, :score=>"10"}, {:member=>"member_9", :rank=>48, :score=>"9"}, {:member=>"member_8", :rank=>49, :score=>"8"}, {:member=>"member_7", :rank=>50, :score=>"7"}, {:member=>"member_6", :rank=>51, :score=>"6"}, {:member=>"member_5", :rank=>52, :score=>"5"}, {:member=>"member_4", :rank=>53, :score=>"4"}, {:member=>"member_3", :rank=>54, :score=>"3"}, {:member=>"member_2", :rank=>55, :score=>"2"}]
|
80
|
+
|
81
|
+
Get rank and score for an arbitrary list of members (e.g. friends):
|
82
|
+
|
83
|
+
ruby-1.8.7-p302 > highscore_lb.ranked_in_list(['member_1', 'member_62', 'member_67'], true)
|
84
|
+
=> [{:rank=>55, :member=>"member_1", :score=>1.0}, {:rank=>33, :member=>"member_62", :score=>62.0}, {:rank=>28, :member=>"member_67", :score=>67.0}]
|
85
|
+
|
86
|
+
=== Other useful methods
|
87
|
+
|
88
|
+
remove_member(member): Remove a member from the leaderboard
|
89
|
+
total_members_in_score_range(min_score, max_score): Count the number of members within a score range in the leaderboard
|
90
|
+
change_score_for(member, delta): Change the score for a member by some amount delta (delta could be positive or negative)
|
91
|
+
check_member?(member): Check to see whether member is in the leaderboard
|
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
|
+
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
|
96
|
+
|
97
|
+
== Performance Metrics
|
98
|
+
|
99
|
+
10 million sequential scores insert:
|
100
|
+
|
101
|
+
ruby-1.8.7-p302 > insert_time = Benchmark.measure do
|
102
|
+
ruby-1.8.7-p302 > 1.upto(10000000) do |index|
|
103
|
+
ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
|
104
|
+
ruby-1.8.7-p302 ?> end
|
105
|
+
ruby-1.8.7-p302 ?> end
|
106
|
+
=> #<Benchmark::Tms:0x101605660 @label="", @stime=173.61, @total=577.52, @real=911.718175172806, @utime=403.91, @cstime=0.0, @cutime=0.0>
|
107
|
+
|
108
|
+
Average time to request an arbitrary page from the leaderboard:
|
109
|
+
|
110
|
+
ruby-1.8.7-p302 > requests_to_make = 50000
|
111
|
+
=> 50000
|
112
|
+
ruby-1.8.7-p302 > lb_request_time = 0
|
113
|
+
=> 0
|
114
|
+
ruby-1.8.7-p302 > 1.upto(requests_to_make) do
|
115
|
+
ruby-1.8.7-p302 > lb_request_time += Benchmark.measure do
|
116
|
+
ruby-1.8.7-p302 > highscore_lb.leaders(rand(highscore_lb.total_pages))
|
117
|
+
ruby-1.8.7-p302 ?> end.total
|
118
|
+
ruby-1.8.7-p302 ?> end
|
119
|
+
=> 1
|
120
|
+
ruby-1.8.7-p302 >
|
121
|
+
ruby-1.8.7-p302 > p lb_request_time / requests_to_make
|
122
|
+
0.001808
|
123
|
+
=> nil
|
124
|
+
|
125
|
+
10 million random scores insert:
|
126
|
+
|
127
|
+
ruby-1.8.7-p302 > insert_time = Benchmark.measure do
|
128
|
+
ruby-1.8.7-p302 > 1.upto(10000000) do |index|
|
129
|
+
ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", rand(50000000))
|
130
|
+
ruby-1.8.7-p302 ?> end
|
131
|
+
ruby-1.8.7-p302 ?> end
|
132
|
+
=> #<Benchmark::Tms:0x10164ebf8 @label="", @stime=172.94, @total=577.91, @real=1356.57155895233, @utime=404.97, @cstime=0.0, @cutime=0.0>
|
133
|
+
|
134
|
+
Average time to request an arbitrary page from the leaderboard:
|
135
|
+
|
136
|
+
ruby-1.8.7-p302 > requests_to_make = 50000
|
137
|
+
=> 50000
|
138
|
+
ruby-1.8.7-p302 > lb_request_time = 0
|
139
|
+
=> 0
|
140
|
+
ruby-1.8.7-p302 > 1.upto(requests_to_make) do
|
141
|
+
ruby-1.8.7-p302 > lb_request_time += Benchmark.measure do
|
142
|
+
ruby-1.8.7-p302 > highscore_lb.leaders(rand(highscore_lb.total_pages))
|
143
|
+
ruby-1.8.7-p302 ?> end.total
|
144
|
+
ruby-1.8.7-p302 ?> end
|
145
|
+
=> 1
|
146
|
+
ruby-1.8.7-p302 >
|
147
|
+
ruby-1.8.7-p302 > p lb_request_time / requests_to_make
|
148
|
+
0.00179680000000001
|
149
|
+
=> nil
|
150
|
+
|
151
|
+
== Future Ideas
|
152
|
+
|
153
|
+
* Bulk insert
|
154
|
+
* Atomicity for various operations?
|
155
|
+
* Is nil? OK to return if Redis returns no data or should it be []?
|
156
|
+
|
157
|
+
== Contributing to leaderboard
|
158
|
+
|
159
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
160
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
161
|
+
* Fork the project
|
162
|
+
* Start a feature/bugfix branch
|
163
|
+
* Commit and push until you are happy with your contribution
|
164
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
165
|
+
* 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.
|
166
|
+
|
167
|
+
== Copyright
|
168
|
+
|
169
|
+
Copyright (c) 2011 David Czarnecki. See LICENSE.txt for further details.
|
170
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,89 @@
|
|
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 '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 = "multi-leaderboard"
|
16
|
+
gem.homepage = "http://github.com/willcosgrove/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 = "will@willcosgrove.com"
|
21
|
+
gem.authors = ["David Czarnecki", "Will Cosgrove"]
|
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
|
28
|
+
|
29
|
+
require 'rake/testtask'
|
30
|
+
Rake::TestTask.new(:test) do |test|
|
31
|
+
test.libs << 'lib' << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
|
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 'rake/rdoctask'
|
44
|
+
Rake::RDocTask.new do |rdoc|
|
45
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
46
|
+
|
47
|
+
rdoc.rdoc_dir = 'rdoc'
|
48
|
+
rdoc.title = "multi-leaderboard #{version}"
|
49
|
+
rdoc.rdoc_files.include('README*')
|
50
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
51
|
+
end
|
52
|
+
|
53
|
+
REDIS_DIR = File.expand_path(File.join("..", "test"), __FILE__)
|
54
|
+
REDIS_CNF = File.join(REDIS_DIR, "test.conf")
|
55
|
+
REDIS_PID = File.join(REDIS_DIR, "db", "redis.pid")
|
56
|
+
REDIS_LOCATION = ENV['REDIS_LOCATION']
|
57
|
+
|
58
|
+
task :default => :run
|
59
|
+
|
60
|
+
desc "Run tests and manage server start/stop"
|
61
|
+
task :run => [:start, :test, :stop]
|
62
|
+
|
63
|
+
desc "Run rcov and manage server start/stop"
|
64
|
+
task :rcoverage => [:start, :rcov, :stop]
|
65
|
+
|
66
|
+
desc "Start the Redis server"
|
67
|
+
task :start do
|
68
|
+
redis_running = \
|
69
|
+
begin
|
70
|
+
File.exists?(REDIS_PID) && Process.kill(0, File.read(REDIS_PID).to_i)
|
71
|
+
rescue Errno::ESRCH
|
72
|
+
FileUtils.rm REDIS_PID
|
73
|
+
false
|
74
|
+
end
|
75
|
+
|
76
|
+
if REDIS_LOCATION
|
77
|
+
system "#{REDIS_LOCATION}/redis-server #{REDIS_CNF}" unless redis_running
|
78
|
+
else
|
79
|
+
system "redis-server #{REDIS_CNF}" unless redis_running
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
desc "Stop the Redis server"
|
84
|
+
task :stop do
|
85
|
+
if File.exists?(REDIS_PID)
|
86
|
+
Process.kill "INT", File.read(REDIS_PID).to_i
|
87
|
+
FileUtils.rm REDIS_PID
|
88
|
+
end
|
89
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.4
|
data/leaderboard.gemspec
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{multi-leaderboard}
|
8
|
+
s.version = "1.0.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["David Czarnecki", "Will Cosgrove"]
|
12
|
+
s.date = %q{2011-05-02}
|
13
|
+
s.description = %q{Leaderboards backed by Redis in Ruby}
|
14
|
+
s.email = %q{will@willcosgrove.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 = %q{http://github.com/willcosgrove/leaderboard}
|
36
|
+
s.licenses = ["MIT"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.3.7}
|
39
|
+
s.summary = %q{Leaderboards backed by Redis in Ruby}
|
40
|
+
s.test_files = [
|
41
|
+
"test/helper.rb",
|
42
|
+
"test/test_leaderboard.rb"
|
43
|
+
]
|
44
|
+
|
45
|
+
if s.respond_to? :specification_version then
|
46
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
47
|
+
s.specification_version = 3
|
48
|
+
|
49
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
50
|
+
s.add_runtime_dependency(%q<redis>, ["~> 2.1.1"])
|
51
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
52
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
53
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<redis>, ["~> 2.1.1"])
|
56
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
57
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
58
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
59
|
+
end
|
60
|
+
else
|
61
|
+
s.add_dependency(%q<redis>, ["~> 2.1.1"])
|
62
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
63
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
64
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
data/lib/leaderboard.rb
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'redis'
|
2
|
+
|
3
|
+
class Leaderboard
|
4
|
+
VERSION = '1.0.4'.freeze
|
5
|
+
|
6
|
+
DEFAULT_PAGE_SIZE = 25
|
7
|
+
DEFAULT_REDIS_HOST = 'localhost'
|
8
|
+
DEFAULT_REDIS_PORT = 6379
|
9
|
+
|
10
|
+
attr_reader :host
|
11
|
+
attr_reader :port
|
12
|
+
attr_reader :leaderboard_name
|
13
|
+
attr_accessor :page_size
|
14
|
+
|
15
|
+
def initialize(leaderboard_name, host = DEFAULT_REDIS_HOST, port = DEFAULT_REDIS_PORT, page_size = DEFAULT_PAGE_SIZE, redis_options = {})
|
16
|
+
@leaderboard_name = leaderboard_name
|
17
|
+
@host = host
|
18
|
+
@port = port
|
19
|
+
|
20
|
+
if page_size < 1
|
21
|
+
page_size = DEFAULT_PAGE_SIZE
|
22
|
+
end
|
23
|
+
|
24
|
+
@page_size = page_size
|
25
|
+
|
26
|
+
redis_options = redis_options.dup
|
27
|
+
redis_options[:host] ||= @host
|
28
|
+
redis_options[:port] ||= @port
|
29
|
+
|
30
|
+
@redis_options = redis_options
|
31
|
+
|
32
|
+
@@redis_connection ||= Redis.new(@redis_options)
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_member(member, score)
|
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)
|
41
|
+
end
|
42
|
+
|
43
|
+
def remove_member(member)
|
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)
|
49
|
+
end
|
50
|
+
|
51
|
+
def total_members
|
52
|
+
total_members_in(@leaderboard_name)
|
53
|
+
end
|
54
|
+
|
55
|
+
def total_members_in(leaderboard_name)
|
56
|
+
@@redis_connection.zcard(leaderboard_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
def total_pages
|
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
|
65
|
+
end
|
66
|
+
|
67
|
+
def total_members_in_score_range(min_score, max_score)
|
68
|
+
total_members_in_score_range_in(@leaderboard_name, min_score, max_score)
|
69
|
+
end
|
70
|
+
|
71
|
+
def total_members_in_score_range_in(leaderboard_name, min_score, max_score)
|
72
|
+
@@redis_connection.zcount(leaderboard_name, min_score, max_score)
|
73
|
+
end
|
74
|
+
|
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)
|
88
|
+
if use_zero_index_for_rank
|
89
|
+
return @@redis_connection.zrevrank(leaderboard_name, member)
|
90
|
+
else
|
91
|
+
return @@redis_connection.zrevrank(leaderboard_name, member) + 1 rescue nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def score_for(member)
|
96
|
+
score_for_in(@leaderboard_name, member)
|
97
|
+
end
|
98
|
+
|
99
|
+
def score_for_in(leaderboard_name, member)
|
100
|
+
@@redis_connection.zscore(leaderboard_name, member).to_f
|
101
|
+
end
|
102
|
+
|
103
|
+
def check_member?(member)
|
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?
|
109
|
+
end
|
110
|
+
|
111
|
+
def score_and_rank_for(member, use_zero_index_for_rank = false)
|
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)}
|
117
|
+
end
|
118
|
+
|
119
|
+
def remove_members_in_score_range(min_score, max_score)
|
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)
|
125
|
+
end
|
126
|
+
|
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)
|
132
|
+
if current_page < 1
|
133
|
+
current_page = 1
|
134
|
+
end
|
135
|
+
|
136
|
+
if current_page > total_pages
|
137
|
+
current_page = total_pages
|
138
|
+
end
|
139
|
+
|
140
|
+
index_for_redis = current_page - 1
|
141
|
+
|
142
|
+
starting_offset = (index_for_redis * @page_size)
|
143
|
+
if starting_offset < 0
|
144
|
+
starting_offset = 0
|
145
|
+
end
|
146
|
+
|
147
|
+
ending_offset = (starting_offset + @page_size) - 1
|
148
|
+
|
149
|
+
raw_leader_data = @@redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
|
150
|
+
if raw_leader_data
|
151
|
+
massage_leader_data(leaderboard_name, raw_leader_data, with_rank, use_zero_index_for_rank)
|
152
|
+
else
|
153
|
+
return nil
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def around_me(member, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
|
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)
|
163
|
+
|
164
|
+
starting_offset = reverse_rank_for_member - (@page_size / 2)
|
165
|
+
if starting_offset < 0
|
166
|
+
starting_offset = 0
|
167
|
+
end
|
168
|
+
|
169
|
+
ending_offset = (starting_offset + @page_size) - 1
|
170
|
+
|
171
|
+
raw_leader_data = @@redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
|
172
|
+
if raw_leader_data
|
173
|
+
massage_leader_data(leaderboard_name, raw_leader_data, with_rank, use_zero_index_for_rank)
|
174
|
+
else
|
175
|
+
return nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
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)
|
184
|
+
ranks_for_members = []
|
185
|
+
|
186
|
+
members.each do |member|
|
187
|
+
data = {}
|
188
|
+
data[:member] = member
|
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
|
191
|
+
|
192
|
+
ranks_for_members << data
|
193
|
+
end
|
194
|
+
|
195
|
+
ranks_for_members
|
196
|
+
end
|
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
|
+
|
208
|
+
# Disconnect from the redis server
|
209
|
+
def disconnect
|
210
|
+
@@redis_connection.client.disconnect
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def massage_leader_data(leaderboard_name, leaders, with_rank, use_zero_index_for_rank)
|
216
|
+
member_attribute = true
|
217
|
+
leader_data = []
|
218
|
+
|
219
|
+
data = {}
|
220
|
+
leaders.each do |leader_data_item|
|
221
|
+
if member_attribute
|
222
|
+
data[:member] = leader_data_item
|
223
|
+
else
|
224
|
+
data[:score] = leader_data_item.to_f
|
225
|
+
data[:rank] = rank_for_in(leaderboard_name, data[:member], use_zero_index_for_rank) if with_rank
|
226
|
+
leader_data << data
|
227
|
+
data = {}
|
228
|
+
end
|
229
|
+
|
230
|
+
member_attribute = !member_attribute
|
231
|
+
end
|
232
|
+
|
233
|
+
leader_data
|
234
|
+
end
|
235
|
+
end
|
data/test/db/.gitkeep
ADDED
File without changes
|
data/test/helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
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
|
data/test/test.conf
ADDED
@@ -0,0 +1,259 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestLeaderboard < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@leaderboard = Leaderboard.new('name')
|
6
|
+
@redis_connection = Redis.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
@redis_connection.flushdb
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_version
|
14
|
+
assert_equal '1.0.2', Leaderboard::VERSION
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_initialize_with_defaults
|
18
|
+
assert_equal 'name', @leaderboard.leaderboard_name
|
19
|
+
assert_equal 'localhost', @leaderboard.host
|
20
|
+
assert_equal 6379, @leaderboard.port
|
21
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE, @leaderboard.page_size
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_page_size_is_default_page_size_if_set_to_invalid_value
|
25
|
+
@leaderboard = Leaderboard.new('name', 'localhost', 6379, 0)
|
26
|
+
|
27
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE, @leaderboard.page_size
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_add_member_and_total_members
|
31
|
+
@leaderboard.add_member('member', 1)
|
32
|
+
|
33
|
+
assert_equal 1, @leaderboard.total_members
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_total_members_in_score_range
|
37
|
+
add_members_to_leaderboard(5)
|
38
|
+
|
39
|
+
assert_equal 3, @leaderboard.total_members_in_score_range(2, 4)
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_rank_for
|
43
|
+
add_members_to_leaderboard(5)
|
44
|
+
|
45
|
+
assert_equal 2, @leaderboard.rank_for('member_4')
|
46
|
+
assert_equal 1, @leaderboard.rank_for('member_4', true)
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_score_for
|
50
|
+
add_members_to_leaderboard(5)
|
51
|
+
|
52
|
+
assert_equal 4, @leaderboard.score_for('member_4')
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_total_pages
|
56
|
+
add_members_to_leaderboard(10)
|
57
|
+
|
58
|
+
assert_equal 1, @leaderboard.total_pages
|
59
|
+
|
60
|
+
@redis_connection.flushdb
|
61
|
+
|
62
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE + 1)
|
63
|
+
|
64
|
+
assert_equal 2, @leaderboard.total_pages
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_leaders
|
68
|
+
add_members_to_leaderboard(25)
|
69
|
+
|
70
|
+
assert_equal 25, @leaderboard.total_members
|
71
|
+
|
72
|
+
leaders = @leaderboard.leaders(1)
|
73
|
+
|
74
|
+
assert_equal 25, leaders.size
|
75
|
+
assert_equal 'member_25', leaders[0][:member]
|
76
|
+
assert_equal 'member_2', leaders[-2][:member]
|
77
|
+
assert_equal 'member_1', leaders[-1][:member]
|
78
|
+
assert_equal 1, leaders[-1][:score].to_i
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_leaders_with_multiple_pages
|
82
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)
|
83
|
+
|
84
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1, @leaderboard.total_members
|
85
|
+
|
86
|
+
leaders = @leaderboard.leaders(1)
|
87
|
+
assert_equal @leaderboard.page_size, leaders.size
|
88
|
+
|
89
|
+
leaders = @leaderboard.leaders(2)
|
90
|
+
assert_equal @leaderboard.page_size, leaders.size
|
91
|
+
|
92
|
+
leaders = @leaderboard.leaders(3)
|
93
|
+
assert_equal @leaderboard.page_size, leaders.size
|
94
|
+
|
95
|
+
leaders = @leaderboard.leaders(4)
|
96
|
+
assert_equal 1, leaders.size
|
97
|
+
|
98
|
+
leaders = @leaderboard.leaders(-5)
|
99
|
+
assert_equal @leaderboard.page_size, leaders.size
|
100
|
+
|
101
|
+
leaders = @leaderboard.leaders(10)
|
102
|
+
assert_equal 1, leaders.size
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_around_me
|
106
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1)
|
107
|
+
|
108
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE * 3 + 1, @leaderboard.total_members
|
109
|
+
|
110
|
+
leaders_around_me = @leaderboard.around_me('member_30')
|
111
|
+
assert_equal @leaderboard.page_size / 2, leaders_around_me.size / 2
|
112
|
+
|
113
|
+
leaders_around_me = @leaderboard.around_me('member_1')
|
114
|
+
assert_equal @leaderboard.page_size / 2 + 1, leaders_around_me.size
|
115
|
+
|
116
|
+
leaders_around_me = @leaderboard.around_me('member_76')
|
117
|
+
assert_equal @leaderboard.page_size / 2, leaders_around_me.size / 2
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_ranked_in_list
|
121
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)
|
122
|
+
|
123
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE, @leaderboard.total_members
|
124
|
+
|
125
|
+
members = ['member_1', 'member_5', 'member_10']
|
126
|
+
ranked_members = @leaderboard.ranked_in_list(members, true)
|
127
|
+
|
128
|
+
assert_equal 3, ranked_members.size
|
129
|
+
|
130
|
+
assert_equal 25, ranked_members[0][:rank]
|
131
|
+
assert_equal 1, ranked_members[0][:score]
|
132
|
+
|
133
|
+
assert_equal 21, ranked_members[1][:rank]
|
134
|
+
assert_equal 5, ranked_members[1][:score]
|
135
|
+
|
136
|
+
assert_equal 16, ranked_members[2][:rank]
|
137
|
+
assert_equal 10, ranked_members[2][:score]
|
138
|
+
end
|
139
|
+
|
140
|
+
def test_remove_member
|
141
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)
|
142
|
+
|
143
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE, @leaderboard.total_members
|
144
|
+
|
145
|
+
@leaderboard.remove_member('member_1')
|
146
|
+
|
147
|
+
assert_equal Leaderboard::DEFAULT_PAGE_SIZE - 1, @leaderboard.total_members
|
148
|
+
assert_nil @leaderboard.rank_for('member_1')
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_change_score_for
|
152
|
+
@leaderboard.add_member('member_1', 5)
|
153
|
+
assert_equal 5, @leaderboard.score_for('member_1')
|
154
|
+
|
155
|
+
@leaderboard.change_score_for('member_1', 5)
|
156
|
+
assert_equal 10, @leaderboard.score_for('member_1')
|
157
|
+
|
158
|
+
@leaderboard.change_score_for('member_1', -5)
|
159
|
+
assert_equal 5, @leaderboard.score_for('member_1')
|
160
|
+
end
|
161
|
+
|
162
|
+
def test_check_member
|
163
|
+
@leaderboard.add_member('member_1', 10)
|
164
|
+
|
165
|
+
assert_equal true, @leaderboard.check_member?('member_1')
|
166
|
+
assert_equal false, @leaderboard.check_member?('member_2')
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_can_change_page_size_and_have_it_reflected_in_size_of_result_set
|
170
|
+
add_members_to_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)
|
171
|
+
|
172
|
+
@leaderboard.page_size = 5
|
173
|
+
assert_equal 5, @leaderboard.total_pages
|
174
|
+
assert_equal 5, @leaderboard.leaders(1).size
|
175
|
+
end
|
176
|
+
|
177
|
+
def test_score_and_rank_for
|
178
|
+
add_members_to_leaderboard
|
179
|
+
|
180
|
+
data = @leaderboard.score_and_rank_for('member_1')
|
181
|
+
assert_equal 'member_1', data[:member]
|
182
|
+
assert_equal 1, data[:score]
|
183
|
+
assert_equal 5, data[:rank]
|
184
|
+
end
|
185
|
+
|
186
|
+
def test_remove_members_in_score_range
|
187
|
+
add_members_to_leaderboard
|
188
|
+
|
189
|
+
assert_equal 5, @leaderboard.total_members
|
190
|
+
|
191
|
+
@leaderboard.add_member('cheater_1', 100)
|
192
|
+
@leaderboard.add_member('cheater_2', 101)
|
193
|
+
@leaderboard.add_member('cheater_3', 102)
|
194
|
+
|
195
|
+
assert_equal 8, @leaderboard.total_members
|
196
|
+
|
197
|
+
@leaderboard.remove_members_in_score_range(100, 102)
|
198
|
+
|
199
|
+
assert_equal 5, @leaderboard.total_members
|
200
|
+
|
201
|
+
leaders = @leaderboard.leaders(1)
|
202
|
+
leaders.each do |leader|
|
203
|
+
assert leader[:score] < 100
|
204
|
+
end
|
205
|
+
end
|
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
|
+
|
252
|
+
private
|
253
|
+
|
254
|
+
def add_members_to_leaderboard(members_to_add = 5)
|
255
|
+
1.upto(members_to_add) do |index|
|
256
|
+
@leaderboard.add_member("member_#{index}", index)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: multi-leaderboard
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 31
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 4
|
10
|
+
version: 1.0.4
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- David Czarnecki
|
14
|
+
- Will Cosgrove
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-05-02 00:00:00 -05:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: redis
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 9
|
31
|
+
segments:
|
32
|
+
- 2
|
33
|
+
- 1
|
34
|
+
- 1
|
35
|
+
version: 2.1.1
|
36
|
+
type: :runtime
|
37
|
+
version_requirements: *id001
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: bundler
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 23
|
47
|
+
segments:
|
48
|
+
- 1
|
49
|
+
- 0
|
50
|
+
- 0
|
51
|
+
version: 1.0.0
|
52
|
+
type: :development
|
53
|
+
version_requirements: *id002
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: jeweler
|
56
|
+
prerelease: false
|
57
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ~>
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 7
|
63
|
+
segments:
|
64
|
+
- 1
|
65
|
+
- 5
|
66
|
+
- 2
|
67
|
+
version: 1.5.2
|
68
|
+
type: :development
|
69
|
+
version_requirements: *id003
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rcov
|
72
|
+
prerelease: false
|
73
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
hash: 3
|
79
|
+
segments:
|
80
|
+
- 0
|
81
|
+
version: "0"
|
82
|
+
type: :development
|
83
|
+
version_requirements: *id004
|
84
|
+
description: Leaderboards backed by Redis in Ruby
|
85
|
+
email: will@willcosgrove.com
|
86
|
+
executables: []
|
87
|
+
|
88
|
+
extensions: []
|
89
|
+
|
90
|
+
extra_rdoc_files:
|
91
|
+
- LICENSE.txt
|
92
|
+
- README.rdoc
|
93
|
+
files:
|
94
|
+
- .document
|
95
|
+
- .rvmrc
|
96
|
+
- CHANGELOG.markdown
|
97
|
+
- Gemfile
|
98
|
+
- LICENSE.txt
|
99
|
+
- README.rdoc
|
100
|
+
- Rakefile
|
101
|
+
- VERSION
|
102
|
+
- leaderboard.gemspec
|
103
|
+
- lib/leaderboard.rb
|
104
|
+
- test/db/.gitkeep
|
105
|
+
- test/helper.rb
|
106
|
+
- test/test.conf
|
107
|
+
- test/test_leaderboard.rb
|
108
|
+
has_rdoc: true
|
109
|
+
homepage: http://github.com/willcosgrove/leaderboard
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
requirements: []
|
136
|
+
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.5.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: Leaderboards backed by Redis in Ruby
|
142
|
+
test_files:
|
143
|
+
- test/helper.rb
|
144
|
+
- test/test_leaderboard.rb
|