Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 3 proposal: Store optional member data in a single hash #26

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
22 changes: 13 additions & 9 deletions README.markdown
Expand Up @@ -119,30 +119,34 @@ Get some information about your leaderboard:
=> 1 => 1
``` ```


The `rank_member` call will also accept an optional hash of member data that could The `rank_member` call will also accept an optional string member data that could
be used to store other information about a given member in the leaderboard. This be used to store other information about a given member in the leaderboard. This
may be useful in situations where you are storing member IDs in the leaderboard and may be useful in situations where you are storing member IDs in the leaderboard and
you want to be able to store a member name for display. Example: you want to be able to store a member name for display. You could use JSON to
encode a Hash of member data. Example:


```ruby ```ruby
highscore_lb.rank_member('84849292', 1, {'username' => 'member_name'}) require 'json'
highscore_lb.rank_member('84849292', 1, JSON.generate({'username' => 'member_name'})
``` ```


You can retrieve, update and remove the optional member data using the You can retrieve, update and remove the optional member data using the
`member_data_for`, `update_member_data` and `remove_member_data` calls. Example: `member_data_for`, `update_member_data` and `remove_member_data` calls. Example:


```ruby ```ruby
highscore_lb.member_data_for('84849292') JSON.parse(highscore_lb.member_data_for('84849292'))
=> {"username"=>"member_name"} => {"username"=>"member_name"}


highscore_lb.update_member_data('84849292', {'last_updated' => Time.now, 'username' => 'updated_member_name'}) highscore_lb.update_member_data('84849292', JSON.generate({'last_updated' => Time.now, 'username' => 'updated_member_name'}))
=> "OK" => "OK"
highscore_lb.member_data_for('84849292') JSON.parse(highscore_lb.member_data_for('84849292'))
=> {"username"=>"updated_member_name", "last_updated"=>"2012-06-09 09:11:06 -0400"} => {"username"=>"updated_member_name", "last_updated"=>"2012-06-09 09:11:06 -0400"}


highscore_lb.remove_member_data('84849292') highscore_lb.remove_member_data('84849292')
``` ```


If you delete the leaderboard, ALL of the member data is deleted as well.

Get some information about a specific member(s) in the leaderboard: Get some information about a specific member(s) in the leaderboard:


```ruby ```ruby
Expand Down
26 changes: 13 additions & 13 deletions lib/leaderboard.rb
Expand Up @@ -103,7 +103,10 @@ def delete_leaderboard
# #
# @param leaderboard_name [String] Name of the leaderboard. # @param leaderboard_name [String] Name of the leaderboard.
def delete_leaderboard_named(leaderboard_name) def delete_leaderboard_named(leaderboard_name)
@redis_connection.del(leaderboard_name) @redis_connection.multi do |transaction|
transaction.del(leaderboard_name)
transaction.del(member_data_key(leaderboard_name))
end
end end


# Rank a member in the leaderboard. # Rank a member in the leaderboard.
Expand All @@ -124,9 +127,7 @@ def rank_member(member, score, member_data = nil)
def rank_member_in(leaderboard_name, member, score, member_data) def rank_member_in(leaderboard_name, member, score, member_data)
@redis_connection.multi do |transaction| @redis_connection.multi do |transaction|
transaction.zadd(leaderboard_name, score, member) transaction.zadd(leaderboard_name, score, member)
if member_data transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
transaction.hmset(member_data_key(leaderboard_name, member), *member_data.to_a.flatten)
end
end end
end end


Expand All @@ -146,7 +147,7 @@ def member_data_for(member)
# #
# @return Hash of optional member data. # @return Hash of optional member data.
def member_data_for_in(leaderboard_name, member) def member_data_for_in(leaderboard_name, member)
@redis_connection.hgetall(member_data_key(leaderboard_name, member)) @redis_connection.hget(member_data_key(leaderboard_name), member)
end end


# Update the optional member data for a given member in the leaderboard. # Update the optional member data for a given member in the leaderboard.
Expand All @@ -163,7 +164,7 @@ def update_member_data(member, member_data)
# @param member [String] Member name. # @param member [String] Member name.
# @param member_data [Hash] Optional member data. # @param member_data [Hash] Optional member data.
def update_member_data_in(leaderboard_name, member, member_data) def update_member_data_in(leaderboard_name, member, member_data)
@redis_connection.hmset(member_data_key(leaderboard_name, member), *member_data.to_a.flatten) @redis_connection.hset(member_data_key(leaderboard_name), member, member_data)
end end


# Remove the optional member data for a given member in the leaderboard. # Remove the optional member data for a given member in the leaderboard.
Expand All @@ -178,7 +179,7 @@ def remove_member_data(member)
# @param leaderboard_name [String] Name of the leaderboard. # @param leaderboard_name [String] Name of the leaderboard.
# @param member [String] Member name. # @param member [String] Member name.
def remove_member_data_in(leaderboard_name, member) def remove_member_data_in(leaderboard_name, member)
@redis_connection.del(member_data_key(leaderboard_name, member)) @redis_connection.hdel(member_data_key(leaderboard_name), member)
end end


# Rank an array of members in the leaderboard. # Rank an array of members in the leaderboard.
Expand Down Expand Up @@ -218,7 +219,7 @@ def remove_member(member)
def remove_member_from(leaderboard_name, member) def remove_member_from(leaderboard_name, member)
@redis_connection.multi do |transaction| @redis_connection.multi do |transaction|
transaction.zrem(leaderboard_name, member) transaction.zrem(leaderboard_name, member)
transaction.del(member_data_key(leaderboard_name, member)) transaction.del(member_data_key(leaderboard_name), member)
end end
end end


Expand Down Expand Up @@ -815,11 +816,10 @@ def intersect_leaderboards(destination, keys, options = {:aggregate => :sum})
# Key for retrieving optional member data. # Key for retrieving optional member data.
# #
# @param leaderboard_name [String] Name of the leaderboard. # @param leaderboard_name [String] Name of the leaderboard.
# @param member [String] Member name. #
# # @return a key in the form of +leaderboard_name:member_data+
# @return a key in the form of +leaderboard_name:data:member+ def member_data_key(leaderboard_name)
def member_data_key(leaderboard_name, member) "#{leaderboard_name}:member_data"
"#{leaderboard_name}:member_data:#{member}"
end end


# Validate and return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1. # Validate and return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1.
Expand Down
38 changes: 20 additions & 18 deletions spec/leaderboard_spec.rb
Expand Up @@ -40,8 +40,10 @@
rank_members_in_leaderboard rank_members_in_leaderboard


@redis_connection.exists('name').should be_true @redis_connection.exists('name').should be_true
@redis_connection.exists('name:member_data').should be_true
@leaderboard.delete_leaderboard @leaderboard.delete_leaderboard
@redis_connection.exists('name').should be_false @redis_connection.exists('name').should be_false
@redis_connection.exists('name:member_data').should be_false
end end


it 'should allow you to pass in an existing redis connection in the initializer' do it 'should allow you to pass in an existing redis connection in the initializer' do
Expand Down Expand Up @@ -170,11 +172,11 @@


members = @leaderboard.members_from_score_range(10, 15, {:with_scores => true, :with_rank => true, :with_member_data => true}) members = @leaderboard.members_from_score_range(10, 15, {:with_scores => true, :with_rank => true, :with_member_data => true})


member_15 = {:member => 'member_15', :rank => 11, :score => 15.0, :member_data => {'member_name' => 'Leaderboard member 15'}} member_15 = {:member => 'member_15', :rank => 11, :score => 15.0, :member_data => {:member_name => 'Leaderboard member 15'}.to_s}
members[0].should eql(member_15) members[0].should == member_15


member_10 = {:member => 'member_10', :rank => 16, :score => 10.0, :member_data => {'member_name' => 'Leaderboard member 10'}} member_10 = {:member => 'member_10', :rank => 16, :score => 10.0, :member_data => {:member_name => 'Leaderboard member 10'}.to_s}
members[5].should eql(member_10) members[5].should == member_10
end end


it 'should allow you to retrieve leaders without scores and ranks' do it 'should allow you to retrieve leaders without scores and ranks' do
Expand All @@ -196,34 +198,34 @@
@leaderboard.total_members.should be(Leaderboard::DEFAULT_PAGE_SIZE) @leaderboard.total_members.should be(Leaderboard::DEFAULT_PAGE_SIZE)
leaders = @leaderboard.leaders(1, {:with_scores => false, :with_rank => false, :with_member_data => true}) leaders = @leaderboard.leaders(1, {:with_scores => false, :with_rank => false, :with_member_data => true})


member_25 = {:member => 'member_25', :member_data => { "member_name" => "Leaderboard member 25" }} member_25 = {:member => 'member_25', :member_data => { :member_name => "Leaderboard member 25" }.to_s }
leaders[0].should eql(member_25) leaders[0].should == member_25

member_1 = {:member => 'member_1', :member_data => { "member_name" => "Leaderboard member 1" }} member_1 = {:member => 'member_1', :member_data => { :member_name => "Leaderboard member 1" }.to_s }
leaders[24].should eql(member_1) leaders[24].should == member_1
end end


it 'should allow you to retrieve optional member data' do it 'should allow you to retrieve optional member data' do
@leaderboard.rank_member('member_id', 1, {'username' => 'member_name', 'other_data_key' => 'other_data_value'}) @leaderboard.rank_member('member_id', 1, {'username' => 'member_name', 'other_data_key' => 'other_data_value'})


@leaderboard.member_data_for('unknown_member').should eql({}) @leaderboard.member_data_for('unknown_member').should be_nil
@leaderboard.member_data_for('member_id').should eql({'username' => 'member_name', 'other_data_key' => 'other_data_value'}) @leaderboard.member_data_for('member_id').should == {'username' => 'member_name', 'other_data_key' => 'other_data_value'}.to_s
end end


it 'should allow you to update optional member data' do it 'should allow you to update optional member data' do
@leaderboard.rank_member('member_id', 1, {'username' => 'member_name'}) @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})


@leaderboard.member_data_for('member_id').should eql({'username' => 'member_name'}) @leaderboard.member_data_for('member_id').should == {'username' => 'member_name'}.to_s
@leaderboard.update_member_data('member_id', {'other_data_key' => 'other_data_value'}) @leaderboard.update_member_data('member_id', {'username' => 'member_name', 'other_data_key' => 'other_data_value'})
@leaderboard.member_data_for('member_id').should eql({'username' => 'member_name', 'other_data_key' => 'other_data_value'}) @leaderboard.member_data_for('member_id').should == {'username' => 'member_name', 'other_data_key' => 'other_data_value'}.to_s
end end


it 'should allow you to remove optional member data' do it 'should allow you to remove optional member data' do
@leaderboard.rank_member('member_id', 1, {'username' => 'member_name'}) @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})


@leaderboard.member_data_for('member_id').should eql({'username' => 'member_name'}) @leaderboard.member_data_for('member_id').should == {'username' => 'member_name'}.to_s
@leaderboard.remove_member_data('member_id') @leaderboard.remove_member_data('member_id')
@leaderboard.member_data_for('member_id').should eql({}) @leaderboard.member_data_for('member_id').should be_nil
end end


it 'should allow you to call leaders with various options that respect the defaults for the options not passed in' do it 'should allow you to call leaders with various options that respect the defaults for the options not passed in' do
Expand Down Expand Up @@ -268,8 +270,8 @@
@leaderboard.member_at(26)[:rank].should eql(26) @leaderboard.member_at(26)[:rank].should eql(26)
@leaderboard.member_at(50)[:rank].should eql(50) @leaderboard.member_at(50)[:rank].should eql(50)
@leaderboard.member_at(51).should be_nil @leaderboard.member_at(51).should be_nil
@leaderboard.member_at(1, :with_member_data => true)[:member_data].should eql({'member_name' => 'Leaderboard member 50'}) @leaderboard.member_at(1, :with_member_data => true)[:member_data].should == {:member_name => 'Leaderboard member 50'}.to_s
@leaderboard.member_at(1, :use_zero_index_for_rank => true)[:rank].should eql(0) @leaderboard.member_at(1, :use_zero_index_for_rank => true)[:rank].should == 0
end end


it 'should return the correct information when calling around_me' do it 'should return the correct information when calling around_me' do
Expand Down
12 changes: 6 additions & 6 deletions spec/reverse_leaderboard_spec.rb
Expand Up @@ -60,11 +60,11 @@


members = @leaderboard.members_from_score_range(10, 15, {:with_scores => true, :with_rank => true, :with_member_data => true}) members = @leaderboard.members_from_score_range(10, 15, {:with_scores => true, :with_rank => true, :with_member_data => true})


member_10 = {:member => 'member_10', :rank => 10, :score => 10.0, :member_data => {'member_name' => 'Leaderboard member 10'}} member_10 = {:member => 'member_10', :rank => 10, :score => 10.0, :member_data => {:member_name => 'Leaderboard member 10'}.to_s}
members[0].should eql(member_10) members[0].should == member_10


member_15 = {:member => 'member_15', :rank => 15, :score => 15.0, :member_data => {'member_name' => 'Leaderboard member 15'}} member_15 = {:member => 'member_15', :rank => 15, :score => 15.0, :member_data => {:member_name => 'Leaderboard member 15'}.to_s}
members[5].should eql(member_15) members[5].should == member_15
end end


it 'should allow you to retrieve leaders without scores and ranks' do it 'should allow you to retrieve leaders without scores and ranks' do
Expand Down Expand Up @@ -122,8 +122,8 @@
@leaderboard.member_at(26)[:rank].should eql(26) @leaderboard.member_at(26)[:rank].should eql(26)
@leaderboard.member_at(50)[:rank].should eql(50) @leaderboard.member_at(50)[:rank].should eql(50)
@leaderboard.member_at(51).should be_nil @leaderboard.member_at(51).should be_nil
@leaderboard.member_at(1, :with_member_data => true)[:member_data].should eql({'member_name' => 'Leaderboard member 1'}) @leaderboard.member_at(1, :with_member_data => true)[:member_data].should == {:member_name => 'Leaderboard member 1'}.to_s
@leaderboard.member_at(1, :use_zero_index_for_rank => true)[:rank].should eql(0) @leaderboard.member_at(1, :use_zero_index_for_rank => true)[:rank].should == 0
end end


it 'should return the correct information when calling around_me' do it 'should return the correct information when calling around_me' do
Expand Down