Class: Leaderboard

Inherits:
Object
  • Object
show all
Defined in:
lib/leaderboard.rb,
lib/leaderboard/version.rb

Constant Summary collapse

DEFAULT_PAGE_SIZE =

Default page size: 25

25
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.

{
  :page_size => DEFAULT_PAGE_SIZE,
  :reverse => false,
  :member_key => :member,
  :rank_key => :rank,
  :score_key => :score,
  :member_data_key => :member_data,
  :member_data_namespace => 'member_data',
  :global_member_data => false
}
DEFAULT_REDIS_HOST =

Default Redis host: localhost

'localhost'
DEFAULT_REDIS_PORT =

Default Redis post: 6379

6379
DEFAULT_REDIS_OPTIONS =

Default Redis options when creating a connection to Redis. The DEFAULT_REDIS_HOST and DEFAULT_REDIS_PORT will be passed.

{
  :host => DEFAULT_REDIS_HOST,
  :port => DEFAULT_REDIS_PORT
}
DEFAULT_LEADERBOARD_REQUEST_OPTIONS =

Default options when requesting data from a leaderboard. :with_member_data false: Return member data along with the member names. :page_size nil: The default page size will be used. :members_only false: Only return the member name, not their score and rank. :sort_by :none: The default sort for a call to ‘ranked_in_list`.

{
  :with_member_data => false,
  :page_size => nil,
  :members_only => false,
  :sort_by => :none,
  :include_missing => true
}
VERSION =
'3.12.0'.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS) ⇒ Leaderboard

Create a new instance of a leaderboard.

Examples

leaderboard = Leaderboard.new('highscores')
leaderboard = Leaderboard.new('highscores', {:page_size => 10})

Parameters:

  • leaderboard (String)

    Name of the leaderboard.

  • options (Hash) (defaults to: DEFAULT_OPTIONS)

    Options for the leaderboard such as :page_size.

  • redis_options (Hash) (defaults to: DEFAULT_REDIS_OPTIONS)

    Options for configuring Redis.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/leaderboard.rb', line 69

def initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS)
  leaderboard_options = DEFAULT_OPTIONS.dup
  leaderboard_options.merge!(options)

  @leaderboard_name = leaderboard_name

  @reverse   = leaderboard_options[:reverse]
  @page_size = leaderboard_options[:page_size]
  if @page_size.nil? || @page_size < 1
    @page_size = DEFAULT_PAGE_SIZE
  end
  @member_key = leaderboard_options[:member_key]
  @rank_key = leaderboard_options[:rank_key]
  @score_key = leaderboard_options[:score_key]
  @member_data_key = leaderboard_options[:member_data_key]
  @member_data_namespace = leaderboard_options[:member_data_namespace]
  @global_member_data = leaderboard_options[:global_member_data]

  @redis_connection = redis_options[:redis_connection]
  unless @redis_connection.nil?
    redis_options.delete(:redis_connection)
  end

  @redis_connection = Redis.new(redis_options) if @redis_connection.nil?
end

Instance Attribute Details

#leaderboard_nameObject (readonly)

Name of the leaderboard.



49
50
51
# File 'lib/leaderboard.rb', line 49

def leaderboard_name
  @leaderboard_name
end

#page_sizeObject

Page size to be used when paging through the leaderboard.



52
53
54
# File 'lib/leaderboard.rb', line 52

def page_size
  @page_size
end

#reverseObject

Determines whether or not various leaderboard methods return their data in highest-to-lowest (:reverse false) or lowest-to-highest (:reverse true)



57
58
59
# File 'lib/leaderboard.rb', line 57

def reverse
  @reverse
end

Instance Method Details

#all_leaders(options = {}) ⇒ Object Also known as: all_members

Retrieve all leaders from the leaderboard.

Parameters:

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the leaders from the leaderboard.

Returns:

  • the leaders from the leaderboard.



739
740
741
# File 'lib/leaderboard.rb', line 739

def all_leaders(options = {})
  all_leaders_from(@leaderboard_name, options)
end

#all_leaders_from(leaderboard_name, options = {}) ⇒ Object Also known as: all_members_from

Retrieves all leaders from the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the leaders from the named leaderboard.

Returns:

  • the named leaderboard.



751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
# File 'lib/leaderboard.rb', line 751

def all_leaders_from(leaderboard_name, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  if @reverse
    raw_leader_data = @redis_connection.zrange(leaderboard_name, 0, -1, :with_scores => false)
  else
    raw_leader_data = @redis_connection.zrevrange(leaderboard_name, 0, -1, :with_scores => false)
  end

  if raw_leader_data
    return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
  else
    return []
  end
end

#around_me(member, options = {}) ⇒ Object

Retrieve a page of leaders from the leaderboard around a given member.

Parameters:

  • member (String)

    Member name.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the leaderboard.

Returns:

  • a page of leaders from the leaderboard around a given member.



908
909
910
# File 'lib/leaderboard.rb', line 908

def around_me(member, options = {})
  around_me_in(@leaderboard_name, member, options)
end

#around_me_in(leaderboard_name, member, options = {}) ⇒ Object

Retrieve a page of leaders from the named leaderboard around a given member.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the named leaderboard.

Returns:

  • a page of leaders from the named leaderboard around a given member. Returns an empty array for a non-existent member.



919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
# File 'lib/leaderboard.rb', line 919

def around_me_in(leaderboard_name, member, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  reverse_rank_for_member = @reverse ?
    @redis_connection.zrank(leaderboard_name, member) :
    @redis_connection.zrevrank(leaderboard_name, member)

  return [] unless reverse_rank_for_member

  page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size

  starting_offset = reverse_rank_for_member - (page_size / 2)
  if starting_offset < 0
    starting_offset = 0
  end

  ending_offset = (starting_offset + page_size) - 1

  raw_leader_data = @reverse ?
    @redis_connection.zrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false) :
    @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)

  if raw_leader_data
    return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
  else
    return []
  end
end

#change_score_for(member, delta, member_data = nil) ⇒ Object

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

Parameters:

  • member (String)

    Member name.

  • delta (float)

    Score change.

  • member_data (String) (defaults to: nil)

    Optional member data.



384
385
386
# File 'lib/leaderboard.rb', line 384

def change_score_for(member, delta, member_data = nil)
  change_score_for_member_in(@leaderboard_name, member, delta, member_data)
end

#change_score_for_member_in(leaderboard_name, member, delta, member_data) ⇒ Object

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

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

  • delta (float)

    Score change.

  • member_data (String)

    Optional member data.



394
395
396
397
398
399
# File 'lib/leaderboard.rb', line 394

def change_score_for_member_in(leaderboard_name, member, delta, member_data)
  @redis_connection.multi do |transaction|
    transaction.zincrby(leaderboard_name, delta, member)
    transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
  end
end

#check_member?(member) ⇒ Boolean

Check to see if a member exists in the leaderboard.

Parameters:

  • member (String)

    Member name.

Returns:

  • (Boolean)

    true if the member exists in the leaderboard, false otherwise.



449
450
451
# File 'lib/leaderboard.rb', line 449

def check_member?(member)
  check_member_in?(@leaderboard_name, member)
end

#check_member_in?(leaderboard_name, member) ⇒ Boolean

Check to see if a member exists in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

  • (Boolean)

    true if the member exists in the named leaderboard, false otherwise.



459
460
461
# File 'lib/leaderboard.rb', line 459

def check_member_in?(leaderboard_name, member)
  !@redis_connection.zscore(leaderboard_name, member).nil?
end

#delete_leaderboardObject

Delete the current leaderboard.



112
113
114
# File 'lib/leaderboard.rb', line 112

def delete_leaderboard
  delete_leaderboard_named(@leaderboard_name)
end

#delete_leaderboard_named(leaderboard_name) ⇒ Object

Delete the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.



119
120
121
122
123
124
# File 'lib/leaderboard.rb', line 119

def delete_leaderboard_named(leaderboard_name)
  @redis_connection.multi do |transaction|
    transaction.del(leaderboard_name)
    transaction.del(member_data_key(leaderboard_name))
  end
end

#disconnectObject

Disconnect the Redis connection.



107
108
109
# File 'lib/leaderboard.rb', line 107

def disconnect
  @redis_connection.client.disconnect
end

#expire_leaderboard(seconds) ⇒ Object

Expire the current 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.

Parameters:

  • seconds (int)

    Number of seconds after which the leaderboard will be expired.



638
639
640
# File 'lib/leaderboard.rb', line 638

def expire_leaderboard(seconds)
  expire_leaderboard_for(@leaderboard_name, seconds)
end

#expire_leaderboard_at(timestamp) ⇒ Object

Expire the current 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.

Parameters:

  • timestamp (int)

    UNIX timestamp at which the leaderboard will be expired.



660
661
662
# File 'lib/leaderboard.rb', line 660

def expire_leaderboard_at(timestamp)
  expire_leaderboard_at_for(@leaderboard_name, timestamp)
end

#expire_leaderboard_at_for(leaderboard_name, timestamp) ⇒ Object

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.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • timestamp (int)

    UNIX timestamp at which the leaderboard will be expired.



670
671
672
673
674
675
# File 'lib/leaderboard.rb', line 670

def expire_leaderboard_at_for(leaderboard_name, timestamp)
  @redis_connection.multi do |transaction|
    transaction.expireat(leaderboard_name, timestamp)
    transaction.expireat(member_data_key(leaderboard_name), timestamp)
  end
end

#expire_leaderboard_for(leaderboard_name, seconds) ⇒ Object

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.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • seconds (int)

    Number of seconds after which the leaderboard will be expired.



648
649
650
651
652
653
# File 'lib/leaderboard.rb', line 648

def expire_leaderboard_for(leaderboard_name, seconds)
  @redis_connection.multi do |transaction|
    transaction.expire(leaderboard_name, seconds)
    transaction.expire(member_data_key(leaderboard_name), seconds)
  end
end

#intersect_leaderboards(destination, keys, options = {:aggregate => :sum}) ⇒ Object

Intersect leaderboards given by keys with this leaderboard into a named destination leaderboard.

Parameters:

  • destination (String)

    Destination leaderboard name.

  • keys (Array)

    Leaderboards to be merged with the current leaderboard.

  • options (Hash) (defaults to: {:aggregate => :sum})

    Options for intersecting the leaderboards.



1028
1029
1030
# File 'lib/leaderboard.rb', line 1028

def intersect_leaderboards(destination, keys, options = {:aggregate => :sum})
  @redis_connection.zinterstore(destination, keys.insert(0, @leaderboard_name), options)
end

#leaders(current_page, options = {}) ⇒ Object Also known as: members

Retrieve a page of leaders from the leaderboard.

Parameters:

  • current_page (int)

    Page to retrieve from the leaderboard.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the leaderboard.

Returns:

  • a page of leaders from the leaderboard.



683
684
685
# File 'lib/leaderboard.rb', line 683

def leaders(current_page, options = {})
  leaders_in(@leaderboard_name, current_page, options)
end

#leaders_in(leaderboard_name, current_page, options = {}) ⇒ Object Also known as: members_in

Retrieve a page of leaders from the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • current_page (int)

    Page to retrieve from the named leaderboard.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the named leaderboard.

Returns:

  • a page of leaders from the named leaderboard.



696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'lib/leaderboard.rb', line 696

def leaders_in(leaderboard_name, current_page, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  if current_page < 1
    current_page = 1
  end

  page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size

  if current_page > total_pages_in(leaderboard_name, page_size)
    current_page = total_pages_in(leaderboard_name, page_size)
  end

  index_for_redis = current_page - 1

  starting_offset = (index_for_redis * page_size)
  if starting_offset < 0
    starting_offset = 0
  end

  ending_offset = (starting_offset + page_size) - 1

  if @reverse
    raw_leader_data = @redis_connection.zrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
  else
    raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_offset, ending_offset, :with_scores => false)
  end

  if raw_leader_data
    return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
  else
    return []
  end
end

#member_at(position, options = {}) ⇒ Object

Retrieve a member at the specified index from the leaderboard.

Parameters:

  • position (int)

    Position in leaderboard.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the member from the leaderboard.

Returns:

  • a member from the leaderboard.



878
879
880
# File 'lib/leaderboard.rb', line 878

def member_at(position, options = {})
  member_at_in(@leaderboard_name, position, options)
end

#member_at_in(leaderboard_name, position, options = {}) ⇒ Object

Retrieve a member at the specified index from the leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • position (int)

    Position in named leaderboard.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the member from the named leaderboard.

Returns:

  • a page of leaders from the named leaderboard.



889
890
891
892
893
894
895
896
897
898
899
900
# File 'lib/leaderboard.rb', line 889

def member_at_in(leaderboard_name, position, options = {})
  if position > 0 && position <= total_members_in(leaderboard_name)
    leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
    leaderboard_options.merge!(options)
    page_size = validate_page_size(leaderboard_options[:page_size]) || @page_size
    current_page = (position.to_f / page_size.to_f).ceil
    offset = (position - 1) % page_size

    leaders = leaders_in(leaderboard_name, current_page, options)
    leaders[offset] if leaders
  end
end

#member_data_for(member) ⇒ Object

Retrieve the optional member data for a given member in the leaderboard.

Parameters:

  • member (String)

    Member name.

Returns:

  • String of optional member data.



208
209
210
# File 'lib/leaderboard.rb', line 208

def member_data_for(member)
  member_data_for_in(@leaderboard_name, member)
end

#member_data_for_in(leaderboard_name, member) ⇒ Object

Retrieve the optional member data for a given member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

  • String of optional member data.



218
219
220
# File 'lib/leaderboard.rb', line 218

def member_data_for_in(leaderboard_name, member)
  @redis_connection.hget(member_data_key(leaderboard_name), member)
end

#members_data_for(members) ⇒ Object

Retrieve the optional member data for the given members in the leaderboard.

Parameters:

  • members (Array)

    Member names.

Returns:

  • array of strings of optional member data.



238
239
240
# File 'lib/leaderboard.rb', line 238

def members_data_for(members)
  members_data_for_in(@leaderboard_name, members)
end

#members_data_for_in(leaderboard_name, members) ⇒ Object

Retrieve the optional member data for a given member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • members (Array)

    Member names.

Returns:

  • array of strings of optional member data.



228
229
230
231
# File 'lib/leaderboard.rb', line 228

def members_data_for_in(leaderboard_name, members)
  return [] unless members.size > 0
  @redis_connection.hmget(member_data_key(leaderboard_name), *members)
end

#members_from_rank_range(starting_rank, ending_rank, options = {}) ⇒ Object

Retrieve members from the leaderboard within a given rank range.

Parameters:

  • starting_rank (int)

    Starting rank (inclusive).

  • ending_rank (int)

    Ending rank (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • members from the leaderboard that fall within the given rank range.



811
812
813
# File 'lib/leaderboard.rb', line 811

def members_from_rank_range(starting_rank, ending_rank, options = {})
  members_from_rank_range_in(@leaderboard_name, starting_rank, ending_rank, options)
end

#members_from_rank_range_in(leaderboard_name, starting_rank, ending_rank, options = {}) ⇒ Object

Retrieve members from the named leaderboard within a given rank range.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • starting_rank (int)

    Starting rank (inclusive).

  • ending_rank (int)

    Ending rank (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • members from the leaderboard that fall within the given rank range.



823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
# File 'lib/leaderboard.rb', line 823

def members_from_rank_range_in(leaderboard_name, starting_rank, ending_rank, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  starting_rank -= 1
  if starting_rank < 0
    starting_rank = 0
  end

  ending_rank -= 1
  if ending_rank > total_members_in(leaderboard_name)
    ending_rank = total_members_in(leaderboard_name) - 1
  end

  if @reverse
    raw_leader_data = @redis_connection.zrange(leaderboard_name, starting_rank, ending_rank, :with_scores => false)
  else
    raw_leader_data = @redis_connection.zrevrange(leaderboard_name, starting_rank, ending_rank, :with_scores => false)
  end

  if raw_leader_data
    return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
  else
    return []
  end
end

#members_from_score_range(minimum_score, maximum_score, options = {}) ⇒ Object

Retrieve members from the leaderboard within a given score range.

Parameters:

  • minimum_score (float)

    Minimum score (inclusive).

  • maximum_score (float)

    Maximum score (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • members from the leaderboard that fall within the given score range.



777
778
779
# File 'lib/leaderboard.rb', line 777

def members_from_score_range(minimum_score, maximum_score, options = {})
  members_from_score_range_in(@leaderboard_name, minimum_score, maximum_score, options)
end

#members_from_score_range_in(leaderboard_name, minimum_score, maximum_score, options = {}) ⇒ Object

Retrieve members from the named leaderboard within a given score range.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • minimum_score (float)

    Minimum score (inclusive).

  • maximum_score (float)

    Maximum score (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • members from the leaderboard that fall within the given score range.



789
790
791
792
793
794
795
796
797
798
799
800
801
802
# File 'lib/leaderboard.rb', line 789

def members_from_score_range_in(leaderboard_name, minimum_score, maximum_score, options = {})
  leaderboard_options = DEFAULT_LEADERBOARD_REQUEST_OPTIONS.dup
  leaderboard_options.merge!(options)

  raw_leader_data = @reverse ?
    @redis_connection.zrangebyscore(leaderboard_name, minimum_score, maximum_score) :
    @redis_connection.zrevrangebyscore(leaderboard_name, maximum_score, minimum_score)

  if raw_leader_data
    return ranked_in_list_in(leaderboard_name, raw_leader_data, leaderboard_options)
  else
    return []
  end
end

#merge_leaderboards(destination, keys, options = {:aggregate => :sum}) ⇒ Object

Merge leaderboards given by keys with this leaderboard into a named destination leaderboard.

Parameters:

  • destination (String)

    Destination leaderboard name.

  • keys (Array)

    Leaderboards to be merged with the current leaderboard.

  • options (Hash) (defaults to: {:aggregate => :sum})

    Options for merging the leaderboards.



1019
1020
1021
# File 'lib/leaderboard.rb', line 1019

def merge_leaderboards(destination, keys, options = {:aggregate => :sum})
  @redis_connection.zunionstore(destination, keys.insert(0, @leaderboard_name), options)
end

#page_for(member, page_size = DEFAULT_PAGE_SIZE) ⇒ Object

Determine the page where a member falls in the leaderboard.

Parameters:

  • member (String)

    Member name.

  • page_size (int) (defaults to: DEFAULT_PAGE_SIZE)

    Page size to be used in determining page location.

Returns:

  • the page where a member falls in the leaderboard.



608
609
610
# File 'lib/leaderboard.rb', line 608

def page_for(member, page_size = DEFAULT_PAGE_SIZE)
  page_for_in(@leaderboard_name, member, page_size)
end

#page_for_in(leaderboard_name, member, page_size = DEFAULT_PAGE_SIZE) ⇒ Object

Determine the page where a member falls in the named leaderboard.

Parameters:

  • leaderboard (String)

    Name of the leaderboard.

  • member (String)

    Member name.

  • page_size (int) (defaults to: DEFAULT_PAGE_SIZE)

    Page size to be used in determining page location.

Returns:

  • the page where a member falls in the leaderboard.



619
620
621
622
623
624
625
626
627
628
629
630
631
# File 'lib/leaderboard.rb', line 619

def page_for_in(leaderboard_name, member, page_size = DEFAULT_PAGE_SIZE)
  rank_for_member = @reverse ?
    @redis_connection.zrank(leaderboard_name, member) :
    @redis_connection.zrevrank(leaderboard_name, member)

  if rank_for_member.nil?
    rank_for_member = 0
  else
    rank_for_member += 1
  end

  (rank_for_member.to_f / page_size.to_f).ceil
end

#percentile_for(member) ⇒ Object

Retrieve the percentile for a member in the leaderboard.

Parameters:

  • member (String)

    Member name.

Returns:

  • the percentile for a member in the leaderboard. Return nil for a non-existent member.



537
538
539
# File 'lib/leaderboard.rb', line 537

def percentile_for(member)
  percentile_for_in(@leaderboard_name, member)
end

#percentile_for_in(leaderboard_name, member) ⇒ Object

Retrieve the percentile for a member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

  • the percentile for a member in the named leaderboard.



547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
# File 'lib/leaderboard.rb', line 547

def percentile_for_in(leaderboard_name, member)
  return nil unless check_member_in?(leaderboard_name, member)

  responses = @redis_connection.multi do |transaction|
    transaction.zcard(leaderboard_name)
    transaction.zrevrank(leaderboard_name, member)
  end

  percentile = ((responses[0] - responses[1] - 1).to_f / responses[0].to_f * 100).ceil
  if @reverse
    100 - percentile
  else
    percentile
  end
end

#rank_for(member) ⇒ Object

Retrieve the rank for a member in the leaderboard.

Parameters:

  • member (String)

    Member name.

Returns:

  • the rank for a member in the leaderboard.



406
407
408
# File 'lib/leaderboard.rb', line 406

def rank_for(member)
  rank_for_in(@leaderboard_name, member)
end

#rank_for_in(leaderboard_name, member) ⇒ Object

Retrieve the rank for a member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

  • the rank for a member in the leaderboard.



416
417
418
419
420
421
422
# File 'lib/leaderboard.rb', line 416

def rank_for_in(leaderboard_name, member)
  if @reverse
    return @redis_connection.zrank(leaderboard_name, member) + 1 rescue nil
  else
    return @redis_connection.zrevrank(leaderboard_name, member) + 1 rescue nil
  end
end

#rank_member(member, score, member_data = nil) ⇒ Object

Rank a member in the leaderboard.

Parameters:

  • member (String)

    Member name.

  • score (float)

    Member score.

  • member_data (String) (defaults to: nil)

    Optional member data.



131
132
133
# File 'lib/leaderboard.rb', line 131

def rank_member(member, score, member_data = nil)
  rank_member_in(@leaderboard_name, member, score, member_data)
end

#rank_member_across(leaderboards, member, score, member_data = nil) ⇒ Object

Rank a member across multiple leaderboards.

Parameters:

  • leaderboards (Array)

    Leaderboard names.

  • member (String)

    Member name.

  • score (float)

    Member score.

  • member_data (String) (defaults to: nil)

    Optional member data.



154
155
156
157
158
159
160
161
# File 'lib/leaderboard.rb', line 154

def rank_member_across(leaderboards, member, score, member_data = nil)
  @redis_connection.multi do |transaction|
    leaderboards.each do |leaderboard_name|
      transaction.zadd(leaderboard_name, score, member)
      transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
    end
  end
end

#rank_member_if(rank_conditional, member, score, member_data = nil) ⇒ Object

Rank a member in the leaderboard based on execution of the rank_conditional.

The rank_conditional is passed the following parameters:

member: Member name.
current_score: Current score for the member in the leaderboard.
score: Member score.
member_data: Optional member data.
leaderboard_options: Leaderboard options, e.g. :reverse => Value of reverse option

Parameters:

  • rank_conditional (lambda)

    Lambda which must return true or false that controls whether or not the member is ranked in the leaderboard.

  • member (String)

    Member name.

  • score (float)

    Member score.

  • member_data (String) (defaults to: nil)

    Optional member_data.



176
177
178
# File 'lib/leaderboard.rb', line 176

def rank_member_if(rank_conditional, member, score, member_data = nil)
  rank_member_if_in(@leaderboard_name, rank_conditional, member, score, member_data)
end

#rank_member_if_in(leaderboard_name, rank_conditional, member, score, member_data = nil) ⇒ Object

Rank a member in the named leaderboard based on execution of the rank_conditional.

The rank_conditional is passed the following parameters:

member: Member name.
current_score: Current score for the member in the leaderboard.
score: Member score.
member_data: Optional member data.
leaderboard_options: Leaderboard options, e.g. :reverse => Value of reverse option

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • rank_conditional (lambda)

    Lambda which must return true or false that controls whether or not the member is ranked in the leaderboard.

  • member (String)

    Member name.

  • score (float)

    Member score.

  • member_data (String) (defaults to: nil)

    Optional member_data.



194
195
196
197
198
199
200
201
# File 'lib/leaderboard.rb', line 194

def rank_member_if_in(leaderboard_name, rank_conditional, member, score, member_data = nil)
  current_score = @redis_connection.zscore(leaderboard_name, member)
  current_score = current_score.to_f if current_score

  if rank_conditional.call(member, current_score, score, member_data, {:reverse => @reverse})
    rank_member_in(leaderboard_name, member, score, member_data)
  end
end

#rank_member_in(leaderboard_name, member, score, member_data = nil) ⇒ Object

Rank a member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

  • score (float)

    Member score.

  • member_data (String) (defaults to: nil)

    Optional member data.



141
142
143
144
145
146
# File 'lib/leaderboard.rb', line 141

def rank_member_in(leaderboard_name, member, score, member_data = nil)
  @redis_connection.multi do |transaction|
    transaction.zadd(leaderboard_name, score, member)
    transaction.hset(member_data_key(leaderboard_name), member, member_data) if member_data
  end
end

#rank_members(*members_and_scores) ⇒ Object

Rank an array of members in the leaderboard.

Parameters:

  • members_and_scores (Splat or Array)

    Variable list of members and scores



277
278
279
# File 'lib/leaderboard.rb', line 277

def rank_members(*members_and_scores)
  rank_members_in(@leaderboard_name, *members_and_scores)
end

#rank_members_in(leaderboard_name, *members_and_scores) ⇒ Object

Rank an array of members in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • members_and_scores (Splat or Array)

    Variable list of members and scores



285
286
287
288
289
290
291
292
293
294
295
# File 'lib/leaderboard.rb', line 285

def rank_members_in(leaderboard_name, *members_and_scores)
  if members_and_scores.is_a?(Array)
    members_and_scores.flatten!
  end

  @redis_connection.multi do |transaction|
    members_and_scores.each_slice(2) do |member_and_score|
      transaction.zadd(leaderboard_name, member_and_score[1], member_and_score[0])
    end
  end
end

#ranked_in_list(members, options = {}) ⇒ Object

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

Parameters:

  • members (Array)

    Member names.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the leaderboard.

Returns:

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



955
956
957
# File 'lib/leaderboard.rb', line 955

def ranked_in_list(members, options = {})
  ranked_in_list_in(@leaderboard_name, members, options)
end

#ranked_in_list_in(leaderboard_name, members, options = {}) ⇒ Object

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

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • members (Array)

    Member names.

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the page from the named leaderboard.

Returns:

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



966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
# File 'lib/leaderboard.rb', line 966

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[@rank_key] = responses[index * 2] + 1 rescue nil
      if data[@rank_key] == nil
        next unless leaderboard_options[:include_missing]
      end
      data[@score_key] = responses[index * 2 + 1].to_f if responses[index * 2 + 1]
    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(member) ⇒ Object

Remove a member from the leaderboard.

Parameters:

  • member (String)

    Member name.



300
301
302
# File 'lib/leaderboard.rb', line 300

def remove_member(member)
  remove_member_from(@leaderboard_name, member)
end

#remove_member_data(member) ⇒ Object

Remove the optional member data for a given member in the leaderboard.

Parameters:

  • member (String)

    Member name.



262
263
264
# File 'lib/leaderboard.rb', line 262

def remove_member_data(member)
  remove_member_data_in(@leaderboard_name, member)
end

#remove_member_data_in(leaderboard_name, member) ⇒ Object

Remove the optional member data for a given member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.



270
271
272
# File 'lib/leaderboard.rb', line 270

def remove_member_data_in(leaderboard_name, member)
  @redis_connection.hdel(member_data_key(leaderboard_name), member)
end

#remove_member_from(leaderboard_name, member) ⇒ Object

Remove a member from the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.



308
309
310
311
312
313
# File 'lib/leaderboard.rb', line 308

def remove_member_from(leaderboard_name, member)
  @redis_connection.multi do |transaction|
    transaction.zrem(leaderboard_name, member)
    transaction.hdel(member_data_key(leaderboard_name), member)
  end
end

#remove_members_in_score_range(min_score, max_score) ⇒ Object

Remove members from the leaderboard in a given score range.

Parameters:

  • min_score (float)

    Minimum score.

  • max_score (float)

    Maximum score.



498
499
500
# File 'lib/leaderboard.rb', line 498

def remove_members_in_score_range(min_score, max_score)
  remove_members_in_score_range_in(@leaderboard_name, min_score, max_score)
end

#remove_members_in_score_range_in(leaderboard_name, min_score, max_score) ⇒ Object

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

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • min_score (float)

    Minimum score.

  • max_score (float)

    Maximum score.



507
508
509
# File 'lib/leaderboard.rb', line 507

def remove_members_in_score_range_in(leaderboard_name, min_score, max_score)
  @redis_connection.zremrangebyscore(leaderboard_name, min_score, max_score)
end

#remove_members_outside_rank(rank) ⇒ Object

Remove members from the leaderboard outside a given rank.

Parameters:

  • rank (int)

    The rank (inclusive) which we should keep.

Returns:

  • the total number of members removed.



515
516
517
# File 'lib/leaderboard.rb', line 515

def remove_members_outside_rank(rank)
  remove_members_outside_rank_in(@leaderboard_name, rank)
end

#remove_members_outside_rank_in(leaderboard_name, rank) ⇒ Object

Remove members from the leaderboard outside a given rank.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • rank (int)

    The rank (inclusive) which we should keep.

Returns:

  • the total number of members removed.



524
525
526
527
528
529
530
# File 'lib/leaderboard.rb', line 524

def remove_members_outside_rank_in(leaderboard_name, rank)
  if @reverse
    @redis_connection.zremrangebyrank(leaderboard_name, rank, -1)
  else
    @redis_connection.zremrangebyrank(leaderboard_name, 0, -(rank) - 1)
  end
end

#score_and_rank_for(member) ⇒ Object

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

Parameters:

  • member (String)

    Member name.

Returns:

  • the score and rank for a member in the leaderboard as a Hash.



468
469
470
# File 'lib/leaderboard.rb', line 468

def score_and_rank_for(member)
  score_and_rank_for_in(@leaderboard_name, member)
end

#score_and_rank_for_in(leaderboard_name, member) ⇒ Object

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

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

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



478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
# File 'lib/leaderboard.rb', line 478

def score_and_rank_for_in(leaderboard_name, member)
  responses = @redis_connection.multi do |transaction|
    transaction.zscore(leaderboard_name, member)
    if @reverse
      transaction.zrank(leaderboard_name, member)
    else
      transaction.zrevrank(leaderboard_name, member)
    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

#score_for(member) ⇒ Object

Retrieve the score for a member in the leaderboard.

Parameters:

  • member

    Member name.

Returns:

  • the score for a member in the leaderboard or nil if the member is not in the leaderboard.



429
430
431
# File 'lib/leaderboard.rb', line 429

def score_for(member)
  score_for_in(@leaderboard_name, member)
end

#score_for_in(leaderboard_name, member) ⇒ Object

Retrieve the score for a member in the named leaderboard.

Parameters:

  • leaderboard_name

    Name of the leaderboard.

  • member (String)

    Member name.

Returns:

  • the score for a member in the leaderboard or nil if the member is not in the leaderboard.



439
440
441
442
# File 'lib/leaderboard.rb', line 439

def score_for_in(leaderboard_name, member)
  score = @redis_connection.zscore(leaderboard_name, member)
  score.to_f if score
end

#score_for_percentile(percentile) ⇒ Object

Calculate the score for a given percentile value in the leaderboard.

Parameters:

  • percentile (float)

    Percentile value (0.0 to 100.0 inclusive)



566
567
568
# File 'lib/leaderboard.rb', line 566

def score_for_percentile(percentile)
  score_for_percentile_in(@leaderboard_name, percentile)
end

#score_for_percentile_in(leaderboard_name, percentile) ⇒ Object

Calculate the score for a given percentile value in the named leaderboard.

See www.itl.nist.gov/div898/handbook/prc/section2/prc252.htm for implementation details (there are differing methods for calculating percentile scores that do not fall directly upon a ranked item; we are using the method specified by NIST, i.e. linear interpolation).

Parameters:

  • percentile (float)

    Percentile value (0.0 to 100.0 inclusive)



578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
# File 'lib/leaderboard.rb', line 578

def score_for_percentile_in(leaderboard_name, percentile)
  return nil unless percentile.between?(0, 100)

  total_members = total_members_in(leaderboard_name)
  return nil if total_members < 1

  if @reverse
    percentile = 100 - percentile
  end

  index = (total_members - 1) * (percentile / 100.0)

  scores = @redis_connection.zrange(
    leaderboard_name, index.floor, index.ceil, :with_scores => true
  ).map{ |pair| pair.last }

  if index == index.floor
    scores[0]
  else
    interpolate_fraction = index - index.floor
    scores[0] + interpolate_fraction * (scores[1] - scores[0])
  end
end

#top(number, options = {}) ⇒ Object

Retrieve members from the leaderboard within a range from 1 to the number given.

Parameters:

  • ending_rank (int)

    Ending rank (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • number from the leaderboard that fall within the given rank range.



856
857
858
# File 'lib/leaderboard.rb', line 856

def top(number, options = {})
  top_in(@leaderboard_name, number, options)
end

#top_in(leaderboard_name, number, options = {}) ⇒ Object

Retrieve members from the named leaderboard within a range from 1 to the number given.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • starting_rank (int)

    Starting rank (inclusive).

  • ending_rank (int)

    Ending rank (inclusive).

  • options (Hash) (defaults to: {})

    Options to be used when retrieving the data from the leaderboard.

Returns:

  • members from the leaderboard that fall within the given rank range.



868
869
870
# File 'lib/leaderboard.rb', line 868

def top_in(leaderboard_name, number, options={})
  members_from_rank_range_in(leaderboard_name, 1, number, options)
end

#total_membersObject

Retrieve the total number of members in the leaderboard.

Returns:

  • total number of members in the leaderboard.



318
319
320
# File 'lib/leaderboard.rb', line 318

def total_members
  total_members_in(@leaderboard_name)
end

#total_members_in(leaderboard_name) ⇒ Object

Retrieve the total number of members in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

Returns:

  • the total number of members in the named leaderboard.



327
328
329
# File 'lib/leaderboard.rb', line 327

def total_members_in(leaderboard_name)
  @redis_connection.zcard(leaderboard_name)
end

#total_members_in_score_range(min_score, max_score) ⇒ Object

Retrieve the total members in a given score range from the leaderboard.

Parameters:

  • min_score (float)

    Minimum score.

  • max_score (float)

    Maximum score.

Returns:

  • the total members in a given score range from the leaderboard.



357
358
359
# File 'lib/leaderboard.rb', line 357

def total_members_in_score_range(min_score, max_score)
  total_members_in_score_range_in(@leaderboard_name, min_score, max_score)
end

#total_members_in_score_range_in(leaderboard_name, min_score, max_score) ⇒ Object

Retrieve the total members in a given score range from the named leaderboard.

Parameters:

  • leaderboard_name

    Name of the leaderboard.

  • min_score (float)

    Minimum score.

  • max_score (float)

    Maximum score.

Returns:

  • the total members in a given score range from the named leaderboard.



368
369
370
# File 'lib/leaderboard.rb', line 368

def total_members_in_score_range_in(leaderboard_name, min_score, max_score)
  @redis_connection.zcount(leaderboard_name, min_score, max_score)
end

#total_pages(page_size = nil) ⇒ Object

Retrieve the total number of pages in the leaderboard.

Parameters:

  • page_size (int, nil) (defaults to: nil)

    Page size to be used when calculating the total number of pages.

Returns:

  • the total number of pages in the leaderboard.



336
337
338
# File 'lib/leaderboard.rb', line 336

def total_pages(page_size = nil)
  total_pages_in(@leaderboard_name, page_size)
end

#total_pages_in(leaderboard_name, page_size = nil) ⇒ Object

Retrieve the total number of pages in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • page_size (int, nil) (defaults to: nil)

    Page size to be used when calculating the total number of pages.

Returns:

  • the total number of pages in the named leaderboard.



346
347
348
349
# File 'lib/leaderboard.rb', line 346

def total_pages_in(leaderboard_name, page_size = nil)
  page_size ||= @page_size.to_f
  (total_members_in(leaderboard_name) / page_size.to_f).ceil
end

#total_scoresObject

Sum of scores for all members in leaderboard

Returns:

  • Sum of scores for all members in leaderboard



375
376
377
# File 'lib/leaderboard.rb', line 375

def total_scores
  all_leaders.map{|hash| hash[:score] }.inject(0, :+)
end

#update_member_data(member, member_data) ⇒ Object

Update the optional member data for a given member in the leaderboard.

Parameters:

  • member (String)

    Member name.

  • member_data (String)

    Optional member data.



246
247
248
# File 'lib/leaderboard.rb', line 246

def update_member_data(member, member_data)
  update_member_data_in(@leaderboard_name, member, member_data)
end

#update_member_data_in(leaderboard_name, member, member_data) ⇒ Object

Update the optional member data for a given member in the named leaderboard.

Parameters:

  • leaderboard_name (String)

    Name of the leaderboard.

  • member (String)

    Member name.

  • member_data (String)

    Optional member data.



255
256
257
# File 'lib/leaderboard.rb', line 255

def update_member_data_in(leaderboard_name, member, member_data)
  @redis_connection.hset(member_data_key(leaderboard_name), member, member_data)
end