redis_voteable 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ *.log
6
+ *.rdb
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in redis_voteable.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2011 Chris Brauchli
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+ RSpec::Core::RakeTask.new(:spec)
5
+ task :default => :spec
@@ -0,0 +1,63 @@
1
+ require 'active_support/dependencies'
2
+ require 'redis'
3
+ require 'redis_voteable/version'
4
+ require 'redis_voteable/voteable'
5
+ require 'redis_voteable/voter'
6
+ require 'redis_voteable/exceptions'
7
+
8
+ module RedisVoteable
9
+ extend ActiveSupport::Concern
10
+
11
+ UP_VOTERS = "up_voters"
12
+ DOWN_VOTERS = "dn_voters"
13
+ UP_VOTES = "up_votes"
14
+ DOWN_VOTES = "dn_votes"
15
+
16
+ mattr_accessor :redis_voteable_settings
17
+ @@redis_voteable_settings = {
18
+ :host => 'localhost',
19
+ :port => '6379',
20
+ :db => 0,
21
+ :key_prefix => "vote:",
22
+ }
23
+ mattr_accessor :redis
24
+ @@redis = Redis.new(@@redis_voteable_settings)
25
+
26
+ def prefixed(sid)
27
+ "#{@@redis_voteable_settings[:key_prefix]}#{sid}"
28
+ end
29
+
30
+ def class_key(v)
31
+ "#{v.class.name}:#{v.id}"
32
+ end
33
+
34
+ module ClassMethods
35
+ def voteable?
36
+ false
37
+ end
38
+
39
+ def voter?
40
+ false
41
+ end
42
+
43
+ # Specify a model as voteable.
44
+ #
45
+ # Example:
46
+ # class Question < ActiveRecord::Base
47
+ # acts_as_voteable
48
+ # end
49
+ def acts_as_voteable
50
+ include Voteable
51
+ end
52
+
53
+ # Specify a model as voter.
54
+ #
55
+ # Example:
56
+ # class User < ActiveRecord::Base
57
+ # acts_as_voter
58
+ # end
59
+ def acts_as_voter
60
+ include Voter
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,29 @@
1
+ module RedisVoteable
2
+ module Exceptions
3
+ class AlreadyVotedError < StandardError
4
+ attr_reader :up_vote
5
+
6
+ def initialize(up_vote)
7
+ vote = if up_vote
8
+ "up voted"
9
+ else
10
+ "down voted"
11
+ end
12
+
13
+ super "The voteable was already #{vote} by the voter."
14
+ end
15
+ end
16
+
17
+ class NotVotedError < StandardError
18
+ def initialize
19
+ super "The voteable was not voted by the voter."
20
+ end
21
+ end
22
+
23
+ class InvalidVoteableError < StandardError
24
+ def initialize
25
+ super "Invalid voteable."
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module RedisVoteable
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,101 @@
1
+ module RedisVoteable
2
+ module Voteable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+
7
+ end
8
+
9
+ module ClassMethods
10
+ def voteable?
11
+ true
12
+ end
13
+
14
+ # private
15
+ # def build_voter(voter)
16
+ # tmp = voter.split(':')
17
+ # tmp[0, tmp.length - 1].constantize.find(tmp.last)
18
+ # end
19
+ end
20
+
21
+ module InstanceMethods
22
+ def up_votes
23
+ redis.scard prefixed("#{class_key(self)}:#{UP_VOTERS}")
24
+ end
25
+
26
+ def down_votes
27
+ redis.scard prefixed("#{class_key(self)}:#{DOWN_VOTERS}")
28
+ end
29
+
30
+ def total_votes
31
+ up_votes + down_votes
32
+ end
33
+
34
+ # Return the difference between up and and votes.
35
+ # May be negative if there are more down than up votes.
36
+ def tally
37
+ up_votes - down_votes
38
+ end
39
+
40
+ def up_percentage
41
+ return (up_votes.to_f * 100 / total_votes) unless votes == 0
42
+ nil
43
+ end
44
+
45
+ def down_percentage
46
+ return (down_votes.to_f * 100 / total_votes) unless votes == 0
47
+ nil
48
+ end
49
+
50
+ # Returns an array of objects that are +voter+s that voted on this
51
+ # +voteable+. This method can be very slow, as it constructs each
52
+ # object. Also, it assumes that each object has a +find(id)+ method
53
+ # defined (e.g., any ActiveRecord object).
54
+ def voters
55
+ up_voters | down_voters
56
+ end
57
+
58
+ def up_voters
59
+ voters = redis.smembers prefixed("#{class_key(self)}:#{UP_VOTERS}")
60
+ voters.map do |voter|
61
+ tmp = voter.split(':')
62
+ tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
63
+ end
64
+ end
65
+
66
+ def down_voters
67
+ voters = redis.smembers prefixed("#{class_key(self)}:#{DOWN_VOTERS}")
68
+ voters.map do |voter|
69
+ tmp = voter.split(':')
70
+ tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
71
+ end
72
+ end
73
+
74
+ # Calculates the (lower) bound of the Wilson confidence interval
75
+ # See: http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval
76
+ # and: http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
77
+ def confidence(bound = :lower)
78
+ #include Math
79
+ n = up_votes + down_votes
80
+ if n == 0
81
+ return 0 if n == 0
82
+ end
83
+ z = 1.4395314800662002 # Determines confidence to estimate.
84
+ # 1.0364333771448913 = 70%
85
+ # 1.2815515594600038 = 80%
86
+ # 1.4395314800662002 = 85%
87
+ # 1.644853646608357 = 90%
88
+ # 1.9599639715843482 = 95%
89
+ # 2.2414027073522136 = 97.5%
90
+ p_hat = 1.0*up_votes/n
91
+ left = p_hat + z*z/(2*n)
92
+ right = z * Math.sqrt( (p_hat*(1-p_hat) + z*z/(4*n)) / n )
93
+ under = 1 + z*z/n
94
+ return (left - right) / under unless bound == :upper
95
+ return (left + right) / under
96
+ #return Math.sqrt( p_hat + z * z / ( 2 * n ) - z * ( ( p_hat * ( 1 - p_hat ) + z * z / ( 4 * n ) ) / n ) ) / ( 1 + z * z / n )
97
+ end
98
+ end
99
+
100
+ end
101
+ end
@@ -0,0 +1,182 @@
1
+ module RedisVoteable
2
+ module Voter
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ # TODO: add scope
7
+ # scope :up_voted_for, lambda { |votee| where(:_id => { '$in' => votee.up_voter_ids }) }
8
+ # scope :down_voted_for, lambda { |votee| where(:_id => { '$in' => votee.down_voter_ids }) }
9
+ # scope :voted_for, lambda { |votee| where(:_id => { '$in' => votee.voter_ids }) }
10
+ end
11
+
12
+ module ClassMethods
13
+ def voter?
14
+ true
15
+ end
16
+ end
17
+
18
+ module InstanceMethods
19
+ def vote(voteable, direction)
20
+ if direction == :up
21
+ up_vote(voteable)
22
+ elsif direction == :down
23
+ down_vote(voteable)
24
+ end
25
+ end
26
+
27
+ def vote!(voteable, direction)
28
+ if direction == :up
29
+ up_vote!(voteable)
30
+ elsif direction == :down
31
+ down_vote!(voteable)
32
+ end
33
+ end
34
+
35
+ # Up vote a +voteable+.
36
+ # Raises an AlreadyVotedError if the voter already up voted the voteable.
37
+ # Changes a down vote to an up vote if the the voter already down voted the voteable.
38
+ def up_vote(voteable)
39
+ check_voteable(voteable)
40
+
41
+ r = redis.multi do
42
+ redis.srem prefixed("#{class_key(voteable)}:#{DOWN_VOTERS}"), "#{class_key(self)}"
43
+ redis.srem prefixed("#{class_key(self)}:#{DOWN_VOTES}"), "#{class_key(voteable)}"
44
+ redis.sadd prefixed("#{class_key(voteable)}:#{UP_VOTERS}"), "#{class_key(self)}"
45
+ redis.sadd prefixed("#{class_key(self)}:#{UP_VOTES}"), "#{class_key(voteable)}"
46
+ end
47
+ raise Exceptions::AlreadyVotedError.new(true) unless r[2] == 1
48
+ true
49
+ end
50
+
51
+ # Up votes the +voteable+, but doesn't raise an error if the votelable was already up voted.
52
+ # The vote is simply ignored then.
53
+ def up_vote!(voteable)
54
+ begin
55
+ up_vote(voteable)
56
+ return true
57
+ rescue
58
+ return false
59
+ end
60
+ end
61
+
62
+ # Down vote a +voteable+.
63
+ # Raises an AlreadyVotedError if the voter already down voted the voteable.
64
+ # Changes an up vote to a down vote if the the voter already up voted the voteable.
65
+ def down_vote(voteable)
66
+ check_voteable(voteable)
67
+
68
+ r = redis.multi do
69
+ redis.srem prefixed("#{class_key(voteable)}:#{UP_VOTERS}"), "#{class_key(self)}"
70
+ redis.srem prefixed("#{class_key(self)}:#{UP_VOTES}"), "#{class_key(voteable)}"
71
+ redis.sadd prefixed("#{class_key(voteable)}:#{DOWN_VOTERS}"), "#{class_key(self)}"
72
+ redis.sadd prefixed("#{class_key(self)}:#{DOWN_VOTES}"), "#{class_key(voteable)}"
73
+ end
74
+ raise Exceptions::AlreadyVotedError.new(false) unless r[2] == 1
75
+ true
76
+ end
77
+
78
+ # Down votes the +voteable+, but doesn't raise an error if the votelable was already down voted.
79
+ # The vote is simply ignored then.
80
+ def down_vote!(voteable)
81
+ begin
82
+ down_vote(voteable)
83
+ return true
84
+ rescue
85
+ return false
86
+ end
87
+ end
88
+
89
+ # Clears an already done vote on a +voteable+.
90
+ # Raises a NotVotedError if the voter didn't voted for the voteable.
91
+ def clear_vote(voteable)
92
+ check_voteable(voteable)
93
+
94
+ r = redis.multi do
95
+ redis.srem prefixed("#{class_key(voteable)}:#{DOWN_VOTERS}"), "#{class_key(self)}"
96
+ redis.srem prefixed("#{class_key(self)}:#{DOWN_VOTES}"), "#{class_key(voteable)}"
97
+ redis.srem prefixed("#{class_key(voteable)}:#{UP_VOTERS}"), "#{class_key(self)}"
98
+ redis.srem prefixed("#{class_key(self)}:#{UP_VOTES}"), "#{class_key(voteable)}"
99
+ end
100
+ raise Exceptions::NotVotedError unless r[0] == 1 || r[2] == 1
101
+ true
102
+ end
103
+
104
+ # Clears an already done vote on a +voteable+, but doesn't raise an error if
105
+ # the voteable was not voted. It ignores the unvote then.
106
+ def clear_vote!(voteable)
107
+ begin
108
+ clear_vote(voteable)
109
+ return true
110
+ rescue
111
+ return false
112
+ end
113
+ end
114
+
115
+ # Return the total number of votes a voter has cast.
116
+ def vote_count()
117
+ up_vote_count + down_vote_count
118
+ end
119
+
120
+ # Returns the number of upvotes a voter has cast.
121
+ def up_vote_count()
122
+ redis.scard prefixed("#{class_key(self)}:#{UP_VOTES}")
123
+ end
124
+
125
+ # Returns the number of downvotes a voter has cast.
126
+ def down_vote_count()
127
+ redis.scard prefixed("#{class_key(self)}:#{DOWN_VOTES}")
128
+ end
129
+
130
+ # Returns true if the voter voted for the +voteable+.
131
+ def voted?(voteable)
132
+ up_voted?(voteable) || down_voted?(voteable)
133
+ end
134
+
135
+ # Returns :up, :down, or nil.
136
+ def vote_value?(voteable)
137
+ return :up if up_voted?(voteable)
138
+ return :down if down_voted?(voteable)
139
+ return nil
140
+ end
141
+
142
+ # Returns true if the voter up voted the +voteable+.
143
+ def up_voted?(voteable)
144
+ redis.sismember prefixed("#{class_key(voteable)}:#{UP_VOTERS}"), "#{class_key(self)}"
145
+ end
146
+
147
+ # Returns true if the voter down voted the +voteable+.
148
+ def down_voted?(voteable)
149
+ redis.sismember prefixed("#{class_key(voteable)}:#{DOWN_VOTERS}"), "#{class_key(self)}"
150
+ end
151
+
152
+ # Returns an array of objects that are +voter+s that voted on this
153
+ # +voteable+. This method can be very slow, as it constructs each
154
+ # object. Also, it assumes that each object has a +find(id)+ method
155
+ # defined (e.g., any ActiveRecord object).
156
+ def votings
157
+ up_votings | down_votings
158
+ end
159
+
160
+ def up_votings
161
+ votings = redis.smembers prefixed("#{class_key(self)}:#{UP_VOTES}")
162
+ votings.map do |voting|
163
+ tmp = voting.split(':')
164
+ tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
165
+ end
166
+ end
167
+
168
+ def down_votings
169
+ votings = redis.smembers prefixed("#{class_key(self)}:#{DOWN_VOTES}")
170
+ votings.map do |voting|
171
+ tmp = voting.split(':')
172
+ tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
173
+ end
174
+ end
175
+
176
+ private
177
+ def check_voteable(voteable)
178
+ raise Exceptions::InvalidVoteableError unless voteable.class.respond_to?("voteable?") && voteable.class.voteable?
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "redis_voteable/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "redis_voteable"
7
+ s.version = RedisVoteable::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Chris Brauchli"]
10
+ s.email = ["cbrauchli@gmail.com"]
11
+ s.date = "2011-09-30"
12
+ s.homepage = "http://github.com/cbrauchli/redis_voteable"
13
+ s.summary = %q{Simple vote management with Redis used as the backend.}
14
+ s.description = %q{A Redis-backed voting extension for Rails applications. }
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # Dependencies
22
+ s.add_dependency "redis", "~> 2.2.0"
23
+ s.add_dependency "activesupport"
24
+
25
+ # For development only
26
+ s.add_development_dependency "activerecord", "~> 3.0.0"
27
+ s.add_development_dependency "sqlite3-ruby", "~> 1.3.0"
28
+ s.add_development_dependency "bundler", "~> 1.0.0"
29
+ s.add_development_dependency "rspec", "~> 2.0.0"
30
+ s.add_development_dependency "database_cleaner", "~> 0.6.7"
31
+ end
Binary file
data/spec/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite3:
2
+ adapter: sqlite3
3
+ database: redis_voteable.sqlite3
@@ -0,0 +1,220 @@
1
+ # spec/lib/redis_voteable_spec.rb
2
+ require File.expand_path('../../spec_helper', __FILE__)
3
+
4
+ describe "Redis Voteable" do
5
+ before(:each) do
6
+ @voteable = VoteableModel.create(:name => "Votable 1")
7
+ @voter = VoterModel.create(:name => "Voter 1")
8
+ end
9
+
10
+ it "should create a voteable instance" do
11
+ @voteable.class.should == VoteableModel
12
+ @voteable.class.voteable?.should == true
13
+ end
14
+
15
+ it "should create a voter instance" do
16
+ @voter.class.should == VoterModel
17
+ @voter.class.voter?.should == true
18
+ end
19
+
20
+ it "should get correct vote summary" do
21
+ @voter.up_vote(@voteable).should == true
22
+ @voteable.total_votes.should == 1
23
+ @voteable.tally.should == 1
24
+ @voter.down_vote(@voteable).should == true
25
+ @voteable.total_votes.should == 1
26
+ @voteable.tally.should == -1
27
+ @voter.clear_vote(@voteable).should == true
28
+ @voteable.total_votes.should == 0
29
+ @voteable.tally.should == 0
30
+ end
31
+
32
+ it "voteable should have up vote votings" do
33
+ @voteable.up_votes.should == 0
34
+ @voter.up_vote(@voteable)
35
+ @voteable.up_votes.should == 1
36
+ @voteable.voters[0].up_voted?(@voteable).should be_true
37
+ end
38
+
39
+ it "voter should have up vote votings" do
40
+ @voter.up_vote_count == 0
41
+ @voter.up_vote(@voteable)
42
+ @voter.up_vote_count == 1
43
+ @voter.votings[0].should == @voteable
44
+ end
45
+
46
+ it "voteable should have down vote votings" do
47
+ @voteable.down_votes.should == 0
48
+ @voter.down_vote(@voteable)
49
+ @voteable.down_votes.should == 1
50
+ @voteable.voters[0].up_voted?(@voteable).should be_false
51
+ @voteable.voters[0].down_voted?(@voteable).should be_true
52
+ end
53
+
54
+ it "voter should have down vote votings" do
55
+ @voter.down_vote_count.should == 0
56
+ @voter.down_vote(@voteable)
57
+ @voter.down_vote_count.should == 1
58
+ @voter.votings[0].should == @voteable
59
+ end
60
+
61
+ describe "up vote" do
62
+ it "should increase up votes of voteable by one" do
63
+ @voteable.up_votes.should == 0
64
+ @voter.up_vote(@voteable)
65
+ @voteable.up_votes.should == 1
66
+ end
67
+
68
+ it "should increase up votes of voter by one" do
69
+ @voter.up_vote_count.should == 0
70
+ @voter.up_vote(@voteable)
71
+ @voter.up_vote_count.should == 1
72
+ end
73
+
74
+ it "should only allow a voter to up vote a voteable once" do
75
+ @voteable.up_votes.should == 0
76
+ @voter.up_vote(@voteable)
77
+ lambda { @voter.up_vote(@voteable) }.should raise_error(RedisVoteable::Exceptions::AlreadyVotedError)
78
+ @voteable.up_votes.should == 1
79
+ end
80
+
81
+ it "should only allow a voter to up vote a voteable once without raising an error" do
82
+ @voteable.up_votes.should == 0
83
+ @voter.up_vote!(@voteable)
84
+ @voteable.up_votes.should == 1
85
+ lambda {
86
+ @voter.up_vote!(@voteable).should == false
87
+ }.should_not raise_error(RedisVoteable::Exceptions::AlreadyVotedError)
88
+ @voteable.total_votes.should == 1
89
+ end
90
+
91
+ it "should change a down vote to an up vote" do
92
+ @voter.down_vote(@voteable)
93
+ @voteable.up_votes.should == 0
94
+ @voteable.down_votes.should == 1
95
+ @voteable.tally.should == -1
96
+ @voter.up_vote_count.should == 0
97
+ @voter.down_vote_count.should == 1
98
+
99
+ @voter.up_vote(@voteable)
100
+ @voteable.up_votes.should == 1
101
+ @voteable.down_votes.should == 0
102
+ @voteable.tally.should == 1
103
+ @voter.up_vote_count.should == 1
104
+ @voter.down_vote_count.should == 0
105
+ end
106
+
107
+ it "should allow up votes from different voters" do
108
+ @voter2 = VoterModel.create(:name => "Voter 2")
109
+ @voter.up_vote(@voteable)
110
+ @voter2.up_vote(@voteable)
111
+ @voteable.up_votes.should == 2
112
+ @voteable.tally.should == 2
113
+ end
114
+
115
+ it "should raise an error for an invalid voteable" do
116
+ invalid_voteable = InvalidVoteableModel.create
117
+ lambda { @voter.up_vote(invalid_voteable) }.should raise_error(RedisVoteable::Exceptions::InvalidVoteableError)
118
+ end
119
+
120
+ it "should check if voter up voted voteable" do
121
+ @voter.up_vote(@voteable)
122
+ @voter.voted?(@voteable).should be_true
123
+ @voter.up_voted?(@voteable).should be_true
124
+ @voter.down_voted?(@voteable).should be_false
125
+ end
126
+ end
127
+
128
+ describe "down vote" do
129
+ it "should decrease down votes of voteable by one" do
130
+ @voteable.down_votes.should == 0
131
+ @voter.down_vote(@voteable)
132
+ @voteable.down_votes.should == 1
133
+ end
134
+
135
+ it "should decrease down votes of voter by one" do
136
+ @voter.down_vote_count.should == 0
137
+ @voter.down_vote(@voteable)
138
+ @voter.down_vote_count.should == 1
139
+ end
140
+
141
+ it "should only allow a voter to down vote a voteable once" do
142
+ @voteable.down_votes.should == 0
143
+ @voter.down_vote(@voteable)
144
+ lambda { @voter.down_vote(@voteable) }.should raise_error(RedisVoteable::Exceptions::AlreadyVotedError)
145
+ @voteable.down_votes.should == 1
146
+ end
147
+
148
+ it "should only allow a voter to down vote a voteable once without raising an error" do
149
+ @voteable.down_votes.should == 0
150
+ @voter.down_vote!(@voteable)
151
+ @voteable.down_votes.should == 1
152
+ lambda {
153
+ @voter.down_vote!(@voteable).should == false
154
+ }.should_not raise_error(RedisVoteable::Exceptions::AlreadyVotedError)
155
+ @voteable.total_votes.should == 1
156
+ end
157
+
158
+ it "should change an up vote to a down vote" do
159
+ @voter.up_vote(@voteable)
160
+ @voteable.up_votes.should == 1
161
+ @voteable.down_votes.should == 0
162
+ @voteable.tally.should == 1
163
+ @voter.up_vote_count.should == 1
164
+ @voter.down_vote_count.should == 0
165
+
166
+ @voter.down_vote(@voteable)
167
+ @voteable.up_votes.should == 0
168
+ @voteable.down_votes.should == 1
169
+ @voteable.tally.should == -1
170
+ @voter.up_vote_count.should == 0
171
+ @voter.down_vote_count.should == 1
172
+ end
173
+
174
+ it "should allow down votes from different voters" do
175
+ @voter2 = VoterModel.create(:name => "Voter 2")
176
+ @voter.down_vote(@voteable)
177
+ @voter2.down_vote(@voteable)
178
+ @voteable.down_votes.should == 2
179
+ @voteable.tally.should == -2
180
+ end
181
+
182
+ it "should raise an error for an invalid voteable" do
183
+ invalid_voteable = InvalidVoteableModel.create
184
+ lambda { @voter.down_vote(invalid_voteable) }.should raise_error(RedisVoteable::Exceptions::InvalidVoteableError)
185
+ end
186
+
187
+ it "should check if voter down voted voteable" do
188
+ @voter.down_vote(@voteable)
189
+ @voter.voted?(@voteable).should be_true
190
+ @voter.up_voted?(@voteable).should be_false
191
+ @voter.down_voted?(@voteable).should be_true
192
+ end
193
+ end
194
+
195
+ describe "clear_vote" do
196
+ it "should decrease the up votes if up voted before" do
197
+ @voter.up_vote(@voteable)
198
+ @voteable.up_votes.should == 1
199
+ @voter.up_vote_count.should == 1
200
+ @voter.clear_vote(@voteable)
201
+ @voteable.up_votes.should == 0
202
+ @voter.up_vote_count.should == 0
203
+ end
204
+
205
+ it "should raise an error if voter didn't vote for the voteable" do
206
+ lambda { @voter.clear_vote(@voteable) }.should raise_error(RedisVoteable::Exceptions::NotVotedError)
207
+ end
208
+
209
+ it "should not raise error if voter didn't vote for the voteable and clear_vote! is called" do
210
+ lambda {
211
+ @voter.clear_vote!(@voteable).should == false
212
+ }.should_not raise_error(RedisVoteable::Exceptions::NotVotedError)
213
+ end
214
+
215
+ it "should raise an error for an invalid voteable" do
216
+ invalid_voteable = InvalidVoteableModel.create
217
+ lambda { @voter.clear_vote(invalid_voteable) }.should raise_error(RedisVoteable::Exceptions::InvalidVoteableError)
218
+ end
219
+ end
220
+ end
data/spec/models.rb ADDED
@@ -0,0 +1,13 @@
1
+ # spec/models.rb
2
+ class VoteableModel < ActiveRecord::Base
3
+ include RedisVoteable
4
+ acts_as_voteable
5
+ end
6
+
7
+ class VoterModel < ActiveRecord::Base
8
+ include RedisVoteable
9
+ acts_as_voter
10
+ end
11
+
12
+ class InvalidVoteableModel < ActiveRecord::Base
13
+ end
data/spec/schema.rb ADDED
@@ -0,0 +1,14 @@
1
+ # spec/schema.rb
2
+ ActiveRecord::Schema.define :version => 0 do
3
+ create_table :voteable_models, :force => true do |t|
4
+ t.string :name
5
+ end
6
+
7
+ create_table :voter_models, :force => true do |t|
8
+ t.string :name
9
+ end
10
+
11
+ create_table :invalid_voteable_models, :force => true do |t|
12
+ t.string :name
13
+ end
14
+ end
@@ -0,0 +1,40 @@
1
+ # spec/spec_helper.rb
2
+ require 'rubygems'
3
+ require 'bundler'
4
+ require 'logger'
5
+ require 'rspec'
6
+ require 'active_record'
7
+ require 'database_cleaner'
8
+
9
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
10
+ require 'redis_voteable'
11
+
12
+ ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + '/debug.log')
13
+ ActiveRecord::Base.configurations = YAML::load_file(File.dirname(__FILE__) + '/database.yml')
14
+ ActiveRecord::Base.establish_connection(ENV['DB'] || 'sqlite3')
15
+
16
+ ActiveRecord::Base.silence do
17
+ ActiveRecord::Migration.verbose = false
18
+
19
+ load(File.dirname(__FILE__) + '/schema.rb')
20
+ load(File.dirname(__FILE__) + '/models.rb')
21
+ end
22
+
23
+ RSpec.configure do |config|
24
+ config.filter_run :focus => true
25
+ config.run_all_when_everything_filtered = true
26
+ config.filter_run_excluding :exclude => true
27
+
28
+ config.mock_with :rspec
29
+
30
+ config.before(:suite) do
31
+ DatabaseCleaner.strategy = :truncation
32
+ DatabaseCleaner.clean
33
+ end
34
+
35
+ config.after(:each) do
36
+ DatabaseCleaner.clean
37
+ #redis = Redis.new
38
+ #redis.flushdb
39
+ end
40
+ end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: redis_voteable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chris Brauchli
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-09-30 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: &70161134771100 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 2.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70161134771100
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70161134770360 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70161134770360
36
+ - !ruby/object:Gem::Dependency
37
+ name: activerecord
38
+ requirement: &70161134769280 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 3.0.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70161134769280
47
+ - !ruby/object:Gem::Dependency
48
+ name: sqlite3-ruby
49
+ requirement: &70161134768500 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.3.0
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70161134768500
58
+ - !ruby/object:Gem::Dependency
59
+ name: bundler
60
+ requirement: &70161134763980 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: 1.0.0
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70161134763980
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: &70161134763320 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ~>
75
+ - !ruby/object:Gem::Version
76
+ version: 2.0.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70161134763320
80
+ - !ruby/object:Gem::Dependency
81
+ name: database_cleaner
82
+ requirement: &70161134762000 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ~>
86
+ - !ruby/object:Gem::Version
87
+ version: 0.6.7
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70161134762000
91
+ description: ! 'A Redis-backed voting extension for Rails applications. '
92
+ email:
93
+ - cbrauchli@gmail.com
94
+ executables: []
95
+ extensions: []
96
+ extra_rdoc_files: []
97
+ files:
98
+ - .gitignore
99
+ - Gemfile
100
+ - MIT-LICENSE
101
+ - Rakefile
102
+ - lib/redis_voteable.rb
103
+ - lib/redis_voteable/exceptions.rb
104
+ - lib/redis_voteable/version.rb
105
+ - lib/redis_voteable/voteable.rb
106
+ - lib/redis_voteable/voter.rb
107
+ - redis_voteable.gemspec
108
+ - redis_voteable.sqlite3
109
+ - spec/database.yml
110
+ - spec/lib/redis_voteable_spec.rb
111
+ - spec/models.rb
112
+ - spec/schema.rb
113
+ - spec/spec_helper.rb
114
+ homepage: http://github.com/cbrauchli/redis_voteable
115
+ licenses: []
116
+ post_install_message:
117
+ rdoc_options: []
118
+ require_paths:
119
+ - lib
120
+ required_ruby_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ none: false
128
+ requirements:
129
+ - - ! '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ requirements: []
133
+ rubyforge_project:
134
+ rubygems_version: 1.8.11
135
+ signing_key:
136
+ specification_version: 3
137
+ summary: Simple vote management with Redis used as the backend.
138
+ test_files:
139
+ - spec/database.yml
140
+ - spec/lib/redis_voteable_spec.rb
141
+ - spec/models.rb
142
+ - spec/schema.rb
143
+ - spec/spec_helper.rb