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



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

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.



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

def leaderboard_name
  @leaderboard_name
end

#page_sizeObject

Page size to be used when paging through the leaderboard.



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

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)



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

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.



706
707
708
# File 'lib/leaderboard.rb', line 706

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.



718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
# File 'lib/leaderboard.rb', line 718

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.



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

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.



886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
# File 'lib/leaderboard.rb', line 886

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.



355
356
357
# File 'lib/leaderboard.rb', line 355

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.



364
365
366
# File 'lib/leaderboard.rb', line 364

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.



416
417
418
# File 'lib/leaderboard.rb', line 416

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.



426
427
428
# File 'lib/leaderboard.rb', line 426

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

#delete_leaderboardObject

Delete the current leaderboard.



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

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.



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

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.



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

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.



605
606
607
# File 'lib/leaderboard.rb', line 605

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.



627
628
629
# File 'lib/leaderboard.rb', line 627

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.



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

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.



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

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.



989
990
991
# File 'lib/leaderboard.rb', line 989

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.



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

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.



663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
# File 'lib/leaderboard.rb', line 663

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.



845
846
847
# File 'lib/leaderboard.rb', line 845

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.



856
857
858
859
860
861
862
863
864
865
866
867
# File 'lib/leaderboard.rb', line 856

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.



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

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.



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

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.



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

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.



790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
# File 'lib/leaderboard.rb', line 790

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.



744
745
746
# File 'lib/leaderboard.rb', line 744

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.



756
757
758
759
760
761
762
763
764
765
766
767
768
769
# File 'lib/leaderboard.rb', line 756

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.



980
981
982
# File 'lib/leaderboard.rb', line 980

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.



575
576
577
# File 'lib/leaderboard.rb', line 575

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.



586
587
588
589
590
591
592
593
594
595
596
597
598
# File 'lib/leaderboard.rb', line 586

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.



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

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.



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
# File 'lib/leaderboard.rb', line 514

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.



373
374
375
# File 'lib/leaderboard.rb', line 373

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.



383
384
385
386
387
388
389
# File 'lib/leaderboard.rb', line 383

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.



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

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.



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

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.



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

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.



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

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.



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

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



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

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



264
265
266
267
268
269
270
271
272
273
274
# File 'lib/leaderboard.rb', line 264

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.



922
923
924
# File 'lib/leaderboard.rb', line 922

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.



933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
# File 'lib/leaderboard.rb', line 933

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
      data[@score_key] = responses[index * 2 + 1].to_f if responses[index * 2 + 1]
    end

    if leaderboard_options[:with_member_data]
      data[@member_data_key] = 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_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.



279
280
281
# File 'lib/leaderboard.rb', line 279

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.



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

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.



249
250
251
# File 'lib/leaderboard.rb', line 249

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.



287
288
289
290
291
292
# File 'lib/leaderboard.rb', line 287

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.



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

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.



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

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.



482
483
484
# File 'lib/leaderboard.rb', line 482

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.



491
492
493
494
495
496
497
# File 'lib/leaderboard.rb', line 491

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.



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

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.



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
# File 'lib/leaderboard.rb', line 445

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.



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

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.



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

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)



533
534
535
# File 'lib/leaderboard.rb', line 533

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)



545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'lib/leaderboard.rb', line 545

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.



823
824
825
# File 'lib/leaderboard.rb', line 823

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.



835
836
837
# File 'lib/leaderboard.rb', line 835

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.



297
298
299
# File 'lib/leaderboard.rb', line 297

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.



306
307
308
# File 'lib/leaderboard.rb', line 306

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.



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

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.



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

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.



315
316
317
# File 'lib/leaderboard.rb', line 315

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.



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

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.



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

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.



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

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