counter_cachier 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/README.md +40 -0
- data/counter_cachier.gemspec +21 -0
- data/lib/counter_cachier/cachier.rb +20 -0
- data/lib/counter_cachier/counter_cachier.rb +45 -0
- data/lib/counter_cachier/mounter.rb +27 -0
- data/lib/counter_cachier/version.rb +3 -0
- data/lib/counter_cachier.rb +4 -0
- data/spec/lib/cachier_spec.rb +21 -0
- data/spec/lib/counter_cachier_spec.rb +43 -0
- data/spec/lib/mounter_spec.rb +47 -0
- data/spec/spec_helper.rb +9 -0
- metadata +112 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
counter-cachier
|
2
|
+
===============
|
3
|
+
|
4
|
+
usage
|
5
|
+
-------------
|
6
|
+
|
7
|
+
counter cachier is a generic counter caching solution (using redis) for stuff where ActiveRecord's counter cache just does not cut it. i.e. when the counter is on a complex association, etc.
|
8
|
+
|
9
|
+
usage is pretty simple, simply include CounterCachier in the class you wish to use it on, and define counter cachiers:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
class User
|
13
|
+
include CounterCachier
|
14
|
+
|
15
|
+
counter_cachier :approved_posts_count do |user|
|
16
|
+
user.posts.approved.count
|
17
|
+
end
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
the block's argument is the object you're making the calculations for (i.e. an instance of User), the value returned in the block will be the new counter. From this moment, two new methods are added to User - approved_posts_count and recalc_approved_posts_count.
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
user = User.first
|
25
|
+
user.approved_posts_count #=> 10
|
26
|
+
#...user adds an approved post...
|
27
|
+
user.recalc_approved_posts_count #=> 11, but it actually recalculates and pushes the number into redis.
|
28
|
+
```
|
29
|
+
|
30
|
+
installation
|
31
|
+
-------------
|
32
|
+
|
33
|
+
in your gemfile
|
34
|
+
gem "counter_cachier"
|
35
|
+
|
36
|
+
in an initializer:
|
37
|
+
```ruby
|
38
|
+
CounterCachier.redis = Redis.new(redis_configuration)
|
39
|
+
```
|
40
|
+
you can skip this if you've got the $redis global.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/counter_cachier/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Tom Caspy"]
|
6
|
+
gem.email = ["tcaspy@gmail.com"]
|
7
|
+
gem.description = %q{Counter cache on steroids - requires a bit of configuration, but then simply saves counter data.}
|
8
|
+
gem.summary = %q{simply set the counter's name and write a block for recalculation of that cache, and you're done}
|
9
|
+
gem.homepage = "https://github.com/unorthodoxgeek/counter-cachier"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "counter_cachier"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = CounterCachier::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency(%q<rspec>, [">= 0"])
|
19
|
+
gem.add_development_dependency(%q<rake>, ["~> 0.9.2"])
|
20
|
+
gem.add_dependency(%q<redis>, [">= 0"])
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module CounterCachier
|
2
|
+
class Cachier
|
3
|
+
attr_accessor :name
|
4
|
+
|
5
|
+
def initialize(name, &block)
|
6
|
+
@name = name
|
7
|
+
@block = block
|
8
|
+
end
|
9
|
+
|
10
|
+
def recalc(object)
|
11
|
+
@block.call(object)
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(object)
|
15
|
+
new_value = recalc(object)
|
16
|
+
CounterCachier.write(object, name, new_value)
|
17
|
+
new_value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module CounterCachier
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def redis=(r)
|
5
|
+
@redis = r
|
6
|
+
end
|
7
|
+
|
8
|
+
def redis
|
9
|
+
@redis ||= $redis
|
10
|
+
end
|
11
|
+
|
12
|
+
def key(object, name)
|
13
|
+
"counter_cachier::#{object.class.to_s}::#{object.id}::#{name}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def read(object, cachier)
|
17
|
+
value = redis.get(key(object, cachier.name))
|
18
|
+
if value.nil?
|
19
|
+
value = cachier.write(object)
|
20
|
+
end
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def write(object, name, value)
|
25
|
+
redis.set key(object, name), value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.included(base)
|
30
|
+
base.extend ClassMethods
|
31
|
+
class << base
|
32
|
+
attr_accessor :cachiers
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module ClassMethods
|
37
|
+
def counter_cachier(name, options = {}, &block)
|
38
|
+
self.cachiers ||= {}
|
39
|
+
self.cachiers[name] = Mounter.new(self, name, &block)
|
40
|
+
if options[:async]
|
41
|
+
later "recalc_#{name}", queue: options[:queue] || :long
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CounterCachier
|
2
|
+
class Mounter
|
3
|
+
|
4
|
+
attr_accessor :cachier
|
5
|
+
|
6
|
+
def initialize(klass, name, &block)
|
7
|
+
@klass = klass
|
8
|
+
@name = name
|
9
|
+
self.cachier = CounterCachier::Cachier.new(name, &block)
|
10
|
+
define_methods
|
11
|
+
end
|
12
|
+
|
13
|
+
def define_methods
|
14
|
+
#these local variables are set so they can be used inside the define_method blocks
|
15
|
+
name = @name
|
16
|
+
cachier = @cachier
|
17
|
+
|
18
|
+
@klass.send :define_method, @name do
|
19
|
+
CounterCachier.read(self, name)
|
20
|
+
end
|
21
|
+
|
22
|
+
@klass.send :define_method, "recalc_#{@name}" do
|
23
|
+
cachier.write(self)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe CounterCachier::Cachier do
|
4
|
+
|
5
|
+
describe :cachier do
|
6
|
+
|
7
|
+
let :cachier do
|
8
|
+
CounterCachier::Cachier.new(:foo){ |bar| 10 * bar}
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should save a block for recalculation" do
|
12
|
+
cachier.recalc(4).should == 40
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should write the counter cache to the store" do
|
16
|
+
CounterCachier.should_receive(:write)
|
17
|
+
cachier.write(4)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
Redis ||= Class.new
|
3
|
+
|
4
|
+
describe CounterCachier do
|
5
|
+
let(:a){double(id: 1, class: "Foo")}
|
6
|
+
let(:b){double(id: 2, class: "Bar")}
|
7
|
+
let(:cachier){double(name: "foo")}
|
8
|
+
|
9
|
+
before :each do
|
10
|
+
CounterCachier.redis = double
|
11
|
+
end
|
12
|
+
|
13
|
+
describe :write do
|
14
|
+
it "should write the new counter cache" do
|
15
|
+
CounterCachier.redis.should_receive(:set)
|
16
|
+
CounterCachier.write(a, :b, 4)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe :read do
|
21
|
+
it "should read the counter cache" do
|
22
|
+
CounterCachier.redis.should_receive(:get).and_return(1)
|
23
|
+
CounterCachier.read(a, cachier)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should recalc the counter cache if the read yields nil" do
|
27
|
+
CounterCachier.redis.should_receive(:get)
|
28
|
+
cachier.should_receive(:write).and_return(5)
|
29
|
+
CounterCachier.read(a, cachier).should == 5
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe :key do
|
34
|
+
it "should return a string" do
|
35
|
+
CounterCachier.key(a, :a).should be_a String
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return a valid unique key for each object" do
|
39
|
+
CounterCachier.key(a, :a).should_not == CounterCachier.key(b, :a)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
class Dummy
|
3
|
+
include CounterCachier
|
4
|
+
def id
|
5
|
+
4
|
6
|
+
end
|
7
|
+
|
8
|
+
def baz
|
9
|
+
10
|
10
|
+
end
|
11
|
+
|
12
|
+
counter_cachier :foo do |foo|
|
13
|
+
foo.baz
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe CounterCachier::Mounter do
|
18
|
+
let(:dummy) {Dummy.new}
|
19
|
+
describe "class methods" do
|
20
|
+
it "should have cachiers accessor" do
|
21
|
+
Dummy.should respond_to :cachiers
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should call later if later specified" do
|
25
|
+
Dummy.should_receive(:later).with("recalc_bar", queue: :bar)
|
26
|
+
|
27
|
+
Dummy.counter_cachier :bar, async: true, queue: :bar do
|
28
|
+
10
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "instance methods" do
|
34
|
+
it "should respond to foo" do
|
35
|
+
dummy.should respond_to(:foo)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should respond to recalc_foo" do
|
39
|
+
dummy.should respond_to(:recalc_foo)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should run the block when calling recalc_foo" do
|
43
|
+
CounterCachier.redis.should_receive :set
|
44
|
+
dummy.recalc_foo.should == 10
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: counter_cachier
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tom Caspy
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
prerelease: false
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
none: false
|
23
|
+
type: :development
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ! '>='
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: '0'
|
29
|
+
none: false
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
prerelease: false
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.9.2
|
38
|
+
none: false
|
39
|
+
type: :development
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: 0.9.2
|
45
|
+
none: false
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: redis
|
48
|
+
prerelease: false
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
none: false
|
55
|
+
type: :runtime
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
none: false
|
62
|
+
description: Counter cache on steroids - requires a bit of configuration, but then
|
63
|
+
simply saves counter data.
|
64
|
+
email:
|
65
|
+
- tcaspy@gmail.com
|
66
|
+
executables: []
|
67
|
+
extensions: []
|
68
|
+
extra_rdoc_files: []
|
69
|
+
files:
|
70
|
+
- .gitignore
|
71
|
+
- Gemfile
|
72
|
+
- README.md
|
73
|
+
- counter_cachier.gemspec
|
74
|
+
- lib/counter_cachier.rb
|
75
|
+
- lib/counter_cachier/cachier.rb
|
76
|
+
- lib/counter_cachier/counter_cachier.rb
|
77
|
+
- lib/counter_cachier/mounter.rb
|
78
|
+
- lib/counter_cachier/version.rb
|
79
|
+
- spec/lib/cachier_spec.rb
|
80
|
+
- spec/lib/counter_cachier_spec.rb
|
81
|
+
- spec/lib/mounter_spec.rb
|
82
|
+
- spec/spec_helper.rb
|
83
|
+
homepage: https://github.com/unorthodoxgeek/counter-cachier
|
84
|
+
licenses: []
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
none: false
|
95
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ! '>='
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
100
|
+
none: false
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 1.8.24
|
104
|
+
signing_key:
|
105
|
+
specification_version: 3
|
106
|
+
summary: simply set the counter's name and write a block for recalculation of that
|
107
|
+
cache, and you're done
|
108
|
+
test_files:
|
109
|
+
- spec/lib/cachier_spec.rb
|
110
|
+
- spec/lib/counter_cachier_spec.rb
|
111
|
+
- spec/lib/mounter_spec.rb
|
112
|
+
- spec/spec_helper.rb
|