Skip to content

Commit

Permalink
refactor for great justice
Browse files Browse the repository at this point in the history
  • Loading branch information
blahed committed Jan 24, 2013
1 parent 30f7d7b commit 0eee213
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 102 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Or install it yourself as:
Von.increment('something') # bumps 'something'
Von.increment('something:else') # bumps 'something' (total only) and 'something:else'
Von.increment('foo') # bumps 'foo'

# Retrieving counts
Von.count('something') # retrieve total count
Von.count('something', :daily) # retrieve daily count

## Contributing

Expand Down
52 changes: 8 additions & 44 deletions lib/von.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'von/config'
require 'von/counter'
require 'von/period'
require 'von/version'

Expand All @@ -14,53 +15,16 @@ def self.config
Config
end

def self.increment(counter)
periods = config.counter_periods(counter)
def self.increment(field)
counter = Counter.new(field)

connection.hincrby("#{config.namespace}:#{counter}", 'all', 1)

periods.each do |period, length|
period = Period.new(counter, period)
connection.hincrby(period.hash, period.key, 1)
connection.rpush(period.list, period.key) unless connection.lrange(period.list, 0, -1).include?(period.key)

if connection.llen(period.list) > length
expired_counter = connection.lpop(period.list)
connection.hdel(period.hash, expired_counter)
end
end

increment_parents(counter) if counter =~ /:[^:]+\z/
end

def self.increment_parents(counter)
parents = counter.sub(/:[^:]+\z/, '')

until parents.empty? do
connection.hincrby("#{config.namespace}:#{parents}", 'all', 1)
parents.sub!(/:?[^:]+\z/, '')
end
counter.increment
counter.periods.each { |key, period| counter.increment(period) }
end

def self.count(counter, period = nil)
if period.nil?
connection.hget("#{config.namespace}:#{counter}", 'all')
else
count = []
_period = Period.new(counter, period)
length = config.counter_periods(counter)[period.to_sym]
now = DateTime.now.beginning_of_hour

length.times do
this_period = now.strftime(_period.format)
count.unshift(this_period)
now = _period.time_unit == :hour ? now.ago(3600) : now.send(:"prev_#{_period.time_unit}")
end

counter = "#{config.namespace}:#{counter}:#{period}"
keys = connection.hgetall(counter)
count.map { |date| { date => keys.fetch(date, 0) }}
end
def self.count(field, period = nil)
counter = Counter.new(field)
counter.count(period)
end

end
24 changes: 5 additions & 19 deletions lib/von/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module Config

def reset!
@counter_options = {}

# all keys are prefixed with this namespace
self.namespace = 'von'
# 2013
self.yearly_format = '%Y'
Expand All @@ -27,26 +27,12 @@ def reset!
self.hourly_format = '%Y-%m-%d %H:00'
end

def counter(key, options = {})
@counter_options[key] = options
end

def counter_options(counter)
@counter_options[counter] ||= {}
def counter(field, options = {})
@counter_options[field.to_sym] = options
end

def counter_periods(counter)
counter_options(counter).select do |k|
Period::AVAILABLE_PERIODS.include?(k)
end
end

def from_hash!(attributes_hash = {})
return if attributes_hash.empty?

attributes_hash.each do |key, value|
send(:"#{key}=", value)
end
def counter_options(field)
@counter_options[field.to_sym] ||= {}
end

def configure(&block)
Expand Down
77 changes: 77 additions & 0 deletions lib/von/counter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
module Von
class Counter
CHILD_REGEX = /:[^:]+\z/
PARENT_REGEX = /:?[^:]+\z/

def initialize(field)
@field = field.to_sym
end

def options
@options ||= Von.config.counter_options(@field)
end

def periods
@periods ||= options.select { |k|
Period::AVAILABLE_PERIODS.include?(k)
}.inject({}) { |h, (p, l)| h[p] = Period.new(@field, p, l); h }
end

def hash
@hash ||= "#{Von.config.namespace}:#{@field}"
end

def increment(field = 'total')
if field.is_a?(Period)
increment_period(field)
else
Von.connection.hincrby(hash, field, 1)
increment_parents
end
end

def increment_period(period)
Von.connection.hincrby(period.hash, period.field, 1)
unless Von.connection.lrange(period.list, 0, -1).include?(period.field)
Von.connection.rpush(period.list, period.field)
end

if Von.connection.llen(period.list) > period.length
expired_counter = Von.connection.lpop(period.list)
Von.connection.hdel(period.hash, expired_counter)
end
end

def increment_parents
field = @field.to_s
return if field !~ CHILD_REGEX

parents = field.sub(CHILD_REGEX, '')

until parents.empty? do
Von.connection.hincrby("#{Von.config.namespace}:#{parents}", 'total', 1)
parents.sub!(PARENT_REGEX, '')
end
end

def count(period)
if period.nil?
Von.connection.hget(hash, 'total')
else
_count = []
_period = periods[period]
now = DateTime.now.beginning_of_hour

_period.length.times do
this_period = now.strftime(_period.format)
_count.unshift(this_period)
now = _period.hours? ? now.ago(3600) : now.send(:"prev_#{_period.time_unit}")
end

keys = Von.connection.hgetall("#{hash}:#{period}")
_count.map { |date| { date => keys.fetch(date, 0) }}
end
end

end
end
19 changes: 13 additions & 6 deletions lib/von/period.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@ module Von
class Period
AVAILABLE_PERIODS = [ :hourly, :daily, :weekly, :monthly, :yearly ]

def initialize(counter, period)
attr_reader :length

def initialize(counter, period, length)
@counter = counter
@period = period
@length = length
@now = Time.now
end

def time_unit
case @period
@time_unit ||= case @period
when :hourly
:hour
when :daily
Expand All @@ -23,19 +26,23 @@ def time_unit
end
end

def hours?
@period == :hourly
end

def format
Von.config.send(:"#{@period}_format")
@format ||= Von.config.send(:"#{@period}_format")
end

def hash
"#{Von.config.namespace}:#{@counter}:#{@period}"
@hash ||= "#{Von.config.namespace}:#{@counter}:#{@period}"
end

def list
"#{Von.config.namespace}:lists:#{@counter}:#{@period}"
@list ||= "#{Von.config.namespace}:lists:#{@counter}:#{@period}"
end

def key
def field
@now.strftime(format)
end
end
Expand Down
23 changes: 0 additions & 23 deletions test/config_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@
@config.hourly_format.must_equal '%Y-%m-%d %H:00'
end

it 'initializes a config with a hash' do
attributes = {
:namespace => 'foo',
}

@config.from_hash!(attributes)

attributes.each do |key, value|
@config.send(key).must_equal value
end
end

it 'initializes a config and overloads it with a block' do
@config.configure do
self.namespace = 'something'
Expand All @@ -45,15 +33,4 @@
@config.counter_options('bar').must_equal options
end

it 'stores counter options per key and retrieves them' do
options = { :monthly => 3, :total => false }

@config.configure do
counter 'bar', options
end

@config.namespace.must_equal 'von'
@config.counter_periods('bar').must_equal :monthly => 3
end

end
Empty file added test/period_test.tb
Empty file.
20 changes: 10 additions & 10 deletions test/von_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@
Von.expects(:connection).returns(connection).at_least_once
end

it "increments the all counter if given a single key" do
it "increments the total counter if given a single key" do
Von.increment('foo')

@store.has_key?('von:foo').must_equal true
@store['von:foo']['all'].must_equal 1
@store['von:foo']['total'].must_equal 1

Von.increment('foo')
@store['von:foo']['all'].must_equal 2
@store['von:foo']['total'].must_equal 2
end

it "increments the all counter for a key and it's parent keys" do
it "increments the total counter for a key and it's parent keys" do
Von.increment('foo:bar')

@store.has_key?('von:foo').must_equal true
@store['von:foo']['all'].must_equal 1
@store['von:foo']['total'].must_equal 1
@store.has_key?('von:foo:bar').must_equal true
@store['von:foo:bar']['all'].must_equal 1
@store['von:foo:bar']['total'].must_equal 1

Von.increment('foo:bar')
@store['von:foo']['all'].must_equal 2
@store['von:foo:bar']['all'].must_equal 2
@store['von:foo']['total'].must_equal 2
@store['von:foo:bar']['total'].must_equal 2
end

it "increments a month counter" do
Expand All @@ -45,7 +45,7 @@

@store.has_key?('von:foo').must_equal true
@store.has_key?('von:foo:monthly').must_equal true
@store['von:foo']['all'].must_equal 2
@store['von:foo']['total'].must_equal 2
@store['von:foo:monthly']['2013-01'].must_equal 2
@store['von:lists:foo:monthly'].size.must_equal 1
end
Expand All @@ -61,7 +61,7 @@

@store.has_key?('von:foo').must_equal true
@store.has_key?('von:foo:monthly').must_equal true
@store['von:foo']['all'].must_equal 2
@store['von:foo']['total'].must_equal 2
@store['von:foo:monthly'].has_key?('2013-02').must_equal true
@store['von:lists:foo:monthly'].size.must_equal 1
end
Expand Down

0 comments on commit 0eee213

Please sign in to comment.