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 +1 -0
- data/CHANGELOG.markdown +6 -1
- data/README.markdown +61 -12
- data/lib/leaderboard.rb +82 -5
- data/lib/leaderboard/version.rb +1 -1
- data/spec/leaderboard_spec.rb +36 -0
- data/spec/spec_helper.rb +1 -1
- data/spec/version_spec.rb +1 -1
- metadata +26 -11
data/.gitignore
CHANGED
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
|
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`.
|
115
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
121
|
-
|
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.
|
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.
|
data/lib/leaderboard/version.rb
CHANGED
data/spec/leaderboard_spec.rb
CHANGED
@@ -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
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
|
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-
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
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:
|
109
|
+
hash: 248515504144092266
|
95
110
|
requirements: []
|
96
111
|
rubyforge_project: leaderboard
|
97
|
-
rubygems_version: 1.8.
|
112
|
+
rubygems_version: 1.8.23
|
98
113
|
signing_key:
|
99
114
|
specification_version: 3
|
100
115
|
summary: Leaderboards backed by Redis in Ruby
|