boffin 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ 0.2.0
2
+
3
+ * Support for Ruby 1.8.7 thanks to Justin Giancola
4
+
1
5
  0.1.0
2
6
 
3
7
  * Initial public release
data/Gemfile CHANGED
@@ -4,5 +4,5 @@ gemspec
4
4
  group :development do
5
5
  gem 'rake'
6
6
  gem 'redcarpet'
7
- gem 'yard', git: 'https://github.com/lsegal/yard.git'
7
+ gem 'yard', :git => 'https://github.com/lsegal/yard.git'
8
8
  end
data/README.md CHANGED
@@ -15,6 +15,7 @@ weighted combinations of different types of hits.
15
15
  Resources
16
16
  ---------
17
17
 
18
+ * [Documentation](http://rubydoc.info/github/heycarsten/boffin/master/frames)
18
19
  * [Source Code](https://github.com/heycarsten/boffin)
19
20
  * [Issue Tracker](https://github.com/heycarsten/boffin/issues)
20
21
  * [Test Suite](https://github.com/heycarsten/boffin/tree/master/spec)
data/Rakefile CHANGED
@@ -11,4 +11,4 @@ end
11
11
  require 'yard'
12
12
  YARD::Rake::YardocTask.new
13
13
 
14
- task default: :spec
14
+ task :default => :spec
@@ -11,13 +11,14 @@ Gem::Specification.new do |s|
11
11
  s.summary = 'Hit tracking library for Ruby using Redis'
12
12
  s.has_rdoc = 'yard'
13
13
  s.rubyforge_project = 'boffin'
14
- s.files = `git ls-files`.split(?\n)
15
- s.test_files = `git ls-files -- spec/*`.split(?\n)
14
+ s.files = `git ls-files`.split(/\n/)
15
+ s.test_files = `git ls-files -- spec/*`.split(/\n/)
16
16
  s.require_paths = ['lib']
17
17
 
18
18
  s.add_dependency 'redis', '>= 2.2'
19
19
  s.add_development_dependency 'rspec', '~> 2.6'
20
20
  s.add_development_dependency 'timecop'
21
+ s.add_development_dependency 'bundler', '>= 1.0.14'
21
22
 
22
23
  s.description = <<-END
23
24
  Boffin is a library for tracking hits to things in your Ruby application. Things
@@ -1,4 +1,3 @@
1
- require 'base64'
2
1
  require 'date'
3
2
  require 'time'
4
3
  require 'redis'
@@ -24,9 +23,9 @@ module Boffin
24
23
 
25
24
  # The way Time should be formatted for each interval type
26
25
  INTERVAL_FORMATS = {
27
- hours: '%F-%H',
28
- days: '%F',
29
- months: '%Y-%m' }
26
+ :hours => '%F-%H',
27
+ :days => '%F',
28
+ :months => '%Y-%m' }
30
29
 
31
30
  # Different interval types
32
31
  INTERVAL_TYPES = INTERVAL_FORMATS.keys
@@ -91,6 +91,8 @@ module Boffin
91
91
  # Perform union for hit counts over the last _n_ months.
92
92
  # @example Return IDs of most viewed and liked listings in the past 6 days with scores
93
93
  # @tracker.top({ views: 1, likes: 1 }, counts: true, days: 6)
94
+ # @example Return IDS of most viewed and liked listings in the past 6 days with scores (Alternate syntax)
95
+ # @tracker.top([[:views, 1], [:likes, 1]], counts: true, days: 6)
94
96
  # @example Return IDs of most viewed listings in the past 12 hours
95
97
  # @tracker.top(:views, hours: 12)
96
98
  # @note
@@ -178,7 +180,7 @@ module Boffin
178
180
  weights.keys.each { |t| union(ks, t, unit, size, opts) }
179
181
  keys = weights.keys.map { |t| ks.hits_union(t, unit, size) }
180
182
  zfetch(ks.hits_union_multi(weights, unit, size), keys, {
181
- weights: weights.values
183
+ :weights => weights.values
182
184
  }.merge(opts))
183
185
  end
184
186
 
@@ -194,8 +196,8 @@ module Boffin
194
196
  # @see #zrange
195
197
  def zfetch(storkey, keys, opts = {})
196
198
  zrangeopts = {
197
- counts: opts.delete(:counts),
198
- order: (opts.delete(:order) || :desc).to_sym }
199
+ :counts => opts.delete(:counts),
200
+ :order => (opts.delete(:order) || :desc).to_sym }
199
201
  if redis.zcard(storkey) == 0
200
202
  redis.zunionstore(storkey, keys, opts)
201
203
  redis.expire(storkey, @config.cache_expire_secs)
@@ -213,7 +215,7 @@ module Boffin
213
215
  # option is `true` it returns an array of pairs where the first value is
214
216
  # the member, and the second value is the member's score.
215
217
  def zrange(key, opts)
216
- args = [key, 0, -1, opts[:counts] ? { withscores: true } : {}]
218
+ args = [key, 0, -1, opts[:counts] ? { :withscores => true } : {}]
217
219
  result = case opts[:order]
218
220
  when :asc then redis.zrange(*args)
219
221
  when :desc then redis.zrevrange(*args)
@@ -13,9 +13,9 @@ module Boffin
13
13
 
14
14
  # Number of seconds for a single value of each unit
15
15
  SECONDS_IN_UNIT = {
16
- hours: SECONDS_IN_HOUR,
17
- days: SECONDS_IN_DAY,
18
- months: SECONDS_IN_MONTH
16
+ :hours => SECONDS_IN_HOUR,
17
+ :days => SECONDS_IN_DAY,
18
+ :months => SECONDS_IN_MONTH
19
19
  }
20
20
 
21
21
  module_function
@@ -45,6 +45,18 @@ module Boffin
45
45
  obj.respond_to?(:empty?) ? obj.empty? : !obj
46
46
  end
47
47
 
48
+ # @param [Object] obj any Ruby object
49
+ # @return [true, false]
50
+ # `true` if the provided object responds to :id, other than it's
51
+ # internal object identifier
52
+ # `false` if the object does not respond to :id
53
+ def respond_to_id?(obj)
54
+ # NOTE: this feels like a hack. I'm sure there is a more elegant way
55
+ # to determine whether the :id method is the built in Object#id but
56
+ # I can't think of it
57
+ obj.respond_to?(:id) and obj.id != obj.object_id
58
+ end
59
+
48
60
  # Pulls time interval information from a hash of options.
49
61
  # @example
50
62
  # extract_time_unit(this: 'is ignored', days: 6, so_is: 'this')
@@ -134,13 +146,13 @@ module Boffin
134
146
  # generated value will be Base64 encoded.
135
147
  # @return [String]
136
148
  def object_as_identifier(obj, opts = {})
137
- if obj.respond_to?(:as_member) || obj.respond_to?(:id)
149
+ if obj.respond_to?(:as_member) || respond_to_id?(obj)
138
150
  ''.tap do |s|
139
151
  s << "#{underscore(obj.class)}:" if opts[:namespace]
140
152
  s << (obj.respond_to?(:as_member) ? obj.as_member : obj.id).to_s
141
153
  end
142
154
  else
143
- opts[:encode] ? Base64.strict_encode64(obj.to_s) : obj.to_s
155
+ opts[:encode] ? [obj.to_s].pack("m0").chomp : obj.to_s
144
156
  end
145
157
  end
146
158
 
@@ -157,14 +169,14 @@ module Boffin
157
169
  # @return [String] A string that can be used as a member in {Keyspace#hits}.
158
170
  # @see #object_as_identifier
159
171
  def object_as_session_identifier(obj)
160
- object_as_identifier(obj, namespace: true)
172
+ object_as_identifier(obj, :namespace => true)
161
173
  end
162
174
 
163
175
  # @param [#as_member, #id, #to_s] obj
164
176
  # @return [String] A string that can be used as part of a Redis key
165
177
  # @see #object_as_identifier
166
178
  def object_as_key(obj)
167
- object_as_identifier(obj, encode: true)
179
+ object_as_identifier(obj, :encode => true)
168
180
  end
169
181
 
170
182
  end
@@ -1,4 +1,4 @@
1
1
  module Boffin
2
2
  # Version of this Boffin release
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
@@ -28,14 +28,14 @@ describe Boffin::Config do
28
28
  end
29
29
 
30
30
  it 'can be sent a hash' do
31
- conf = Boffin::Config.new(namespace: 'hello')
31
+ conf = Boffin::Config.new(:namespace => 'hello')
32
32
  conf.namespace.should == 'hello'
33
33
  end
34
34
  end
35
35
 
36
36
  describe '#merge' do
37
37
  it 'copies the existing instance' do
38
- newconf = subject.merge(namespace: 'carsten')
38
+ newconf = subject.merge(:namespace => 'carsten')
39
39
  newconf.namespace.should == 'carsten'
40
40
  subject.namespace.should == 'boffin'
41
41
  end
@@ -16,9 +16,9 @@ describe Boffin::Hit, '::new' do
16
16
  it 'stores hit data under the appropriate keys' do
17
17
  Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
18
18
  [:hours, :days, :months].each do |interval|
19
- @tracker.top(:tests, interval => 1, counts: true).
19
+ @tracker.top(:tests, interval => 1, :counts => true).
20
20
  should == [['1', 1]]
21
- @tracker.top(:tests, interval => 1, counts: true, unique: true).
21
+ @tracker.top(:tests, interval => 1, :counts => true, :unique => true).
22
22
  should == [['1', 1]]
23
23
  end
24
24
  @tracker.hit_count(:tests, @ditty).should == 1
@@ -29,9 +29,9 @@ describe Boffin::Hit, '::new' do
29
29
  Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
30
30
  Boffin::Hit.new(@tracker, :tests, @ditty, [nil, @user])
31
31
  [:hours, :days, :months].each do |interval|
32
- @tracker.top(:tests, interval => 1, counts: true).
32
+ @tracker.top(:tests, interval => 1, :counts => true).
33
33
  should == [['1', 2]]
34
- @tracker.top(:tests, interval => 1, counts: true, unique: true).
34
+ @tracker.top(:tests, interval => 1, :counts => true, :unique => true).
35
35
  should == [['1', 1]]
36
36
  end
37
37
  @tracker.hit_count_for_session_id(:tests, @ditty, @user).should == 2
@@ -42,7 +42,13 @@ describe Boffin::Keyspace do
42
42
 
43
43
  describe '#hits_union_multi' do
44
44
  specify do
45
- @ks.hits_union_multi({ views: 1, likes: 3 }, :days, 5).
45
+ weighted_hit_types =
46
+ if RUBY_VERSION < '1.9'
47
+ [[:views, 1], [:likes, 3]]
48
+ else
49
+ { :views => 1, :likes => 3 }
50
+ end
51
+ @ks.hits_union_multi(weighted_hit_types, :days, 5).
46
52
  should == 'b:mock_ditty:views_1_likes_3:hits:current.days_5'
47
53
  end
48
54
  end
@@ -21,7 +21,7 @@ describe Boffin::Trackable do
21
21
  end
22
22
 
23
23
  it 'delegates ::top_ids to the Tracker instance' do
24
- MockTrackableInjected.top_ids(:views, days: 1).should == ['1']
24
+ MockTrackableInjected.top_ids(:views, :days => 1).should == ['1']
25
25
  end
26
26
 
27
27
  it 'delegates #hit_count to the Tracker instance' do
@@ -55,14 +55,14 @@ describe Boffin::Tracker do
55
55
 
56
56
  describe '#hit' do
57
57
  it 'throws an error if the hit type is not in the list' do
58
- -> { @tracker.hit(:view, @instance1) }.
58
+ lambda { @tracker.hit(:view, @instance1) }.
59
59
  should raise_error Boffin::UndefinedHitTypeError
60
60
  end
61
61
  end
62
62
 
63
63
  describe '#hit_count' do
64
64
  it 'throws an error if the hit type is not in the list' do
65
- -> { @tracker.hit_count(:view, @instance1) }.
65
+ lambda { @tracker.hit_count(:view, @instance1) }.
66
66
  should raise_error Boffin::UndefinedHitTypeError
67
67
  end
68
68
 
@@ -77,7 +77,7 @@ describe Boffin::Tracker do
77
77
 
78
78
  describe '#uhit_count' do
79
79
  it 'throws an error if the hit type is not in the list' do
80
- -> { @tracker.uhit_count(:view, @instance1) }.
80
+ lambda { @tracker.uhit_count(:view, @instance1) }.
81
81
  should raise_error Boffin::UndefinedHitTypeError
82
82
  end
83
83
 
@@ -92,7 +92,7 @@ describe Boffin::Tracker do
92
92
 
93
93
  describe '#hit_count_for_session_id' do
94
94
  it 'throws an error if the hit type is not in the list' do
95
- -> { @tracker.hit_count_for_session_id(:view, @instance1, 'sess.1') }.
95
+ lambda { @tracker.hit_count_for_session_id(:view, @instance1, 'sess.1') }.
96
96
  should raise_error Boffin::UndefinedHitTypeError
97
97
  end
98
98
 
@@ -107,32 +107,32 @@ describe Boffin::Tracker do
107
107
 
108
108
  describe '#top' do
109
109
  it 'throws an error if passed hit type is invalid' do
110
- -> { @tracker.top(:view, days: 3) }.
110
+ lambda { @tracker.top(:view, :days => 3) }.
111
111
  should raise_error Boffin::UndefinedHitTypeError
112
112
  end
113
113
 
114
114
  it 'throws an error if passed weights with hit type that is invalid' do
115
- -> { @tracker.top({ view: 1 }, days: 3) }.
115
+ lambda { @tracker.top({ :view => 1 }, :days => 3) }.
116
116
  should raise_error Boffin::UndefinedHitTypeError
117
117
  end
118
118
 
119
119
  it 'returns ids ordered by hit counts of weighted totals' do
120
- ids = @tracker.top({ views: 1, likes: 2 }, days: 3)
120
+ ids = @tracker.top({ :views => 1, :likes => 2 }, :days => 3)
121
121
  ids.should == ['300', '200', '100']
122
122
  end
123
123
 
124
124
  it 'returns ids ordered by total counts of a specific hit type' do
125
- ids = @tracker.top(:views, days: 3)
125
+ ids = @tracker.top(:views, :days => 3)
126
126
  ids.should == ['300', '100', '200']
127
127
  end
128
128
 
129
129
  it 'returns ids in ascending order when passed { order: "asc" } as an option' do
130
- ids = @tracker.top(:views, days: 3, order: 'asc')
130
+ ids = @tracker.top(:views, :days => 3, :order => 'asc')
131
131
  ids.should == ['200', '100', '300']
132
132
  end
133
133
 
134
134
  it 'returns ids and counts when passed { counts: true } as an option' do
135
- ids = @tracker.top(:views, days: 3, counts: true)
135
+ ids = @tracker.top(:views, :days => 3, :counts => true)
136
136
  ids.should == [
137
137
  ['300', 12],
138
138
  ['100', 8],
@@ -141,7 +141,7 @@ describe Boffin::Tracker do
141
141
  end
142
142
 
143
143
  it 'returns ids based on unique hit data when passed { unique: true } as an option' do
144
- ids = @tracker.top(:views, days: 3, counts: true, unique: true)
144
+ ids = @tracker.top(:views, :days => 3, :counts => true, :unique => true)
145
145
  ids.should == [
146
146
  ['300', 7],
147
147
  ['200', 5],
@@ -159,4 +159,4 @@ describe Boffin::Tracker do
159
159
  @tracker.keyspace(true).unique_namespace?.should be_true
160
160
  end
161
161
  end
162
- end
162
+ end
@@ -44,12 +44,12 @@ describe Boffin::Utils do
44
44
  end
45
45
 
46
46
  describe '::extract_time_unit' do
47
- specify { subject.extract_time_unit(hours: 6).should == [:hours, 6] }
48
- specify { subject.extract_time_unit(days: 2).should == [:days, 2] }
49
- specify { subject.extract_time_unit(months: 3).should == [:months, 3] }
47
+ specify { subject.extract_time_unit(:hours => 6).should == [:hours, 6] }
48
+ specify { subject.extract_time_unit(:days => 2).should == [:days, 2] }
49
+ specify { subject.extract_time_unit(:months => 3).should == [:months, 3] }
50
50
 
51
51
  it 'throws an error if no time unit pair exists in the hash' do
52
- lambda { subject.extract_time_unit(fun: 'times') }.
52
+ lambda { subject.extract_time_unit(:fun => 'times') }.
53
53
  should raise_error ArgumentError
54
54
  end
55
55
  end
@@ -57,12 +57,12 @@ describe Boffin::Utils do
57
57
  describe '::time_ago' do
58
58
  before { @time = Time.local(2011, 2, 15, 12) }
59
59
 
60
- specify { subject.time_ago(@time, hours: 6).should == Time.local(2011, 2, 15, 6) }
61
- specify { subject.time_ago(@time, days: 5).should == Time.local(2011, 2, 10, 12) }
62
- specify { subject.time_ago(@time, months: 1).should == Time.local(2011, 1, 16, 12) } # A "month" is 30 days
60
+ specify { subject.time_ago(@time, :hours => 6).should == Time.local(2011, 2, 15, 6) }
61
+ specify { subject.time_ago(@time, :days => 5).should == Time.local(2011, 2, 10, 12) }
62
+ specify { subject.time_ago(@time, :months => 1).should == Time.local(2011, 1, 16, 12) } # A "month" is 30 days
63
63
 
64
64
  it 'throws an error if no time unit pair exists in the hash' do
65
- lambda { subject.time_ago(@time, fun: 'fail') }.
65
+ lambda { subject.time_ago(@time, :fun => 'fail') }.
66
66
  should raise_error ArgumentError
67
67
  end
68
68
  end
@@ -70,16 +70,16 @@ describe Boffin::Utils do
70
70
  describe '::time_ago_range' do
71
71
  before { @time = Time.local(2011, 2, 15, 12) }
72
72
 
73
- specify { subject.time_ago_range(@time, hours: 6).size.should == 6 }
74
- specify { subject.time_ago_range(@time, months: 1).size.should == 1 }
73
+ specify { subject.time_ago_range(@time, :hours => 6).size.should == 6 }
74
+ specify { subject.time_ago_range(@time, :months => 1).size.should == 1 }
75
75
 
76
76
  specify do
77
- subject.time_ago_range(@time, days: 2).
77
+ subject.time_ago_range(@time, :days => 2).
78
78
  should == [Time.local(2011, 2, 14, 12), Time.local(2011, 2, 15, 12)]
79
79
  end
80
80
 
81
81
  specify do
82
- subject.time_ago_range(@time, days: 3).
82
+ subject.time_ago_range(@time, :days => 3).
83
83
  should == [
84
84
  Time.local(2011, 2, 13, 12),
85
85
  Time.local(2011, 2, 14, 12),
@@ -88,13 +88,13 @@ describe Boffin::Utils do
88
88
  end
89
89
 
90
90
  it 'ranges from n days away upto @time' do
91
- times = subject.time_ago_range(@time, days: 4)
91
+ times = subject.time_ago_range(@time, :days => 4)
92
92
  times.first.should == Time.local(2011, 2, 12, 12)
93
93
  times.last.should == @time
94
94
  end
95
95
 
96
96
  it 'throws an error if no time unit pair exists in the hash' do
97
- lambda { subject.time_ago_range(@time, fun: 'crash') }.
97
+ lambda { subject.time_ago_range(@time, :fun => 'crash') }.
98
98
  should raise_error ArgumentError
99
99
  end
100
100
  end
@@ -32,7 +32,7 @@ describe Boffin do
32
32
  end
33
33
 
34
34
  it 'accepts a hash' do
35
- Boffin.config(namespace: 'trendy')
35
+ Boffin.config(:namespace => 'trendy')
36
36
  Boffin.config.namespace.should == 'trendy'
37
37
  end
38
38
 
@@ -6,7 +6,7 @@ require 'timecop'
6
6
 
7
7
  $redis = if ENV['DEBUG']
8
8
  require 'logger'
9
- Redis.connect(logger: Logger.new(STDERR))
9
+ Redis.connect(:logger => Logger.new(STDERR))
10
10
  else
11
11
  Redis.connect
12
12
  end
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.1.0
4
+ version: 0.2.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-08-26 00:00:00.000000000Z
12
+ date: 2011-09-08 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &70146172825080 !ruby/object:Gem::Requirement
16
+ requirement: &70347564151120 !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: *70146172825080
24
+ version_requirements: *70347564151120
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &70146172824200 !ruby/object:Gem::Requirement
27
+ requirement: &70347564150300 !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: *70146172824200
35
+ version_requirements: *70347564150300
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: timecop
38
- requirement: &70146172823600 !ruby/object:Gem::Requirement
38
+ requirement: &70347564149720 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,18 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70146172823600
46
+ version_requirements: *70347564149720
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: &70347564149100 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: 1.0.14
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70347564149100
47
58
  description: ! 'Boffin is a library for tracking hits to things in your Ruby application.
48
59
  Things
49
60
 
@@ -100,7 +111,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
100
111
  version: '0'
101
112
  segments:
102
113
  - 0
103
- hash: -3925281016385501386
114
+ hash: 3845958252376638612
104
115
  required_rubygems_version: !ruby/object:Gem::Requirement
105
116
  none: false
106
117
  requirements:
@@ -109,7 +120,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
120
  version: '0'
110
121
  segments:
111
122
  - 0
112
- hash: -3925281016385501386
123
+ hash: 3845958252376638612
113
124
  requirements: []
114
125
  rubyforge_project: boffin
115
126
  rubygems_version: 1.8.6