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 CHANGED
@@ -1,7 +1,15 @@
1
- 0.2.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.configure do |c|
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, months: 1)
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
- TODO
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&trade;
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 blend unique hits with raw hits
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 [Array] uniquenesses
16
- # An array of which the first object is used to generate a session
17
- # identifier for hit uniqueness
18
- def initialize(tracker, type, instance, uniquenesses = [])
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.incr(keyspace.hit_count(@type, @instance))
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, 1, @member)
89
+ redis.zincrby(key, @increment, @member)
79
90
  redis.expire(key, @tracker.config.send("#{interval}_window_secs"))
80
91
  end
81
92
 
@@ -40,8 +40,8 @@ module Boffin
40
40
 
41
41
  # @see Tracker#hit
42
42
  # @return [Hit]
43
- def hit(type, uniquenesses = [])
44
- self.class.boffin.hit(type, self, uniquenesses)
43
+ def hit(type, opts = {})
44
+ self.class.boffin.hit(type, self, opts)
45
45
  end
46
46
 
47
47
  # @see Tracker#hit_count
@@ -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 [Array] uniquenesses
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, uniquenesses = [])
35
+ def hit(hit_type, instance, opts = {})
34
36
  validate_hit_type(hit_type)
35
- Hit.new(self, hit_type, instance, uniquenesses)
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
- obj.respond_to?(:id) and obj.id != obj.object_id
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.
@@ -1,4 +1,4 @@
1
1
  module Boffin
2
2
  # Version of this Boffin release
3
- VERSION = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
@@ -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
@@ -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', 12],
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.2.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-08 00:00:00.000000000Z
12
+ date: 2011-09-12 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &70347564151120 !ruby/object:Gem::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: *70347564151120
24
+ version_requirements: *70276273568860
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70347564150300 !ruby/object:Gem::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: *70347564150300
35
+ version_requirements: *70276273568280
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: timecop
38
- requirement: &70347564149720 !ruby/object:Gem::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: *70347564149720
46
+ version_requirements: *70276273567900
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
- requirement: &70347564149100 !ruby/object:Gem::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: *70347564149100
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: 3845958252376638612
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: 3845958252376638612
123
+ hash: -2879744131493119363
124
124
  requirements: []
125
125
  rubyforge_project: boffin
126
126
  rubygems_version: 1.8.6