visit-counter 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
- # Visit::Counter
1
+ # VisitCounter
2
2
 
3
- TODO: Write a gem description
3
+ VisitCounter is a gem which solves the annoying problem of counting visits and displaying them in real time. In an SQL database, for a site with a lot of hits, this can cause quite a lot of overhead. VisitCounter aims to solve this by using a quick key-value store to keep a delta, and only persist to the SQL DB when the delta crosses a certain percent of the saved counter.
4
+ It can be used transparently, by overriding the accessor to the counter, or simply by using the helper functions it defines - incr_counter, read_counter, get_counter_delta and nullify_counter.
4
5
 
5
6
  ## Installation
6
7
 
@@ -18,7 +19,21 @@ Or install it yourself as:
18
19
 
19
20
  ## Usage
20
21
 
21
- TODO: Write usage instructions here
22
+ a. the default storage engine is redis. If you have a global $redis for your redis connection, we default to using that. Otherwise, or if you want to specify a different connection, in an initializer you should define it by:
23
+
24
+ VisitCounter::Store::RedisStore.redis = Redis.new(host: "your_redis_host", port: port)
25
+
26
+ b. in the class you wish to have a visit counter simply declare
27
+ include VisitCounter
28
+ from this moment on, you can use the incr_counter(:counter_name), nullify_counter(:counter_name) and read_counter(:counter_name) methods
29
+ You can also do something like this:
30
+
31
+ class Foo < ActiveRecord::Base
32
+ include VisitCounter
33
+ cached_counter :counter_name
34
+ end
35
+
36
+ this will override the counter_name method to read the live counter (from both database and the NoSQL storage) and add a increase_counter_name method for upping the counter by 1 (in the NoSQL and/or persist to DB when needed)
22
37
 
23
38
  ## Contributing
24
39
 
@@ -0,0 +1,21 @@
1
+ module VisitCounter
2
+ class Store
3
+ class AbstractStore
4
+ class << self
5
+ #placeholders
6
+
7
+ def incr(key)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def get(key)
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def nullify(key)
16
+ raise NotImplementedError
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ module VisitCounter
2
+ class Store
3
+ class RailsStore < VisitCounter::Store::AbstractStore
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ require "redis"
2
+
3
+ module VisitCounter
4
+ class Store
5
+ class RedisStore < VisitCounter::Store::AbstractStore
6
+ @@redis = nil
7
+ class << self
8
+
9
+ def redis=(r)
10
+ if r.is_a?(Redis)
11
+ @@redis = r
12
+ else
13
+ @@redis = Redis.new(r)
14
+ end
15
+ end
16
+
17
+ def redis
18
+ if @@redis.nil? && defined?($redis)
19
+ @@redis = $redis
20
+ else
21
+ @@redis
22
+ end
23
+ end
24
+
25
+ def incr(key)
26
+ redis.incr(key).to_i
27
+ end
28
+
29
+ def get(key)
30
+ redis.get(key).to_i
31
+ end
32
+
33
+ def nullify(key)
34
+ redis.set(key, 0)
35
+ end
36
+
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
1
  module VisitCounter
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -1,16 +1,52 @@
1
1
  module VisitCounter
2
- def incr_counter(name)
3
- key = VisitCounter::Key.key(self, name)
4
- count = VisitCounter::Store.engine.incr(key)
5
- current_count = self.send(name).to_i
6
- if current_count / (current_count + count).to_f < 0.7
7
- self.update_attribute(name, current_count + count)
8
- nullify_counter_cache(name)
2
+
3
+ def self.included(base)
4
+ base.send(:include, InstanceMethods)
5
+ base.send(:extend, ClassMethods)
6
+ end
7
+
8
+ module InstanceMethods
9
+ def incr_counter(name)
10
+ key = VisitCounter::Key.key(self, name)
11
+ count = VisitCounter::Store.engine.incr(key)
12
+ current_count = self.send(:read_attribute, name).to_i
13
+ if current_count / (current_count + count).to_f < 0.7
14
+ self.update_attribute(name, current_count + count)
15
+ nullify_counter_cache(name)
16
+ end
17
+ end
18
+
19
+ def get_counter_delta(name)
20
+ key = VisitCounter::Key.key(self, name)
21
+ VisitCounter::Store.engine.get(key)
22
+ end
23
+
24
+ def read_counter(name)
25
+ current_count = self.send(:read_attribute, name).to_i
26
+ count = get_counter_delta(name)
27
+
28
+ current_count + count
29
+ end
30
+
31
+ def nullify_counter_cache(name)
32
+ key = VisitCounter::Key.key(self, name)
33
+ VisitCounter::Store.engine.nullify(key)
9
34
  end
10
35
  end
11
36
 
12
- def nullify_counter_cache(name)
13
- key = VisitCounter::Key.key(self, name)
14
- VisitCounter::Store.engine.nullify(key)
37
+ module ClassMethods
38
+
39
+ def cached_counter(name)
40
+ self.send(:alias_method, "real_#{name}", name)
41
+
42
+ self.send(:define_method, name) do
43
+ read_counter(name)
44
+ end
45
+
46
+ self.send(:define_method, "increase_#{name}") do
47
+ incr_counter(name)
48
+ end
49
+ end
50
+
15
51
  end
16
52
  end
@@ -8,9 +8,14 @@ class DummyObject
8
8
  def update_attribute(attribute, value)
9
9
  self.send("#{attribute}=", value)
10
10
  end
11
+
12
+ def read_attribute(name)
13
+ #yeah, evals are evil, but it works and it's for testing purposes only. we assume read_attribute is defined the same as in AR wherever we include this module
14
+ eval("@#{name}")
15
+ end
11
16
  end
12
17
 
13
- VisitCounter::Store::RedisStore.redis = {host: "localhost"}
18
+ VisitCounter::Store::RedisStore.redis = Redis.new(host: "localhost")
14
19
 
15
20
  describe VisitCounter do
16
21
  describe "incrementing counters" do
@@ -41,6 +46,49 @@ describe VisitCounter do
41
46
  end
42
47
  @d.counter.should == 143
43
48
  end
49
+ end
50
+
51
+ describe "reading counters" do
52
+
53
+ before :each do
54
+ @d = DummyObject.new
55
+ @d.stub!(:id).and_return(1)
56
+ @d.nullify_counter_cache(:counter)
57
+ end
58
+
59
+ it "should allow to read an unchanged counter" do
60
+ @d.counter = 10
61
+ @d.read_counter(:counter).should == 10
62
+ end
63
+
64
+ it "should read an updated counter" do
65
+ @d.counter = 10
66
+ @d.incr_counter(:counter)
67
+ @d.read_counter(:counter).should == 11
68
+ end
69
+ end
70
+
71
+ describe "overriding the getter" do
72
+ before :all do
73
+ DummyObject.cached_counter :counter
74
+ end
75
+
76
+ before :each do
77
+ @d = DummyObject.new
78
+ @d.stub!(:id).and_return(1)
79
+ @d.nullify_counter_cache(:counter)
80
+ end
44
81
 
82
+ it "should define the methods" do
83
+ @d.should respond_to(:real_counter)
84
+ @d.should respond_to(:increase_counter)
85
+ end
86
+
87
+ it "should set the counter" do
88
+ @d.counter = 10
89
+ @d.increase_counter
90
+ @d.counter.should == 11
91
+ end
45
92
  end
93
+
46
94
  end
@@ -6,7 +6,7 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["tcaspy@gmail.com"]
7
7
  gem.description = %q{Simple counter increment which only writes to DB once in a while}
8
8
  gem.summary = %q{No need to write to db each visit, save the visits to a quick DB like redis or memcached, and write to the SQL db once reads exeeded a certain threshold}
9
- gem.homepage = ""
9
+ gem.homepage = "https://github.com/KensoDev/visit-counter"
10
10
 
11
11
  gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
12
  gem.files = `git ls-files`.split("\n")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: visit-counter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
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: 2012-07-25 00:00:00.000000000Z
12
+ date: 2012-08-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
16
- requirement: &70131008040560 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,15 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *70131008040560
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
25
30
  - !ruby/object:Gem::Dependency
26
31
  name: rake
27
- requirement: &70131008040040 !ruby/object:Gem::Requirement
32
+ requirement: !ruby/object:Gem::Requirement
28
33
  none: false
29
34
  requirements:
30
35
  - - ~>
@@ -32,10 +37,15 @@ dependencies:
32
37
  version: 0.9.2
33
38
  type: :development
34
39
  prerelease: false
35
- version_requirements: *70131008040040
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.9.2
36
46
  - !ruby/object:Gem::Dependency
37
47
  name: redis
38
- requirement: &70131008039560 !ruby/object:Gem::Requirement
48
+ requirement: !ruby/object:Gem::Requirement
39
49
  none: false
40
50
  requirements:
41
51
  - - ! '>='
@@ -43,7 +53,12 @@ dependencies:
43
53
  version: '0'
44
54
  type: :runtime
45
55
  prerelease: false
46
- version_requirements: *70131008039560
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
47
62
  description: Simple counter increment which only writes to DB once in a while
48
63
  email:
49
64
  - tcaspy@gmail.com
@@ -59,13 +74,16 @@ files:
59
74
  - lib/visit-counter.rb
60
75
  - lib/visit-counter/key.rb
61
76
  - lib/visit-counter/store.rb
77
+ - lib/visit-counter/store/abstract_store.rb
78
+ - lib/visit-counter/store/rails_store.rb
79
+ - lib/visit-counter/store/redis_store.rb
62
80
  - lib/visit-counter/version.rb
63
81
  - lib/visit-counter/visit_counter.rb
64
82
  - spec/lib/store_spec.rb
65
83
  - spec/lib/visit_counter_spec.rb
66
84
  - spec/spec_helper.rb
67
85
  - visit-counter.gemspec
68
- homepage: ''
86
+ homepage: https://github.com/KensoDev/visit-counter
69
87
  licenses: []
70
88
  post_install_message:
71
89
  rdoc_options: []
@@ -85,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
85
103
  version: '0'
86
104
  requirements: []
87
105
  rubyforge_project:
88
- rubygems_version: 1.8.10
106
+ rubygems_version: 1.8.24
89
107
  signing_key:
90
108
  specification_version: 3
91
109
  summary: No need to write to db each visit, save the visits to a quick DB like redis