visit-counter 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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