redis_voteable 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/.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