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
}
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
}
VERSION =
'3.4.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.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/leaderboard.rb', line 62

def initialize(leaderboard_name, options = DEFAULT_OPTIONS, redis_options = DEFAULT_REDIS_OPTIONS)
  @leaderboard_name = leaderboard_name

  @reverse   = options[:reverse]
  @page_size = options[:page_size]
  if @page_size.nil? || @page_size < 1
    @page_size = DEFAULT_PAGE_SIZE
  end

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



42
43
44
# File 'lib/leaderboard.rb', line 42

def leaderboard_name
  @leaderboard_name
end

#page_sizeObject

Page size to be used when paging through the leaderboard.



45
46
47
# File 'lib/leaderboard.rb', line 45

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)



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

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.



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

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.



703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
# File 'lib/leaderboard.rb', line 703

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.



838
839
840
# File 'lib/leaderboard.rb', line 838

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.



849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
# File 'lib/leaderboard.rb', line 849

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) ⇒ 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.



340
341
342
# File 'lib/leaderboard.rb', line 340

def change_score_for(member, delta)
  change_score_for_member_in(@leaderboard_name, member, delta)
end

#change_score_for_member_in(leaderboard_name, member, delta) ⇒ 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.



349
350
351
# File 'lib/leaderboard.rb', line 349

def change_score_for_member_in(leaderboard_name, member, delta)
  @redis_connection.zincrby(leaderboard_name, delta, member)
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.



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

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.



411
412
413
# File 'lib/leaderboard.rb', line 411

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

#delete_leaderboardObject

Delete the current leaderboard.



96
97
98
# File 'lib/leaderboard.rb', line 96

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.



103
104
105
106
107
108
# File 'lib/leaderboard.rb', line 103

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.



91
92
93
# File 'lib/leaderboard.rb', line 91

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.



590
591
592
# File 'lib/leaderboard.rb', line 590

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.



612
613
614
# File 'lib/leaderboard.rb', line 612

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.



622
623
624
625
626
627
# File 'lib/leaderboard.rb', line 622

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.



600
601
602
603
604
605
# File 'lib/leaderboard.rb', line 600

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.



952
953
954
# File 'lib/leaderboard.rb', line 952

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.



635
636
637
# File 'lib/leaderboard.rb', line 635

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.



648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
# File 'lib/leaderboard.rb', line 648

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.



808
809
810
# File 'lib/leaderboard.rb', line 808

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.



819
820
821
822
823
824
825
826
827
828
829
830
# File 'lib/leaderboard.rb', line 819

def member_at_in(leaderboard_name, position, options = {})
  if 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.



192
193
194
# File 'lib/leaderboard.rb', line 192

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.



202
203
204
# File 'lib/leaderboard.rb', line 202

def member_data_for_in(leaderboard_name, member)
  @redis_connection.hget(member_data_key(leaderboard_name), member)
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.



763
764
765
# File 'lib/leaderboard.rb', line 763

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.



775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
# File 'lib/leaderboard.rb', line 775

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.



729
730
731
# File 'lib/leaderboard.rb', line 729

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.



741
742
743
744
745
746
747
748
749
750
751
752
753
754
# File 'lib/leaderboard.rb', line 741

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.



943
944
945
# File 'lib/leaderboard.rb', line 943

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.



560
561
562
# File 'lib/leaderboard.rb', line 560

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.



571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/leaderboard.rb', line 571

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.



489
490
491
# File 'lib/leaderboard.rb', line 489

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.



499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
# File 'lib/leaderboard.rb', line 499

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.



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

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.



368
369
370
371
372
373
374
# File 'lib/leaderboard.rb', line 368

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.



115
116
117
# File 'lib/leaderboard.rb', line 115

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.



138
139
140
141
142
143
144
145
# File 'lib/leaderboard.rb', line 138

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.



160
161
162
# File 'lib/leaderboard.rb', line 160

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.



178
179
180
181
182
183
184
185
# File 'lib/leaderboard.rb', line 178

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.



125
126
127
128
129
130
# File 'lib/leaderboard.rb', line 125

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



241
242
243
# File 'lib/leaderboard.rb', line 241

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



249
250
251
252
253
254
255
256
257
258
259
# File 'lib/leaderboard.rb', line 249

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.



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

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.



896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
# File 'lib/leaderboard.rb', line 896

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] = member
    unless leaderboard_options[:members_only]
      data[:rank] = responses[index * 2] + 1 rescue nil
      data[:score] = responses[index * 2 + 1].to_f if responses[index * 2 + 1]
    end

    if leaderboard_options[:with_member_data]
      data[:member_data] = member_data_for_in(leaderboard_name, member)
    end

    ranks_for_members << data
  end

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

  ranks_for_members
end

#remove_member(member) ⇒ Object

Remove a member from the leaderboard.

Parameters:

  • member (String)

    Member name.



264
265
266
# File 'lib/leaderboard.rb', line 264

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.



226
227
228
# File 'lib/leaderboard.rb', line 226

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.



234
235
236
# File 'lib/leaderboard.rb', line 234

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.



272
273
274
275
276
277
# File 'lib/leaderboard.rb', line 272

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.



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

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.



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

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.



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

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.



476
477
478
479
480
481
482
# File 'lib/leaderboard.rb', line 476

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.



420
421
422
# File 'lib/leaderboard.rb', line 420

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.



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
# File 'lib/leaderboard.rb', line 430

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 => member, :score => responses[0], :rank => 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.



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

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.



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

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)



518
519
520
# File 'lib/leaderboard.rb', line 518

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)



530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
# File 'lib/leaderboard.rb', line 530

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

#total_membersObject

Retrieve the total number of members in the leaderboard.

Returns:

  • total number of members in the leaderboard.



282
283
284
# File 'lib/leaderboard.rb', line 282

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.



291
292
293
# File 'lib/leaderboard.rb', line 291

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.



321
322
323
# File 'lib/leaderboard.rb', line 321

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.



332
333
334
# File 'lib/leaderboard.rb', line 332

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.



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

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.



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

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

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



210
211
212
# File 'lib/leaderboard.rb', line 210

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.



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

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