leaderboard 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm 1.8.7
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'
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,143 @@
1
+ = leaderboard
2
+
3
+ Leaderboards backed by Redis, 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
+ == Usage
17
+
18
+ Create a new leaderboard or attach to an existing leaderboard named 'highscores':
19
+
20
+ ruby-1.8.7-p302 > highscore_lb = Leaderboard.new('highscores')
21
+ => #<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">
22
+
23
+ Add members to your leaderboard:
24
+
25
+ ruby-1.8.7-p302 > 1.upto(10) do |index|
26
+ ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
27
+ ruby-1.8.7-p302 ?> end
28
+ => 1
29
+
30
+ Get some information about your leaderboard:
31
+
32
+ ruby-1.8.7-p302 > highscore_lb.total_members
33
+ => 10
34
+ ruby-1.8.7-p302 > highscore_lb.total_pages
35
+ => 1
36
+
37
+ Get some information about a specific member(s) in the leaderboard:
38
+
39
+ ruby-1.8.7-p302 > highscore_lb.score_for('member_4')
40
+ => 4.0
41
+ ruby-1.8.7-p302 > highscore_lb.rank_for('member_4')
42
+ => 7
43
+ ruby-1.8.7-p302 > highscore_lb.rank_for('member_10')
44
+ => 1
45
+
46
+ Get page 1 in the leaderboard:
47
+
48
+ ruby-1.8.7-p302 > highscore_lb.leaders(1)
49
+ => [{: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"}]
50
+
51
+ Add more members to your leaderboard:
52
+
53
+ ruby-1.8.7-p302 > 50.upto(95) do |index|
54
+ ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
55
+ ruby-1.8.7-p302 ?> end
56
+ => 50
57
+ ruby-1.8.7-p302 > highscore_lb.total_pages
58
+ => 3
59
+
60
+ Get an "Around Me" leaderboard for a member:
61
+
62
+ ruby-1.8.7-p302 > highscore_lb.around_me('member_53')
63
+ => [{: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"}]
64
+
65
+ Get rank and score for an arbitrary list of members (e.g. friends):
66
+
67
+ ruby-1.8.7-p302 > highscore_lb.ranked_in_list(['member_1', 'member_62', 'member_67'], true)
68
+ => [{:rank=>55, :member=>"member_1", :score=>1.0}, {:rank=>33, :member=>"member_62", :score=>62.0}, {:rank=>28, :member=>"member_67", :score=>67.0}]
69
+
70
+ == Performance Metrics
71
+
72
+ 10 million sequential scores insert:
73
+
74
+ ruby-1.8.7-p302 > insert_time = Benchmark.measure do
75
+ ruby-1.8.7-p302 > 1.upto(10000000) do |index|
76
+ ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", index)
77
+ ruby-1.8.7-p302 ?> end
78
+ ruby-1.8.7-p302 ?> end
79
+ => #<Benchmark::Tms:0x101605660 @label="", @stime=173.61, @total=577.52, @real=911.718175172806, @utime=403.91, @cstime=0.0, @cutime=0.0>
80
+
81
+ Average time to request an arbitrary page from the leaderboard:
82
+
83
+ ruby-1.8.7-p302 > requests_to_make = 50000
84
+ => 50000
85
+ ruby-1.8.7-p302 > lb_request_time = 0
86
+ => 0
87
+ ruby-1.8.7-p302 > 1.upto(requests_to_make) do
88
+ ruby-1.8.7-p302 > lb_request_time += Benchmark.measure do
89
+ ruby-1.8.7-p302 > highscore_lb.leaders(rand(highscore_lb.total_pages))
90
+ ruby-1.8.7-p302 ?> end.total
91
+ ruby-1.8.7-p302 ?> end
92
+ => 1
93
+ ruby-1.8.7-p302 >
94
+ ruby-1.8.7-p302 > p lb_request_time / requests_to_make
95
+ 0.001808
96
+ => nil
97
+
98
+ 10 million random scores insert:
99
+
100
+ ruby-1.8.7-p302 > insert_time = Benchmark.measure do
101
+ ruby-1.8.7-p302 > 1.upto(10000000) do |index|
102
+ ruby-1.8.7-p302 > highscore_lb.add_member("member_#{index}", rand(50000000))
103
+ ruby-1.8.7-p302 ?> end
104
+ ruby-1.8.7-p302 ?> end
105
+ => #<Benchmark::Tms:0x10164ebf8 @label="", @stime=172.94, @total=577.91, @real=1356.57155895233, @utime=404.97, @cstime=0.0, @cutime=0.0>
106
+
107
+ Average time to request an arbitrary page from the leaderboard:
108
+
109
+ ruby-1.8.7-p302 > requests_to_make = 50000
110
+ => 50000
111
+ ruby-1.8.7-p302 > lb_request_time = 0
112
+ => 0
113
+ ruby-1.8.7-p302 > 1.upto(requests_to_make) do
114
+ ruby-1.8.7-p302 > lb_request_time += Benchmark.measure do
115
+ ruby-1.8.7-p302 > highscore_lb.leaders(rand(highscore_lb.total_pages))
116
+ ruby-1.8.7-p302 ?> end.total
117
+ ruby-1.8.7-p302 ?> end
118
+ => 1
119
+ ruby-1.8.7-p302 >
120
+ ruby-1.8.7-p302 > p lb_request_time / requests_to_make
121
+ 0.00179680000000001
122
+ => nil
123
+
124
+ == Future Ideas
125
+
126
+ * Bulk insert
127
+ * Is nil? OK to return if Redis returns no data or should it be []?
128
+
129
+ == Contributing to leaderboard
130
+
131
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
132
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
133
+ * Fork the project
134
+ * Start a feature/bugfix branch
135
+ * Commit and push until you are happy with your contribution
136
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
137
+ * 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.
138
+
139
+ == Copyright
140
+
141
+ Copyright (c) 2011 David Czarnecki. See LICENSE.txt for
142
+ further details.
143
+
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 = "leaderboard"
16
+ gem.homepage = "http://github.com/agoragames/leaderboard"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Leaderboards backed by Redis}
19
+ gem.description = %Q{Leaderboards backed by Redis}
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
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 = "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.0
@@ -0,0 +1,66 @@
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{leaderboard}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["David Czarnecki"]
12
+ s.date = %q{2011-01-05}
13
+ s.description = %q{Leaderboards backed by Redis}
14
+ s.email = %q{dczarnecki@agoragames.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rvmrc",
22
+ "Gemfile",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "leaderboard.gemspec",
28
+ "lib/leaderboard.rb",
29
+ "test/db/.gitkeep",
30
+ "test/helper.rb",
31
+ "test/test.conf",
32
+ "test/test_leaderboard.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/agoragames/leaderboard}
35
+ s.licenses = ["MIT"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.7}
38
+ s.summary = %q{Leaderboards backed by Redis}
39
+ s.test_files = [
40
+ "test/helper.rb",
41
+ "test/test_leaderboard.rb"
42
+ ]
43
+
44
+ if s.respond_to? :specification_version then
45
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_runtime_dependency(%q<redis>, [">= 0"])
50
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
51
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
52
+ s.add_development_dependency(%q<rcov>, [">= 0"])
53
+ else
54
+ s.add_dependency(%q<redis>, [">= 0"])
55
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
56
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
57
+ s.add_dependency(%q<rcov>, [">= 0"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<redis>, [">= 0"])
61
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
62
+ s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
63
+ s.add_dependency(%q<rcov>, [">= 0"])
64
+ end
65
+ end
66
+
@@ -0,0 +1,143 @@
1
+ require 'redis'
2
+
3
+ class Leaderboard
4
+ VERSION = '1.0.0'.freeze
5
+ DEFAULT_PAGE_SIZE = 25
6
+
7
+ attr_reader :host
8
+ attr_reader :port
9
+ attr_reader :leaderboard_name
10
+ attr_reader :page_size
11
+
12
+ def initialize(leaderboard_name, host = 'localhost', port = 6379, page_size = DEFAULT_PAGE_SIZE)
13
+ @leaderboard_name = leaderboard_name
14
+ @host = host
15
+ @port = port
16
+
17
+ if page_size < 1
18
+ page_size = DEFAULT_PAGE_SIZE
19
+ end
20
+
21
+ @page_size = page_size
22
+
23
+ @redis_connection = Redis.new(:host => @host, :port => @port)
24
+ end
25
+
26
+ def add_member(member, score)
27
+ @redis_connection.zadd(@leaderboard_name, score, member)
28
+ end
29
+
30
+ def remove_member(member)
31
+ @redis_connection.zrem(@leaderboard_name, member)
32
+ end
33
+
34
+ def total_members
35
+ @redis_connection.zcard(@leaderboard_name)
36
+ end
37
+
38
+ def total_pages
39
+ (total_members / @page_size.to_f).ceil
40
+ end
41
+
42
+ def total_members_in_score_range(min_score, max_score)
43
+ @redis_connection.zcount(@leaderboard_name, min_score, max_score)
44
+ end
45
+
46
+ def change_score_for(member, delta)
47
+ @redis_connection.zincrby(@leaderboard_name, delta, member)
48
+ end
49
+
50
+ def rank_for(member, use_zero_index_for_rank = false)
51
+ if use_zero_index_for_rank
52
+ return @redis_connection.zrevrank(@leaderboard_name, member)
53
+ else
54
+ return @redis_connection.zrevrank(@leaderboard_name, member) + 1 rescue nil
55
+ end
56
+ end
57
+
58
+ def score_for(member)
59
+ @redis_connection.zscore(@leaderboard_name, member).to_f
60
+ end
61
+
62
+ def leaders(current_page, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
63
+ if current_page < 1
64
+ current_page = 1
65
+ end
66
+
67
+ if current_page > total_pages
68
+ current_page = total_pages
69
+ end
70
+
71
+ index_for_redis = current_page - 1
72
+
73
+ starting_offset = (index_for_redis * @page_size)
74
+ if starting_offset < 0
75
+ starting_offset = 0
76
+ end
77
+
78
+ ending_offset = (starting_offset + @page_size) - 1
79
+
80
+ raw_leader_data = @redis_connection.zrevrange(@leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
81
+ if raw_leader_data
82
+ massage_leader_data(raw_leader_data, with_rank, use_zero_index_for_rank)
83
+ else
84
+ return nil
85
+ end
86
+ end
87
+
88
+ def around_me(member, with_scores = true, with_rank = true, use_zero_index_for_rank = false)
89
+ reverse_rank_for_member = @redis_connection.zrevrank(@leaderboard_name, member)
90
+
91
+ starting_offset = reverse_rank_for_member - (@page_size / 2)
92
+ if starting_offset < 0
93
+ starting_offset = 0
94
+ end
95
+
96
+ ending_offset = (starting_offset + @page_size) - 1
97
+
98
+ raw_leader_data = @redis_connection.zrevrange(@leaderboard_name, starting_offset, ending_offset, :with_scores => with_scores)
99
+ if raw_leader_data
100
+ massage_leader_data(raw_leader_data, with_rank, use_zero_index_for_rank)
101
+ else
102
+ return nil
103
+ end
104
+ end
105
+
106
+ def ranked_in_list(members, with_scores = true, use_zero_index_for_rank = false)
107
+ ranks_for_members = []
108
+
109
+ members.each do |member|
110
+ data = {}
111
+ data[:member] = member
112
+ data[:rank] = rank_for(member, use_zero_index_for_rank)
113
+ data[:score] = score_for(member) if with_scores
114
+
115
+ ranks_for_members << data
116
+ end
117
+
118
+ ranks_for_members
119
+ end
120
+
121
+ private
122
+
123
+ def massage_leader_data(leaders, with_rank, use_zero_index_for_rank)
124
+ member_attribute = true
125
+ leader_data = []
126
+
127
+ data = {}
128
+ leaders.each do |leader_data_item|
129
+ if member_attribute
130
+ data[:member] = leader_data_item
131
+ else
132
+ data[:score] = leader_data_item
133
+ data[:rank] = rank_for(data[:member], use_zero_index_for_rank) if with_rank
134
+ leader_data << data
135
+ data = {}
136
+ end
137
+
138
+ member_attribute = !member_attribute
139
+ end
140
+
141
+ leader_data
142
+ end
143
+ 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,8 @@
1
+ dir ./test/db
2
+ pidfile ./redis.pid
3
+ port 6379
4
+ timeout 300
5
+ loglevel debug
6
+ logfile stdout
7
+ databases 16
8
+ daemonize yes
@@ -0,0 +1,169 @@
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.0', 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
+ private
163
+
164
+ def add_members_to_leaderboard(members_to_add = 5)
165
+ 1.upto(members_to_add) do |index|
166
+ @leaderboard.add_member("member_#{index}", index)
167
+ end
168
+ end
169
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: leaderboard
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - David Czarnecki
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-05 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ prerelease: false
24
+ name: redis
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 3
31
+ segments:
32
+ - 0
33
+ version: "0"
34
+ requirement: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ type: :development
37
+ prerelease: false
38
+ name: bundler
39
+ version_requirements: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 23
45
+ segments:
46
+ - 1
47
+ - 0
48
+ - 0
49
+ version: 1.0.0
50
+ requirement: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ type: :development
53
+ prerelease: false
54
+ name: jeweler
55
+ version_requirements: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 7
61
+ segments:
62
+ - 1
63
+ - 5
64
+ - 2
65
+ version: 1.5.2
66
+ requirement: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ type: :development
69
+ prerelease: false
70
+ name: rcov
71
+ version_requirements: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirement: *id004
81
+ description: Leaderboards backed by Redis
82
+ email: dczarnecki@agoragames.com
83
+ executables: []
84
+
85
+ extensions: []
86
+
87
+ extra_rdoc_files:
88
+ - LICENSE.txt
89
+ - README.rdoc
90
+ files:
91
+ - .document
92
+ - .rvmrc
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.rdoc
96
+ - Rakefile
97
+ - VERSION
98
+ - leaderboard.gemspec
99
+ - lib/leaderboard.rb
100
+ - test/db/.gitkeep
101
+ - test/helper.rb
102
+ - test/test.conf
103
+ - test/test_leaderboard.rb
104
+ has_rdoc: true
105
+ homepage: http://github.com/agoragames/leaderboard
106
+ licenses:
107
+ - MIT
108
+ post_install_message:
109
+ rdoc_options: []
110
+
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ none: false
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ hash: 3
119
+ segments:
120
+ - 0
121
+ version: "0"
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ none: false
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ hash: 3
128
+ segments:
129
+ - 0
130
+ version: "0"
131
+ requirements: []
132
+
133
+ rubyforge_project:
134
+ rubygems_version: 1.3.7
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Leaderboards backed by Redis
138
+ test_files:
139
+ - test/helper.rb
140
+ - test/test_leaderboard.rb