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.1'.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.



746
747
748
# File 'lib/leaderboard.rb', line 746

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.



758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
# File 'lib/leaderboard.rb', line 758

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.



915
916
917
# File 'lib/leaderboard.rb', line 915

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.



926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
# File 'lib/leaderboard.rb', line 926

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.



391
392
393
# File 'lib/leaderboard.rb', line 391

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.



401
402
403
404
405
406
# File 'lib/leaderboard.rb', line 401

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.



456
457
458
# File 'lib/leaderboard.rb', line 456

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.



466
467
468
# File 'lib/leaderboard.rb', line 466

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.



645
646
647
# File 'lib/leaderboard.rb', line 645

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.



667
668
669
# File 'lib/leaderboard.rb', line 667

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.



677
678
679
680
681
682
# File 'lib/leaderboard.rb', line 677

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.



655
656
657
658
659
660
# File 'lib/leaderboard.rb', line 655

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.



1035
1036
1037
# File 'lib/leaderboard.rb', line 1035

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.



690
691
692
# File 'lib/leaderboard.rb', line 690

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.



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
731
732
733
734
735
736
737
# File 'lib/leaderboard.rb', line 703

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.



885
886
887
# File 'lib/leaderboard.rb', line 885

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.



896
897
898
899
900
901
902
903
904
905
906
907
# File 'lib/leaderboard.rb', line 896

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.



818
819
820
# File 'lib/leaderboard.rb', line 818

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.



830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
# File 'lib/leaderboard.rb', line 830

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.



784
785
786
# File 'lib/leaderboard.rb', line 784

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.



796
797
798
799
800
801
802
803
804
805
806
807
808
809
# File 'lib/leaderboard.rb', line 796

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.



1026
1027
1028
# File 'lib/leaderboard.rb', line 1026

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.



615
616
617
# File 'lib/leaderboard.rb', line 615

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.



626
627
628
629
630
631
632
633
634
635
636
637
638
# File 'lib/leaderboard.rb', line 626

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.



544
545
546
# File 'lib/leaderboard.rb', line 544

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.



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
# File 'lib/leaderboard.rb', line 554

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.



413
414
415
# File 'lib/leaderboard.rb', line 413

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.



423
424
425
426
427
428
429
# File 'lib/leaderboard.rb', line 423

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.



962
963
964
# File 'lib/leaderboard.rb', line 962

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.



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
1013
1014
1015
1016
1017
1018
1019
# File 'lib/leaderboard.rb', line 973

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.



505
506
507
# File 'lib/leaderboard.rb', line 505

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.



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

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.



522
523
524
# File 'lib/leaderboard.rb', line 522

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.



531
532
533
534
535
536
537
# File 'lib/leaderboard.rb', line 531

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.



475
476
477
# File 'lib/leaderboard.rb', line 475

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.



485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
# File 'lib/leaderboard.rb', line 485

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.



436
437
438
# File 'lib/leaderboard.rb', line 436

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.



446
447
448
449
# File 'lib/leaderboard.rb', line 446

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)



573
574
575
# File 'lib/leaderboard.rb', line 573

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)



585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
# File 'lib/leaderboard.rb', line 585

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.



863
864
865
# File 'lib/leaderboard.rb', line 863

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.



875
876
877
# File 'lib/leaderboard.rb', line 875

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
  total_scores_in(@leaderboard_name)
end

#total_scores_in(leaderboard_name) ⇒ Object

Sum of scores for all members in the named leaderboard

Returns:

  • Sum of scores for all members in named leaderboard



382
383
384
# File 'lib/leaderboard.rb', line 382

def total_scores_in(leaderboard_name)
  all_leaders_from(leaderboard_name).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