leaderboard 2.0.6 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore CHANGED
@@ -8,3 +8,4 @@ pkg
8
8
  Gemfile.lock
9
9
  test/db/*
10
10
  *.rdb
11
+ spec/db/*.pid
data/CHANGELOG.markdown CHANGED
@@ -1,8 +1,13 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## leaderboard 2.1.0 (2012-06-11)
4
+
5
+ * Added ability to store optional member data alongside the leaderboard data.
6
+ * `:with_member_data` is now a valid request option when retrieving leader data.
7
+
3
8
  ## leaderboard 2.0.6 (2012-04-26)
4
9
 
5
- * Added accessor for +reverse+ option so that you can set reverse after creating a leaderboard to see results in either highest-to-lowest or lowest-to-highest order.
10
+ * Added accessor for `reverse` option so that you can set reverse after creating a leaderboard to see results in either highest-to-lowest or lowest-to-highest order.
6
11
 
7
12
  ## leaderboard 2.0.5 (2012-03-14)
8
13
 
data/README.markdown CHANGED
@@ -19,10 +19,14 @@ check out the Redis documentation, http://redis.io/documentation.
19
19
 
20
20
  ## Compatibility
21
21
 
22
- The gem has been built and tested under Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3
22
+ The gem has been built and tested under Ruby 1.8.7, Ruby 1.9.2 and Ruby 1.9.3.
23
+
24
+ The gem is compatible with Redis 2.4.x and Redis 2.6.x.
23
25
 
24
26
  ## Usage
25
27
 
28
+ ### Creating a leaderboard
29
+
26
30
  Create a new leaderboard or attach to an existing leaderboard named 'highscores':
27
31
 
28
32
  ```ruby
@@ -39,6 +43,8 @@ If you need to pass in options for Redis, you can do this in the initializer:
39
43
  => #<Leaderboard:0x00000103095200 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://localhost:6379/1 (Redis v2.2.5)>>
40
44
  ```
41
45
 
46
+ ### Defining leaderboard options
47
+
42
48
  The `Leaderboard::DEFAULT_OPTIONS` are as follows:
43
49
 
44
50
  ```ruby
@@ -64,6 +70,18 @@ You can pass in an existing connection to Redis using `:redis_connection` in the
64
70
  => #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=25, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
65
71
  ```
66
72
 
73
+ To use the same connection for multiple leaderboards, reset the options hash before instantiating more leaderboards:
74
+
75
+ ```ruby
76
+ redis = Redis.new
77
+ => #<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>
78
+ redis_options = {:redis_connection => redis}
79
+ => {:redis_connection=>#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>}
80
+ highscore_lb = Leaderboard.new('highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
81
+ redis_options = {:redis_connection => redis}
82
+ other_highscore_lb = Leaderboard.new('other_highscores', Leaderboard::DEFAULT_OPTIONS, redis_options)
83
+ ```
84
+
67
85
  You can set the page size to something other than the default page size (25):
68
86
 
69
87
  ```ruby
@@ -72,7 +90,9 @@ You can set the page size to something other than the default page size (25):
72
90
  highscore_lb
73
91
  => #<Leaderboard:0x000001028791e8 @leaderboard_name="highscores", @page_size=5, @redis_connection=#<Redis client v2.2.2 connected to redis://127.0.0.1:6379/0 (Redis v2.2.5)>>
74
92
  ```
75
-
93
+
94
+ ### Ranking members in the leaderboard
95
+
76
96
  Add members to your leaderboard using `rank_member`:
77
97
 
78
98
  ```ruby
@@ -92,6 +112,30 @@ Get some information about your leaderboard:
92
112
  highscore_lb.total_pages
93
113
  => 1
94
114
  ```
115
+
116
+ The `rank_member` call will also accept an optional hash of member data that could
117
+ be used to store other information about a given member in the leaderboard. This
118
+ may be useful in situations where you are storing member IDs in the leaderboard and
119
+ you want to be able to store a member name for display. Example:
120
+
121
+ ```ruby
122
+ highscore_lb.rank_member('84849292', 1, {'username' => 'member_name'})
123
+ ```
124
+
125
+ You can retrieve, update and remove the optional member data using the
126
+ `member_data_for`, `update_member_data` and `remove_member_data` calls. Example:
127
+
128
+ ```ruby
129
+ highscore_lb.member_data_for('84849292')
130
+ => {"username"=>"member_name"}
131
+
132
+ highscore_lb.update_member_data('84849292', {'last_updated' => Time.now, 'username' => 'updated_member_name'})
133
+ => "OK"
134
+ highscore_lb.member_data_for('84849292')
135
+ => {"username"=>"updated_member_name", "last_updated"=>"2012-06-09 09:11:06 -0400"}
136
+
137
+ highscore_lb.remove_member_data('84849292')
138
+ ```
95
139
 
96
140
  Get some information about a specific member(s) in the leaderboard:
97
141
 
@@ -103,7 +147,9 @@ Get some information about a specific member(s) in the leaderboard:
103
147
  highscore_lb.rank_for('member_10')
104
148
  => 1
105
149
  ```
106
-
150
+
151
+ ### Retrieving members from the leaderboard
152
+
107
153
  Get page 1 in the leaderboard:
108
154
 
109
155
  ```ruby
@@ -111,8 +157,10 @@ Get page 1 in the leaderboard:
111
157
  => [{:member=>"member_10", :rank=>1, :score=>10.0}, {:member=>"member_9", :rank=>2, :score=>9.0}, {:member=>"member_8", :rank=>3, :score=>8.0}, {:member=>"member_7", :rank=>4, :score=>7.0}, {:member=>"member_6", :rank=>5, :score=>6.0}, {:member=>"member_5", :rank=>6, :score=>5.0}, {:member=>"member_4", :rank=>7, :score=>4.0}, {:member=>"member_3", :rank=>8, :score=>3.0}, {:member=>"member_2", :rank=>9, :score=>2.0}, {:member=>"member_1", :rank=>10, :score=>1.0}]
112
158
  ```
113
159
 
114
- You can pass various options to the calls `leaders`, `around_me` and `ranked_in_list`. Valid options are `:with_scores`, `:with_rank`, `:use_zero_index_for_rank` and `:page_size`.
115
- Below is an example of retrieving the first page in the leaderboard without ranks:
160
+ You can pass various options to the calls `leaders`, `around_me` and `ranked_in_list`.
161
+ Valid options are `:with_scores`, `:with_rank`, `:with_member_data`, `:use_zero_index_for_rank`
162
+ and `:page_size`. Below is an example of retrieving the first page in the leaderboard
163
+ without ranks:
116
164
 
117
165
  ```ruby
118
166
  highscore_lb.leaders(1, :with_rank => false)
@@ -151,6 +199,8 @@ Get rank and score for an arbitrary list of members (e.g. friends) from the lead
151
199
  => [{:member=>"member_1", :rank=>56, :score=>1.0}, {:member=>"member_62", :rank=>34, :score=>62.0}, {:member=>"member_67", :rank=>29, :score=>67.0}]
152
200
  ```
153
201
 
202
+ ### Ranking multiple members in a leaderboard at once
203
+
154
204
  Insert multiple data items for members and their associated scores:
155
205
 
156
206
  As a splat:
@@ -170,7 +220,10 @@ Use this method to do bulk insert of data, but be mindful of the amount of data
170
220
  ### Other useful methods
171
221
 
172
222
  ```
173
- delete_leaderboard: Delete the current leaderboard
223
+ delete_leaderboard: Delete the current leaderboard
224
+ member_data_for(member): Retrieve the optional member data for a given member in the leaderboard
225
+ update_member_data(member, member_data): Update the optional member data for a given member in the leaderboard
226
+ remove_member_data(member): Remove the optional member data for a given member in the leaderboard
174
227
  remove_member(member): Remove a member from the leaderboard
175
228
  total_members: Total # of members in the leaderboard
176
229
  total_pages: Total # of pages in the leaderboard given the leaderboard's page_size
@@ -189,7 +242,7 @@ Use this method to do bulk insert of data, but be mindful of the amount of data
189
242
  ```
190
243
 
191
244
  Check the [online documentation](http://rubydoc.info/github/agoragames/leaderboard/master/frames) for more detail on each method.
192
-
245
+
193
246
  ## Performance Metrics
194
247
 
195
248
  10 million sequential scores insert:
@@ -249,7 +302,7 @@ Average time to request an arbitrary page from the leaderboard:
249
302
  => 0.0014615999999999531
250
303
  ```
251
304
 
252
- Bulk insert performance:
305
+ ### Bulk insert performance
253
306
 
254
307
  Ranking individual members:
255
308
 
@@ -278,10 +331,6 @@ end
278
331
  => 22.390000 6.380000 28.770000 ( 31.144027)
279
332
  ```
280
333
 
281
- ## Future Ideas
282
-
283
- * Ideas?
284
-
285
334
  ## Ports
286
335
 
287
336
  The following ports have been made of the leaderboard gem.
data/lib/leaderboard.rb CHANGED
@@ -29,11 +29,13 @@ class Leaderboard
29
29
  # Default options when requesting data from a leaderboard.
30
30
  # +:with_scores+ true: Return scores along with the member names.
31
31
  # +:with_rank+ true: Return ranks along with the member names.
32
+ # +:with_member_data+ false: Return member data along with the member names.
32
33
  # +:use_zero_index_for_rank+ false: If you want to 0-index ranks.
33
34
  # +:page_size+ nil: The default page size will be used.
34
35
  DEFAULT_LEADERBOARD_REQUEST_OPTIONS = {
35
36
  :with_scores => true,
36
37
  :with_rank => true,
38
+ :with_member_data => false,
37
39
  :use_zero_index_for_rank => false,
38
40
  :page_size => nil
39
41
  }
@@ -108,8 +110,9 @@ class Leaderboard
108
110
  #
109
111
  # @param member [String] Member name.
110
112
  # @param score [float] Member score.
111
- def rank_member(member, score)
112
- rank_member_in(@leaderboard_name, member, score)
113
+ # @param member_data [Hash] Optional member data.
114
+ def rank_member(member, score, member_data = nil)
115
+ rank_member_in(@leaderboard_name, member, score, member_data)
113
116
  end
114
117
 
115
118
  # Rank a member in the named leaderboard.
@@ -117,8 +120,65 @@ class Leaderboard
117
120
  # @param leaderboard_name [String] Name of the leaderboard.
118
121
  # @param member [String] Member name.
119
122
  # @param score [float] Member score.
120
- def rank_member_in(leaderboard_name, member, score)
121
- @redis_connection.zadd(leaderboard_name, score, member)
123
+ # @param member_data [Hash] Optional member data.
124
+ def rank_member_in(leaderboard_name, member, score, member_data)
125
+ @redis_connection.multi do |transaction|
126
+ transaction.zadd(leaderboard_name, score, member)
127
+ if member_data
128
+ transaction.hmset(member_data_key(leaderboard_name, member), *member_data.to_a.flatten)
129
+ end
130
+ end
131
+ end
132
+
133
+ # Retrieve the optional member data for a given member in the leaderboard.
134
+ #
135
+ # @param member [String] Member name.
136
+ #
137
+ # @return Hash of optional member data.
138
+ def member_data_for(member)
139
+ member_data_for_in(@leaderboard_name, member)
140
+ end
141
+
142
+ # Retrieve the optional member data for a given member in the named leaderboard.
143
+ #
144
+ # @param leaderboard_name [String] Name of the leaderboard.
145
+ # @param member [String] Member name.
146
+ #
147
+ # @return Hash of optional member data.
148
+ def member_data_for_in(leaderboard_name, member)
149
+ @redis_connection.hgetall(member_data_key(leaderboard_name, member))
150
+ end
151
+
152
+ # Update the optional member data for a given member in the leaderboard.
153
+ #
154
+ # @param member [String] Member name.
155
+ # @param member_data [Hash] Optional member data.
156
+ def update_member_data(member, member_data)
157
+ update_member_data_in(@leaderboard_name, member, member_data)
158
+ end
159
+
160
+ # Update the optional member data for a given member in the named leaderboard.
161
+ #
162
+ # @param leaderboard_name [String] Name of the leaderboard.
163
+ # @param member [String] Member name.
164
+ # @param member_data [Hash] Optional member data.
165
+ def update_member_data_in(leaderboard_name, member, member_data)
166
+ @redis_connection.hmset(member_data_key(leaderboard_name, member), *member_data.to_a.flatten)
167
+ end
168
+
169
+ # Remove the optional member data for a given member in the leaderboard.
170
+ #
171
+ # @param member [String] Member name.
172
+ def remove_member_data(member)
173
+ remove_member_data_in(@leaderboard_name, member)
174
+ end
175
+
176
+ # Remove the optional member data for a given member in the named leaderboard.
177
+ #
178
+ # @param leaderboard_name [String] Name of the leaderboard.
179
+ # @param member [String] Member name.
180
+ def remove_member_data_in(leaderboard_name, member)
181
+ @redis_connection.del(member_data_key(leaderboard_name, member))
122
182
  end
123
183
 
124
184
  # Rank an array of members in the leaderboard.
@@ -156,7 +216,10 @@ class Leaderboard
156
216
  # @param leaderboard_name [String] Name of the leaderboard.
157
217
  # @param member [String] Member name.
158
218
  def remove_member_from(leaderboard_name, member)
159
- @redis_connection.zrem(leaderboard_name, member)
219
+ @redis_connection.multi do |transaction|
220
+ transaction.zrem(leaderboard_name, member)
221
+ transaction.del(member_data_key(leaderboard_name, member))
222
+ end
160
223
  end
161
224
 
162
225
  # Retrieve the total number of members in the leaderboard.
@@ -574,6 +637,10 @@ class Leaderboard
574
637
  end
575
638
  end
576
639
  end
640
+
641
+ if leaderboard_options[:with_member_data]
642
+ data[:member_data] = member_data_for_in(leaderboard_name, member)
643
+ end
577
644
 
578
645
  ranks_for_members << data
579
646
  end
@@ -601,6 +668,16 @@ class Leaderboard
601
668
 
602
669
  private
603
670
 
671
+ # Key for retrieving optional member data.
672
+ #
673
+ # @param leaderboard_name [String] Name of the leaderboard.
674
+ # @param member [String] Member name.
675
+ #
676
+ # @return a key in the form of +leaderboard_name:data:member+
677
+ def member_data_key(leaderboard_name, member)
678
+ "#{leaderboard_name}:member_data:#{member}"
679
+ end
680
+
604
681
  # Validate and return the page size. Returns the +DEFAULT_PAGE_SIZE+ if the page size is less than 1.
605
682
  #
606
683
  # @param page_size [int] Page size.
@@ -1,4 +1,4 @@
1
1
  class Leaderboard
2
2
  # Leaderboard version
3
- VERSION = '2.0.6'.freeze
3
+ VERSION = '2.1.0'.freeze
4
4
  end
@@ -139,6 +139,42 @@ describe 'Leaderboard' do
139
139
  leaders[24].should == member_1
140
140
  end
141
141
 
142
+ it 'should allow you to retrieve leaders with extra data' do
143
+ rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE)
144
+
145
+ @leaderboard.total_members.should be(Leaderboard::DEFAULT_PAGE_SIZE)
146
+ leaders = @leaderboard.leaders(1, {:with_scores => false, :with_rank => false, :with_member_data => true})
147
+
148
+ member_25 = {:member => 'member_25', :member_data => { "member_name" => "Leaderboard member 25" }}
149
+ leaders[0].should == member_25
150
+
151
+ member_1 = {:member => 'member_1', :member_data => { "member_name" => "Leaderboard member 1" }}
152
+ leaders[24].should == member_1
153
+ end
154
+
155
+ it 'should allow you to retrieve optional member data' do
156
+ @leaderboard.rank_member('member_id', 1, {'username' => 'member_name', 'other_data_key' => 'other_data_value'})
157
+
158
+ @leaderboard.member_data_for('unknown_member').should == {}
159
+ @leaderboard.member_data_for('member_id').should == {'username' => 'member_name', 'other_data_key' => 'other_data_value'}
160
+ end
161
+
162
+ it 'should allow you to update optional member data' do
163
+ @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})
164
+
165
+ @leaderboard.member_data_for('member_id').should == {'username' => 'member_name'}
166
+ @leaderboard.update_member_data('member_id', {'other_data_key' => 'other_data_value'})
167
+ @leaderboard.member_data_for('member_id').should == {'username' => 'member_name', 'other_data_key' => 'other_data_value'}
168
+ end
169
+
170
+ it 'should allow you to remove optional member data' do
171
+ @leaderboard.rank_member('member_id', 1, {'username' => 'member_name'})
172
+
173
+ @leaderboard.member_data_for('member_id').should == {'username' => 'member_name'}
174
+ @leaderboard.remove_member_data('member_id')
175
+ @leaderboard.member_data_for('member_id').should == {}
176
+ end
177
+
142
178
  it 'should allow you to call leaders with various options that respect the defaults for the options not passed in' do
143
179
  rank_members_in_leaderboard(Leaderboard::DEFAULT_PAGE_SIZE + 1)
144
180
 
data/spec/spec_helper.rb CHANGED
@@ -9,7 +9,7 @@ RSpec.configure do |config|
9
9
  # @param members_to_add [int] Number of members to add to the leaderboard.
10
10
  def rank_members_in_leaderboard(members_to_add = 5)
11
11
  1.upto(members_to_add) do |index|
12
- @leaderboard.rank_member("member_#{index}", index)
12
+ @leaderboard.rank_member("member_#{index}", index, { :member_name => "Leaderboard member #{index}" })
13
13
  end
14
14
  end
15
15
  end
data/spec/version_spec.rb CHANGED
@@ -2,6 +2,6 @@ require 'spec_helper'
2
2
 
3
3
  describe 'Leaderboard::VERSION' do
4
4
  it 'should be the correct version' do
5
- Leaderboard::VERSION.should == '2.0.6'
5
+ Leaderboard::VERSION.should == '2.1.0'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leaderboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.6
4
+ version: 2.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-27 00:00:00.000000000 Z
12
+ date: 2012-06-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &70173076945220 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70173076945220
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rake
27
- requirement: &70173076944740 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ! '>='
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: '0'
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70173076944740
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: rspec
38
- requirement: &70173076944140 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,7 +53,12 @@ dependencies:
43
53
  version: '0'
44
54
  type: :development
45
55
  prerelease: false
46
- version_requirements: *70173076944140
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  description: Leaderboards backed by Redis in Ruby
48
63
  email:
49
64
  - dczarnecki@agoragames.com
@@ -82,7 +97,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
97
  version: '0'
83
98
  segments:
84
99
  - 0
85
- hash: -4420304645092000909
100
+ hash: 248515504144092266
86
101
  required_rubygems_version: !ruby/object:Gem::Requirement
87
102
  none: false
88
103
  requirements:
@@ -91,10 +106,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
106
  version: '0'
92
107
  segments:
93
108
  - 0
94
- hash: -4420304645092000909
109
+ hash: 248515504144092266
95
110
  requirements: []
96
111
  rubyforge_project: leaderboard
97
- rubygems_version: 1.8.10
112
+ rubygems_version: 1.8.23
98
113
  signing_key:
99
114
  specification_version: 3
100
115
  summary: Leaderboards backed by Redis in Ruby