abaci 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## Abaci 0.1.0
2
+
3
+ * Initial build
data/README.md ADDED
@@ -0,0 +1,75 @@
1
+ # Abaci
2
+
3
+ [![Build Status](https://secure.travis-ci.org/jdtornow/abaci.png)](http://travis-ci.org/jdtornow/abaci) [![Dependency Status](https://gemnasium.com/jdtornow/abaci.png?travis)](https://gemnasium.com/jdtornow/abaci)
4
+
5
+ Abaci (pronounced abba-sigh) is a simple structure for the collection and reporting on numerical counters. Counters can be used to track time-based statistics for a wide array of functions within your application. Abaci uses a Redis backend to store all counters for lightning quick read and write access.
6
+
7
+ What does *abaci* mean? Its the plural form of [Abacus](http://en.wikipedia.org/wiki/Abacus), a common arithmetic assistance device from before we had computers. Imagine that.
8
+
9
+ ## Requirements
10
+
11
+ * Ruby 1.9.2+
12
+ * Redis
13
+
14
+ ## Configuration
15
+
16
+ Abaci will use the default Redis instance by default, assumed to be available via `Redis.current`. To use with a specific Redis instance, pass a `Redis` object to `Abaci.options[:redis]`. You can use it this way with [Redis Namespace](https://github.com/defunkt/redis-namespace) if necessary.
17
+
18
+ By default, the prefix `ab` is used before all stats stored in Redis. If you are using running multiple apps using the same Redis instance (likely in development mode), you can change the prefix by setting `Abaci.options[:prefix]`.
19
+
20
+ Here's a sample configuration that we use often:
21
+
22
+ ```ruby
23
+ connection = Redis.new
24
+ REDIS = Redis::Namespace.new(:ns, :redis => connection)
25
+
26
+ Abaci.options[:redis] = REDIS
27
+ Abaci.options[:prefix] = 'stats'
28
+ ```
29
+
30
+ If you are using Rails, put that in an initializer and you're good to go.
31
+
32
+ ## Basic Usage
33
+
34
+ ```ruby
35
+ # Log page views
36
+ Abaci['views'].increment # => 1
37
+ Abaci['views'].increment # => 2
38
+
39
+ # Increment (and decrement) all take a parameter or default to incrementing by 1
40
+ Abaci['views'].increment(3) # => 5
41
+
42
+ # Decrementing is available if needed
43
+ Abaci['views'].decrement # => 4
44
+
45
+ # Get total number of views
46
+ Abaci['views'].get # => 4
47
+
48
+ # Get total number of views in year 2012
49
+ Abaci['views'].get(2012) # => 4
50
+
51
+ # Get total number of views in October of 2012
52
+ Abaci['views'].get(2012, 10) # => 4
53
+
54
+ # Get total number of views on October 20, 2012
55
+ Abaci['views'].get(2012, 10, 20) # => 4
56
+
57
+ # Get total views in the last 30 days
58
+ Abaci['views'].get_last_days(30) # => 4
59
+
60
+ # View all counter stat keys stored
61
+ Abaci['views'].keys
62
+
63
+ # Clear counters for views
64
+ Abaci['views'].del
65
+ ```
66
+
67
+ ## Issues
68
+
69
+ If you have any issues or find bugs running Abaci, please [report them on Github](https://github.com/jdtornow/abaci/issues). While most functions should be stable, Abaci is still in its infancy and certain issues may be present.
70
+
71
+ ## License
72
+
73
+ Abaci is released under the [MIT license](http://www.opensource.org/licenses/MIT)
74
+
75
+ Contributions and pull-requests are more than welcome.
data/lib/abaci.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'abaci/version'
2
+
3
+ module Abaci
4
+ autoload :Counter, 'abaci/counter'
5
+ autoload :Store, 'abaci/store'
6
+
7
+ # Configuration options
8
+ class << self
9
+ def [](key)
10
+ Counter[key]
11
+ end
12
+
13
+ def options
14
+ @options ||= {
15
+ :redis => nil,
16
+ :prefix => 'ab'
17
+ }
18
+ end
19
+
20
+ def prefix
21
+ options[:prefix]
22
+ end
23
+
24
+ def store
25
+ @store ||= Store.new(options)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ module Abaci
2
+ class Counter
3
+ attr_reader :key
4
+
5
+ def initialize(key = nil)
6
+ @key = key
7
+ end
8
+
9
+ def decr(by = 1)
10
+ decrement_at(nil, by)
11
+ end
12
+ alias_method :decrement, :decr
13
+
14
+ def decrement_at(date = nil, by = 1)
15
+ date = Time.now unless date.respond_to?(:strftime)
16
+ run(:decrby, by, date)
17
+ end
18
+
19
+ def del
20
+ keys.each { |k| Abaci.store.del(k) }
21
+ true
22
+ end
23
+
24
+ def get(year = nil, month = nil, day = nil, hour = nil, min = nil)
25
+ get_key = [ key, year, month, day, hour, min ].compact.join(':')
26
+ Abaci.store.get(get_key).to_i
27
+ end
28
+
29
+ def get_last_days(number_of_days = 30)
30
+ seconds = number_of_days.to_i * 86400
31
+ start = (Date.today - Rational(seconds, 86400)).to_date
32
+ dates = (start..Date.today).map { |d| d.strftime('%Y:%-m:%-d') }
33
+ dates.map { |date| Abaci.store.get("#{key}:#{date}" ).to_i }.reduce(:+)
34
+ end
35
+
36
+ def incr(by = 1)
37
+ increment_at(nil, by)
38
+ end
39
+ alias_method :increment, :incr
40
+
41
+ def increment_at(date = nil, by = 1)
42
+ date = Time.now unless date.respond_to?(:strftime)
43
+ run(:incrby, by, date)
44
+ end
45
+
46
+ def keys
47
+ Abaci.store.keys("#{key}*")
48
+ end
49
+
50
+ ## Class methods ##
51
+ ############################################################################
52
+
53
+ # Returns a hash of all current values
54
+ def self.all
55
+ keys.inject({}) { |hash, key| hash[key] = Abaci.store.get(key).to_i; hash }
56
+ end
57
+
58
+ # Gets all currently logged stat keys
59
+ def self.keys(search = "*")
60
+ Abaci.store.keys(search).sort
61
+ end
62
+
63
+ # Alias for Counter#new(key)
64
+ def self.[](key)
65
+ Counter.new(key)
66
+ end
67
+
68
+ def self.method_missing(method, *args)
69
+ ms = method.to_s.downcase.strip
70
+
71
+ if ms =~ /(incr|decr)(ement|ease)?_([a-z_]*)$/
72
+ return self[$3].send($1, *args)
73
+ elsif ms =~ /^(clear|reset|del)_([a-z_]*)!$/
74
+ return self[$2].del
75
+ elsif ms =~ /^last_(\d*)_days_of_([a-z_]*)$/
76
+ return self[$2].get_last_days($1)
77
+ elsif ms =~ /[a-z_]*/
78
+ return self[ms].get(*args)
79
+ end
80
+
81
+ super
82
+ end
83
+
84
+ ## Protected methods ##
85
+ ############################################################################
86
+
87
+ protected
88
+ def run(method, by, date)
89
+ now = date.strftime('%Y/%m/%d/%k/%M').split('/')
90
+
91
+ now.inject(key) do |memo, t|
92
+ memo = "#{memo}:#{t.to_i}"
93
+ Abaci.store.send(method, memo, by)
94
+ memo
95
+ end
96
+
97
+ Abaci.store.send(method, key, by)
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,48 @@
1
+ module Abaci
2
+ require 'redis'
3
+
4
+ # Common interface for Redis. In the future this could be
5
+ # swapped out for an alternate datastore.
6
+ class Store
7
+ def initialize(options)
8
+ @redis = options[:redis] || Redis.current
9
+ @prefix = options[:prefix] || 'ab'
10
+ end
11
+
12
+ def decrby(key, by = 1)
13
+ exec(:decrby, key, by)
14
+ end
15
+
16
+ def del(key)
17
+ exec(:del, key)
18
+ end
19
+
20
+ def get(key)
21
+ exec(:get, key)
22
+ end
23
+
24
+ def incrby(key, by = 1)
25
+ exec(:incrby, key, by)
26
+ end
27
+
28
+ def keys(pattern)
29
+ sub = Regexp.new("^#{Abaci.prefix}:")
30
+ exec(:keys, pattern).map { |k| k.gsub(sub, '') }
31
+ end
32
+
33
+ def set(key, value)
34
+ exec(:set, key, value)
35
+ end
36
+
37
+ protected
38
+ def exec(command, key, *args)
39
+ if @redis and @redis.respond_to?(command)
40
+ @redis.send(command, prefixed_key(key), *args)
41
+ end
42
+ end
43
+
44
+ def prefixed_key(key)
45
+ [ @prefix, key ].compact.join(':')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,3 @@
1
+ module Abaci
2
+ VERSION = "0.1.0" unless defined?(::Abaci::VERSION)
3
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: abaci
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - John Tornow
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-11-03 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '2.0'
30
+ description: Collect stats on events, activities and behaviors across time in Ruby
31
+ with a Redis backend.
32
+ email:
33
+ - john@johntornow.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files: []
37
+ files:
38
+ - lib/abaci/counter.rb
39
+ - lib/abaci/store.rb
40
+ - lib/abaci/version.rb
41
+ - lib/abaci.rb
42
+ - README.md
43
+ - CHANGELOG.md
44
+ homepage: http://github.com/jdtornow/abaci
45
+ licenses: []
46
+ post_install_message:
47
+ rdoc_options: []
48
+ require_paths:
49
+ - lib
50
+ required_ruby_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: 1.8.7
56
+ required_rubygems_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ requirements: []
63
+ rubyforge_project:
64
+ rubygems_version: 1.8.23
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A basic stats reporting and collection tool for Ruby.
68
+ test_files: []
69
+ has_rdoc: