leaderboard 2.0.6 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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