boffin 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +10 -2
- data/README.md +52 -18
- data/lib/boffin/hit.rb +17 -6
- data/lib/boffin/trackable.rb +2 -2
- data/lib/boffin/tracker.rb +5 -3
- data/lib/boffin/utils.rb +5 -1
- data/lib/boffin/version.rb +1 -1
- data/spec/boffin/hit_spec.rb +13 -3
- data/spec/boffin/trackable_spec.rb +2 -2
- data/spec/boffin/tracker_spec.rb +31 -31
- metadata +12 -12
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
0.
|
1
|
+
**0.3.0**
|
2
|
+
|
3
|
+
* `Hit` can now accept a custom increment, this allows values such as cents to
|
4
|
+
be tracked. (Justin Giancola)
|
5
|
+
* Unique qualities are now passed in as a member in an options hash for
|
6
|
+
`Tracker#hit`, this deprecates the unique qualities argument and allows for
|
7
|
+
other options such as `:increment` to be passed as well. (Justin Giancola)
|
8
|
+
|
9
|
+
**0.2.0**
|
2
10
|
|
3
11
|
* Support for Ruby 1.8.7 thanks to Justin Giancola
|
4
12
|
|
5
|
-
0.1.0
|
13
|
+
**0.1.0**
|
6
14
|
|
7
15
|
* Initial public release
|
data/README.md
CHANGED
@@ -51,7 +51,7 @@ Most of Boffin's default configuration options are quite reasonable, but they
|
|
51
51
|
are easy to change if required:
|
52
52
|
|
53
53
|
```ruby
|
54
|
-
Boffin.
|
54
|
+
Boffin.config do |c|
|
55
55
|
c.redis = MyApp.redis # Redis.connect by default
|
56
56
|
c.namespace = "tracking:#{MyApp.env}" # Redis key namespace
|
57
57
|
c.hours_window_secs = 3.days # Time to maintain hourly interval data
|
@@ -128,7 +128,7 @@ identify hits from particular users or sessions:
|
|
128
128
|
```ruby
|
129
129
|
get '/listings/:id' do
|
130
130
|
@listing = Listing[params[:id]]
|
131
|
-
@listing.hit(:views, [current_user, session[:id]])
|
131
|
+
@listing.hit(:views, unique: [current_user, session[:id]])
|
132
132
|
haml :'listings/show'
|
133
133
|
end
|
134
134
|
```
|
@@ -144,7 +144,7 @@ we want to hit an instance, so let's create a helper:
|
|
144
144
|
```ruby
|
145
145
|
helpers do
|
146
146
|
def hit(trackable, type)
|
147
|
-
trackable.hit(type, [current_user, session[:id]])
|
147
|
+
trackable.hit(type, unique: [current_user, session[:id]])
|
148
148
|
end
|
149
149
|
end
|
150
150
|
```
|
@@ -156,7 +156,7 @@ applicable to a Rails application as well:
|
|
156
156
|
class ApplicationController < ActionController::Base
|
157
157
|
protected
|
158
158
|
def hit(trackable, type)
|
159
|
-
trackable.hit(type, [current_user, session[:session_id]])
|
159
|
+
trackable.hit(type, unique: [current_user, session[:session_id]])
|
160
160
|
end
|
161
161
|
end
|
162
162
|
```
|
@@ -238,14 +238,14 @@ track your friends' favourite and least favourite colours:
|
|
238
238
|
```ruby
|
239
239
|
@tracker = Boffin::Tracker.new(:colours, [:faves, :unfaves])
|
240
240
|
|
241
|
-
@tracker.hit(:faves, 'red', ['lena'])
|
242
|
-
@tracker.hit(:unfaves, 'blue', ['lena'])
|
243
|
-
@tracker.hit(:faves, 'green', ['soren'])
|
244
|
-
@tracker.hit(:unfaves, 'red', ['soren'])
|
245
|
-
@tracker.hit(:faves, 'green', ['jens'])
|
246
|
-
@tracker.hit(:unfaves, 'yellow', ['jens'])
|
241
|
+
@tracker.hit(:faves, 'red', unique: ['lena'])
|
242
|
+
@tracker.hit(:unfaves, 'blue', unique: ['lena'])
|
243
|
+
@tracker.hit(:faves, 'green', unique: ['soren'])
|
244
|
+
@tracker.hit(:unfaves, 'red', unique: ['soren'])
|
245
|
+
@tracker.hit(:faves, 'green', unique: ['jens'])
|
246
|
+
@tracker.hit(:unfaves, 'yellow', unique: ['jens'])
|
247
247
|
|
248
|
-
@tracker.top(:faves,
|
248
|
+
@tracker.top(:faves, days: 1)
|
249
249
|
```
|
250
250
|
|
251
251
|
Or, perhaps you'd like to clone Twitter? Using Boffin, all the work is
|
@@ -263,7 +263,7 @@ end
|
|
263
263
|
post '/tweets' do
|
264
264
|
@tweet = Tweet.create(params[:tweet])
|
265
265
|
if @tweet.valid?
|
266
|
-
@tweet.words.each { WordsTracker.hit(:tweets, word) }
|
266
|
+
@tweet.words.each { |word| WordsTracker.hit(:tweets, word) }
|
267
267
|
redirect to("/tweets/#{@tweet.id}")
|
268
268
|
else
|
269
269
|
haml :'tweets/form'
|
@@ -277,18 +277,52 @@ end
|
|
277
277
|
```
|
278
278
|
_*This is a joke._
|
279
279
|
|
280
|
-
|
281
|
-
|
280
|
+
|
281
|
+
Custom increments
|
282
|
+
-----------------
|
283
|
+
|
284
|
+
For some applications you might want to track something beyond simple hits.
|
285
|
+
To accomodate this you can specify a custom increment to any hit you record.
|
286
|
+
For example, if you run an ecommerce site it might be nice to know which
|
287
|
+
products are your bestsellers:
|
288
|
+
|
289
|
+
```ruby
|
290
|
+
class Product < ActiveRecord::Base
|
291
|
+
Boffin.track(self, [:sales])
|
292
|
+
end
|
293
|
+
|
294
|
+
class Order < ActiveRecord::Base
|
295
|
+
after_create :track_sales
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
def track_sales
|
300
|
+
line_items.each do |line_item|
|
301
|
+
product = line_item.product
|
302
|
+
amount = product.amount.cents * line_item.quantity
|
303
|
+
product.hit :sales, increment: amount
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
Then, when you want to check on your sales over the last day:
|
310
|
+
|
311
|
+
```ruby
|
312
|
+
Product.top_ids(:sales, hours: 24, counts: true)
|
313
|
+
```
|
314
|
+
|
315
|
+
The Future™
|
316
|
+
-----------------
|
282
317
|
|
283
318
|
* Ability to hit multiple instances in one command
|
284
319
|
* Ability to get hit-count range for an instance
|
285
320
|
* Some nice examples with pretty things
|
286
|
-
* ORM adapters for niceness and tighter integration
|
321
|
+
* Maybe ORM adapters for niceness and tighter integration
|
287
322
|
* Examples of how to turn IDs back into instances
|
288
323
|
* Reporting DSL thingy
|
289
|
-
* Web framework integration (helpers for tracking hits)
|
290
|
-
* Ability to
|
291
|
-
* Ability to unhit an instance (if a model instance is destroyed for example)
|
324
|
+
* Web framework integration (helpers for tracking hits, console type ditty.)
|
325
|
+
* Ability to union on unique hits and raw hits
|
292
326
|
|
293
327
|
FAQ
|
294
328
|
---
|
data/lib/boffin/hit.rb
CHANGED
@@ -12,10 +12,21 @@ module Boffin
|
|
12
12
|
# @param [Object] instance
|
13
13
|
# The instance that is being hit, any object that responds to
|
14
14
|
# `#to_member`, `#id`, or `#to_s`
|
15
|
-
# @param [
|
16
|
-
#
|
17
|
-
# identifier for hit uniqueness
|
18
|
-
|
15
|
+
# @param [Hash] options
|
16
|
+
# @option options [Array] :unique ([]) An array of which the first
|
17
|
+
# object is used to generate a session identifier for hit uniqueness
|
18
|
+
# @option options [Fixnum] :increment (1) The hit increment
|
19
|
+
def initialize(tracker, type, instance, opts = {})
|
20
|
+
if opts.is_a?(Array)
|
21
|
+
warn "This constructor is deprecated and will soon be unavailable\n" \
|
22
|
+
"please create Hits with: \n" \
|
23
|
+
"Hit.new(@tracker, :type, @instance, :unique => [1,2,3,4])\n"
|
24
|
+
uniquenesses = opts
|
25
|
+
@increment = 1
|
26
|
+
else
|
27
|
+
uniquenesses = opts.delete(:unique) || []
|
28
|
+
@increment = opts.delete(:increment) || 1
|
29
|
+
end
|
19
30
|
@now = Time.now
|
20
31
|
@sessid = Utils.uniquenesses_as_session_identifier(uniquenesses)
|
21
32
|
@type = type
|
@@ -54,7 +65,7 @@ module Boffin
|
|
54
65
|
# `true` if this hit is unique, `false` if it has been made before by the
|
55
66
|
# same session identifer.
|
56
67
|
def track_hit
|
57
|
-
redis.
|
68
|
+
redis.incrby(keyspace.hit_count(@type, @instance), @increment)
|
58
69
|
redis.zincrby(keyspace.hits(@type, @instance), 1, @sessid) == '1'
|
59
70
|
end
|
60
71
|
|
@@ -75,7 +86,7 @@ module Boffin
|
|
75
86
|
# Changes keyspace scope to keys under .uniq
|
76
87
|
def set_window_interval(interval, uniq = false)
|
77
88
|
key = keyspace(uniq).hits_time_window(@type, interval, @now)
|
78
|
-
redis.zincrby(key,
|
89
|
+
redis.zincrby(key, @increment, @member)
|
79
90
|
redis.expire(key, @tracker.config.send("#{interval}_window_secs"))
|
80
91
|
end
|
81
92
|
|
data/lib/boffin/trackable.rb
CHANGED
data/lib/boffin/tracker.rb
CHANGED
@@ -25,14 +25,16 @@ module Boffin
|
|
25
25
|
|
26
26
|
# @param [Symbol] hit_type
|
27
27
|
# @param [#as_member, #id, #to_s] instance
|
28
|
-
# @param [
|
28
|
+
# @param [Hash] options
|
29
|
+
# @option options [Array] :unique ([]) uniquenesses
|
30
|
+
# @option options [Fixnum] :increment (1) hit increment
|
29
31
|
# @return [Hit]
|
30
32
|
# @raise Boffin::UndefinedHitTypeError
|
31
33
|
# Raised if a list of hit types is available and the provided hit type is
|
32
34
|
# not in the list.
|
33
|
-
def hit(hit_type, instance,
|
35
|
+
def hit(hit_type, instance, opts = {})
|
34
36
|
validate_hit_type(hit_type)
|
35
|
-
Hit.new(self, hit_type, instance,
|
37
|
+
Hit.new(self, hit_type, instance, opts)
|
36
38
|
end
|
37
39
|
|
38
40
|
# @param [Symbol] hit_type
|
data/lib/boffin/utils.rb
CHANGED
@@ -54,7 +54,11 @@ module Boffin
|
|
54
54
|
# NOTE: this feels like a hack. I'm sure there is a more elegant way
|
55
55
|
# to determine whether the :id method is the built in Object#id but
|
56
56
|
# I can't think of it
|
57
|
-
|
57
|
+
if RUBY_VERSION < "1.9"
|
58
|
+
obj.respond_to?(:id) and obj.id != obj.object_id
|
59
|
+
else
|
60
|
+
obj.respond_to?(:id)
|
61
|
+
end
|
58
62
|
end
|
59
63
|
|
60
64
|
# Pulls time interval information from a hash of options.
|
data/lib/boffin/version.rb
CHANGED
data/spec/boffin/hit_spec.rb
CHANGED
@@ -14,7 +14,7 @@ describe Boffin::Hit, '::new' do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'stores hit data under the appropriate keys' do
|
17
|
-
Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
|
17
|
+
Boffin::Hit.new(@tracker, :tests, @ditty, :unique => [nil, @user])
|
18
18
|
[:hours, :days, :months].each do |interval|
|
19
19
|
@tracker.top(:tests, interval => 1, :counts => true).
|
20
20
|
should == [['1', 1]]
|
@@ -26,8 +26,8 @@ describe Boffin::Hit, '::new' do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'does not store data under unique keys if the hit is not unique' do
|
29
|
-
Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
|
30
|
-
Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
|
29
|
+
Boffin::Hit.new(@tracker, :tests, @ditty, :unique => [nil, @user])
|
30
|
+
Boffin::Hit.new(@tracker, :tests, @ditty, :unique => [nil, @user])
|
31
31
|
[:hours, :days, :months].each do |interval|
|
32
32
|
@tracker.top(:tests, interval => 1, :counts => true).
|
33
33
|
should == [['1', 2]]
|
@@ -39,4 +39,14 @@ describe Boffin::Hit, '::new' do
|
|
39
39
|
@tracker.uhit_count(:tests, @ditty).should == 1
|
40
40
|
end
|
41
41
|
|
42
|
+
it 'allows arbitrary hit increments' do
|
43
|
+
Boffin::Hit.new(@tracker, :tests, @ditty, :unique => [nil, @user], :increment => 5)
|
44
|
+
Boffin::Hit.new(@tracker, :tests, @ditty, :unique => [nil, @user], :increment => 5)
|
45
|
+
[:hours, :days, :months].each do |interval|
|
46
|
+
@tracker.top(:tests, interval => 1, :counts => true).
|
47
|
+
should == [['1', 10]]
|
48
|
+
@tracker.top(:tests, interval => 1, :counts => true, :unique => true).
|
49
|
+
should == [['1', 5]]
|
50
|
+
end
|
51
|
+
end
|
42
52
|
end
|
@@ -4,8 +4,8 @@ describe Boffin::Trackable do
|
|
4
4
|
before :all do
|
5
5
|
SpecHelper.flush_keyspace!
|
6
6
|
@mock = MockTrackableInjected.new(1)
|
7
|
-
@mock.hit(:views, ['sess.1'])
|
8
|
-
@mock.hit(:views, ['sess.1'])
|
7
|
+
@mock.hit(:views, :unique => ['sess.1'])
|
8
|
+
@mock.hit(:views, :unique => ['sess.1'])
|
9
9
|
end
|
10
10
|
|
11
11
|
it 'can be included' do
|
data/spec/boffin/tracker_spec.rb
CHANGED
@@ -14,43 +14,43 @@ describe Boffin::Tracker do
|
|
14
14
|
|
15
15
|
Timecop.freeze(@date - 2) do
|
16
16
|
@tracker.hit(:views, @instance3)
|
17
|
-
@tracker.hit(:likes, @instance3, [@user1])
|
18
|
-
@tracker.hit(:views, @instance3, ['sess.1'])
|
19
|
-
@tracker.hit(:views, @instance3, ['sess.2'])
|
20
|
-
@tracker.hit(:views, @instance3, [@user2])
|
21
|
-
@tracker.hit(:likes, @instance1, [nil, nil])
|
22
|
-
@tracker.hit(:views, @instance3, ['sess.4'])
|
23
|
-
@tracker.hit(:views, @instance3, [@user1])
|
24
|
-
@tracker.hit(:views, @instance2, [@user2])
|
25
|
-
@tracker.hit(:views, @instance3, [@user2])
|
17
|
+
@tracker.hit(:likes, @instance3, :unique => [@user1])
|
18
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.1'])
|
19
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.2'])
|
20
|
+
@tracker.hit(:views, @instance3, :unique => [@user2])
|
21
|
+
@tracker.hit(:likes, @instance1, :unique => [nil, nil])
|
22
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.4'])
|
23
|
+
@tracker.hit(:views, @instance3, :unique => [@user1])
|
24
|
+
@tracker.hit(:views, @instance2, :unique => [@user2])
|
25
|
+
@tracker.hit(:views, @instance3, :unique => [@user2])
|
26
26
|
@tracker.hit(:likes, @instance3)
|
27
|
-
@tracker.hit(:views, @instance3, ['sess.1'])
|
27
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.1'])
|
28
28
|
@tracker.hit(:views, @instance3)
|
29
|
-
@tracker.hit(:likes, @instance3, [@user1])
|
30
|
-
@tracker.hit(:views, @instance3, ['sess.1'])
|
29
|
+
@tracker.hit(:likes, @instance3, :unique => [@user1])
|
30
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.1'], :increment => 2)
|
31
31
|
end
|
32
32
|
|
33
33
|
Timecop.freeze(@date - 1) do
|
34
34
|
@tracker.hit(:views, @instance1)
|
35
|
-
@tracker.hit(:likes, @instance2, [@user1])
|
36
|
-
@tracker.hit(:views, @instance2, ['sess.4'])
|
37
|
-
@tracker.hit(:views, @instance2, [nil, @user1])
|
38
|
-
@tracker.hit(:views, @instance2, ['sess.3'])
|
39
|
-
@tracker.hit(:views, @instance1, ['sess.3'])
|
40
|
-
@tracker.hit(:views, @instance1, [@user1])
|
41
|
-
@tracker.hit(:views, @instance2, ['sess.2'])
|
42
|
-
@tracker.hit(:views, @instance1, [@user1])
|
43
|
-
@tracker.hit(:views, @instance1, [@user2])
|
44
|
-
end
|
45
|
-
|
46
|
-
@tracker.hit(:views, @instance3, ['sess.2'])
|
47
|
-
@tracker.hit(:views, @instance2, [@user2])
|
35
|
+
@tracker.hit(:likes, @instance2, :unique => [@user1])
|
36
|
+
@tracker.hit(:views, @instance2, :unique => ['sess.4'])
|
37
|
+
@tracker.hit(:views, @instance2, :unique => [nil, @user1])
|
38
|
+
@tracker.hit(:views, @instance2, :unique => ['sess.3'])
|
39
|
+
@tracker.hit(:views, @instance1, :unique => ['sess.3'])
|
40
|
+
@tracker.hit(:views, @instance1, :unique => [@user1])
|
41
|
+
@tracker.hit(:views, @instance2, :unique => ['sess.2'])
|
42
|
+
@tracker.hit(:views, @instance1, :unique => [@user1])
|
43
|
+
@tracker.hit(:views, @instance1, :unique => [@user2])
|
44
|
+
end
|
45
|
+
|
46
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.2'])
|
47
|
+
@tracker.hit(:views, @instance2, :unique => [@user2])
|
48
48
|
@tracker.hit(:likes, @instance2)
|
49
|
-
@tracker.hit(:views, @instance2, [@user1])
|
50
|
-
@tracker.hit(:views, @instance1, ['sess.4'])
|
51
|
-
@tracker.hit(:views, @instance3, ['sess.3'])
|
52
|
-
@tracker.hit(:views, @instance1, [@user1])
|
53
|
-
@tracker.hit(:views, @instance1, [@user2])
|
49
|
+
@tracker.hit(:views, @instance2, :unique => [@user1])
|
50
|
+
@tracker.hit(:views, @instance1, :unique => ['sess.4'])
|
51
|
+
@tracker.hit(:views, @instance3, :unique => ['sess.3'])
|
52
|
+
@tracker.hit(:views, @instance1, :unique => [@user1])
|
53
|
+
@tracker.hit(:views, @instance1, :unique => [@user2])
|
54
54
|
end
|
55
55
|
|
56
56
|
describe '#hit' do
|
@@ -134,7 +134,7 @@ describe Boffin::Tracker do
|
|
134
134
|
it 'returns ids and counts when passed { counts: true } as an option' do
|
135
135
|
ids = @tracker.top(:views, :days => 3, :counts => true)
|
136
136
|
ids.should == [
|
137
|
-
['300',
|
137
|
+
['300', 13],
|
138
138
|
['100', 8],
|
139
139
|
['200', 7]
|
140
140
|
]
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: boffin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.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: 2011-09-
|
12
|
+
date: 2011-09-12 00:00:00.000000000Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
|
-
requirement: &
|
16
|
+
requirement: &70276273568860 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '2.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70276273568860
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70276273568280 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '2.6'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70276273568280
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: timecop
|
38
|
-
requirement: &
|
38
|
+
requirement: &70276273567900 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70276273567900
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: bundler
|
49
|
-
requirement: &
|
49
|
+
requirement: &70276273567340 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: 1.0.14
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70276273567340
|
58
58
|
description: ! 'Boffin is a library for tracking hits to things in your Ruby application.
|
59
59
|
Things
|
60
60
|
|
@@ -111,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
111
111
|
version: '0'
|
112
112
|
segments:
|
113
113
|
- 0
|
114
|
-
hash:
|
114
|
+
hash: -2879744131493119363
|
115
115
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
116
|
none: false
|
117
117
|
requirements:
|
@@ -120,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
120
120
|
version: '0'
|
121
121
|
segments:
|
122
122
|
- 0
|
123
|
-
hash:
|
123
|
+
hash: -2879744131493119363
|
124
124
|
requirements: []
|
125
125
|
rubyforge_project: boffin
|
126
126
|
rubygems_version: 1.8.6
|