class TieRankingLeaderboard

Constants

DEFAULT_OPTIONS

Default options when creating a leaderboard. Page size is 25 and reverse is set to false, meaning various methods will return results in highest-to-lowest order.

Public Class Methods

new(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS) click to toggle source

Create a new instance of a leaderboard.

@param leaderboard [String] Name of the leaderboard. @param options [Hash] Options for the leaderboard such as :page_size. @param redis_options [Hash] Options for configuring Redis.

Examples

leaderboard = Leaderboard.new('highscores')
leaderboard = Leaderboard.new('highscores', {:page_size => 10})
Calls superclass method Leaderboard::new
# File lib/tie_ranking_leaderboard.rb, line 28
def initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS)
  super

  leaderboard_options = DEFAULT_OPTIONS.dup
  leaderboard_options.merge!(options)

  @ties_namespace = leaderboard_options[:ties_namespace]
end

Public Instance Methods

change_score_for_member_in(leaderboard_name, member, delta, member_data = nil) click to toggle source

Change the score for a member in the named leaderboard by a delta which can be positive or negative.

@param leaderboard_name [String] Name of the leaderboard. @param member [String] Member name. @param delta [float] Score change. @param member_data [String] Optional member data.

# File lib/tie_ranking_leaderboard.rb, line 54
def change_score_for_member_in(leaderboard_name, member, delta, member_data = nil)
  previous_score = score_for(member)
  new_score = (previous_score || 0) + delta

  total_members_at_previous_score = @redis_connection.zrevrangebyscore(leaderboard_name, previous_score, previous_score)

  @redis_connection.multi do |transaction|
    transaction.zadd(leaderboard_name, new_score, member)
    transaction.zadd(ties_leaderboard_key(leaderboard_name), new_score, new_score.to_f.to_s)
    transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
  end

  if total_members_at_previous_score.length == 1
    @redis_connection.zrem(ties_leaderboard_key(leaderboard_name), previous_score.to_f.to_s)
  end
end
delete_leaderboard_named(leaderboard_name) click to toggle source

Delete the named leaderboard.

@param leaderboard_name [String] Name of the leaderboard.

# File lib/tie_ranking_leaderboard.rb, line 40
def delete_leaderboard_named(leaderboard_name)
  @redis_connection.multi do |transaction|
    transaction.del(leaderboard_name)
    transaction.del(member_data_key(leaderboard_name))
    transaction.del(ties_leaderboard_key(leaderboard_name))
  end
end
expire_leaderboard_at_for(leaderboard_name, timestamp) click to toggle source

Expire the given leaderboard at a specific UNIX timestamp. Do not use this with leaderboards that utilize member data as there is no facility to cascade the expiration out to the keys for the member data.

@param leaderboard_name [String] Name of the leaderboard. @param timestamp [int] UNIX timestamp at which the leaderboard will be expired.

# File lib/tie_ranking_leaderboard.rb, line 203
def expire_leaderboard_at_for(leaderboard_name, timestamp)
  @redis_connection.multi do |transaction|
    transaction.expireat(leaderboard_name, timestamp)
    transaction.expireat(ties_leaderboard_key(leaderboard_name), timestamp)
    transaction.expireat(member_data_key(leaderboard_name), timestamp)
  end
end
expire_leaderboard_for(leaderboard_name, seconds) click to toggle source

Expire the given leaderboard in a set number of seconds. Do not use this with leaderboards that utilize member data as there is no facility to cascade the expiration out to the keys for the member data.

@param leaderboard_name [String] Name of the leaderboard. @param seconds [int] Number of seconds after which the leaderboard will be expired.

# File lib/tie_ranking_leaderboard.rb, line 189
def expire_leaderboard_for(leaderboard_name, seconds)
  @redis_connection.multi do |transaction|
    transaction.expire(leaderboard_name, seconds)
    transaction.expire(ties_leaderboard_key(leaderboard_name), seconds)
    transaction.expire(member_data_key(leaderboard_name), seconds)
  end
end
rank_for_in(leaderboard_name, member) click to toggle source

Retrieve the rank for a member in the named leaderboard.

@param leaderboard_name [String] Name of the leaderboard. @param member [String] Member name.

@return the rank for a member in the leaderboard.

# File lib/tie_ranking_leaderboard.rb, line 138
def rank_for_in(leaderboard_name, member)
  member_score = score_for_in(leaderboard_name, member)
  if @reverse
    return @redis_connection.zrank(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s) + 1 rescue nil
  else
    return @redis_connection.zrevrank(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s) + 1 rescue nil
  end
end
rank_member_across(leaderboards, member, score, member_data = nil) click to toggle source

Rank a member across multiple leaderboards.

@param leaderboards [Array] Leaderboard names. @param member [String] Member name. @param score [float] Member score. @param member_data [String] Optional member data.

# File lib/tie_ranking_leaderboard.rb, line 97
def rank_member_across(leaderboards, member, score, member_data = nil)
  leaderboards.each do |leaderboard_name|
    rank_member_in(leaderboard_name, member, score, member_data)
  end
end
rank_member_in(leaderboard_name, member, score, member_data = nil) click to toggle source

Rank a member in the named leaderboard.

@param leaderboard_name [String] Name of the leaderboard. @param member [String] Member name. @param score [float] Member score. @param member_data [String] Optional member data.

# File lib/tie_ranking_leaderboard.rb, line 77
def rank_member_in(leaderboard_name, member, score, member_data = nil)
  member_score = @redis_connection.zscore(leaderboard_name, member) || nil
  can_delete_score = member_score &&
    members_from_score_range_in(leaderboard_name, member_score, member_score).length == 1 &&
    member_score != score

  @redis_connection.multi do |transaction|
    transaction.zadd(leaderboard_name, score, member)
    transaction.zadd(ties_leaderboard_key(leaderboard_name), score, score.to_f.to_s)
    transaction.zrem(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s) if can_delete_score
    transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
  end
end
rank_members_in(leaderboard_name, *members_and_scores) click to toggle source

Rank an array of members in the named leaderboard.

@param leaderboard_name [String] Name of the leaderboard. @param members_and_scores [Splat or Array] Variable list of members and scores

# File lib/tie_ranking_leaderboard.rb, line 107
def rank_members_in(leaderboard_name, *members_and_scores)
  if members_and_scores.is_a?(Array)
    members_and_scores.flatten!
  end

  members_and_scores.each_slice(2) do |member_and_score|
    rank_member_in(leaderboard_name, member_and_score[0], member_and_score[1])
  end
end
ranked_in_list_in(leaderboard_name, members, options = {}) click to toggle source

Retrieve a page of leaders from the named leaderboard for a given list of members.

@param leaderboard_name [String] Name of the leaderboard. @param members [Array] Member names. @param options [Hash] Options to be used when retrieving the page from the named leaderboard.

@return a page of leaders from the named leaderboard for a given list of members.

# File lib/tie_ranking_leaderboard.rb, line 218
def ranked_in_list_in(leaderboard_name, members, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  ranks_for_members = []

  responses = @redis_connection.multi do |transaction|
    members.each do |member|
      if @reverse
        transaction.zrank(leaderboard_name, member)
      else
        transaction.zrevrank(leaderboard_name, member)
      end
      transaction.zscore(leaderboard_name, member)
    end
  end unless leaderboard_options[:members_only]

  members.each_with_index do |member, index|
    data = {}
    data[@member_key] = member
    unless leaderboard_options[:members_only]
      data[@score_key] = responses[index * 2 + 1].to_f if responses[index * 2 + 1]

      if @reverse
        data[@rank_key] = @redis_connection.zrank(ties_leaderboard_key(leaderboard_name), data[@score_key].to_s) + 1 rescue nil
      else
        data[@rank_key] = @redis_connection.zrevrank(ties_leaderboard_key(leaderboard_name), data[@score_key].to_s) + 1 rescue nil
      end

      if data[@rank_key] == nil
        next unless leaderboard_options[:include_missing]
      end
    end

    ranks_for_members << data
  end

  if leaderboard_options[:with_member_data]
    included_members = ranks_for_members.collect { |member| member[@member_key] }
    members_data_for_in(leaderboard_name, included_members).each_with_index do |member_data, index|
      ranks_for_members[index][@member_data_key] = member_data
    end
  end

  case leaderboard_options[:sort_by]
  when :rank
    ranks_for_members = ranks_for_members.sort_by { |member| member[@rank_key] }
  when :score
    ranks_for_members = ranks_for_members.sort_by { |member| member[@score_key] }
  end

  ranks_for_members
end
remove_member_from(leaderboard_name, member) click to toggle source

Remove a member from the named leaderboard.

@param leaderboard_name [String] Name of the leaderboard. @param member [String] Member name.

# File lib/tie_ranking_leaderboard.rb, line 121
def remove_member_from(leaderboard_name, member)
  member_score = @redis_connection.zscore(leaderboard_name, member) || nil
  can_delete_score = member_score && members_from_score_range_in(leaderboard_name, member_score, member_score).length == 1

  @redis_connection.multi do |transaction|
    transaction.zrem(leaderboard_name, member)
    transaction.zrem(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s) if can_delete_score
    transaction.hdel(member_data_key(leaderboard_name), member)
  end
end
remove_members_in_score_range_in(leaderboard_name, min_score, max_score) click to toggle source

Remove members from the named leaderboard in a given score range.

@param leaderboard_name [String] Name of the leaderboard. @param min_score [float] Minimum score. @param max_score [float] Maximum score.

# File lib/tie_ranking_leaderboard.rb, line 176
def remove_members_in_score_range_in(leaderboard_name, min_score, max_score)
  @redis_connection.multi do |transaction|
    transaction.zremrangebyscore(leaderboard_name, min_score, max_score)
    transaction.zremrangebyscore(ties_leaderboard_key(leaderboard_name), min_score, max_score)
  end
end
score_and_rank_for_in(leaderboard_name, member) click to toggle source

Retrieve the score and rank for a member in the named leaderboard.

@param leaderboard_name [String]Name of the leaderboard. @param member [String] Member name.

@return the score and rank for a member in the named leaderboard as a Hash.

# File lib/tie_ranking_leaderboard.rb, line 153
def score_and_rank_for_in(leaderboard_name, member)
  member_score = @redis_connection.zscore(leaderboard_name, member)

  responses = @redis_connection.multi do |transaction|
    transaction.zscore(leaderboard_name, member)
    if @reverse
      transaction.zrank(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s)
    else
      transaction.zrevrank(ties_leaderboard_key(leaderboard_name), member_score.to_f.to_s)
    end
  end

  responses[0] = responses[0].to_f if responses[0]
  responses[1] = responses[1] + 1 rescue nil

  {@member_key => member, @score_key => responses[0], @rank_key => responses[1]}
end

Protected Instance Methods

ties_leaderboard_key(leaderboard_name) click to toggle source

Key for ties leaderboard.

@param leaderboard_name [String] Name of the leaderboard.

@return a key in the form of leaderboard_name:ties_namespace

# File lib/tie_ranking_leaderboard.rb, line 279
def ties_leaderboard_key(leaderboard_name)
  "#{leaderboard_name}:#{@ties_namespace}"
end