redis_voteable 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,155 @@
1
+ RedisVoteable
2
+ =============
3
+
4
+ `RedisVoteable` is an extension for building a simple voting system for Rails
5
+ applications. It does not need to be used with any ORM in particular, however
6
+ classes in which it is used must define an `id` method. Also, only the
7
+ ActiveSupport library from Rails is used.
8
+
9
+ Installation
10
+ ------------
11
+
12
+ Add RedisVoteable to your Gemfile
13
+
14
+ gem 'redis_voteable'
15
+
16
+ afterwards execute
17
+
18
+ bundle install
19
+
20
+ and you're done.
21
+
22
+ Configuration
23
+ -------------
24
+
25
+ By default, RedisVoteable's settings are
26
+
27
+ redis_voteable_settings = {
28
+ :host => 'localhost',
29
+ :port => '6379',
30
+ :db => 0,
31
+ :key_prefix => 'vote:'
32
+ }
33
+
34
+ If you'd like to override any of the settings, just add a line like the
35
+ following one to a config file or initializer.
36
+
37
+ RedisVoteable::redis_voteable_settings = {
38
+ :db => 4,
39
+ :key_prefix => 'voterecord:'
40
+ }
41
+
42
+ Usage
43
+ -----
44
+
45
+ Note that in this example an ActiveRecord model is used, however, any other
46
+ ORM will do, as long as the object defines an `id` method. Also, for a couple
47
+ of methods, a `find` or `get` method is required. I'll point those out below.
48
+
49
+ # Specify a voteable model.
50
+ class Option < ActiveRecord::Base
51
+ include RedisVoteable
52
+ acts_as_voteable
53
+ end
54
+
55
+ # Specify a voter model.
56
+ class User < ActiveRecord::Base
57
+ include RedisVoteable
58
+ acts_as_voter
59
+ end
60
+
61
+ # Votes up the question by the user.
62
+ # If the user already voted the question up then an AlreadyVotedError is raised.
63
+ # If the same user already voted the question down then the vote is changed to an up vote.
64
+ user.up_vote(question)
65
+
66
+ # Votes the question up, but without raising an AlreadyVotedError when the user
67
+ # already voted the question up (it just ignores the vote and returns false).
68
+ user.up_vote!(question)
69
+
70
+ user.down_vote(question)
71
+ user.down_vote!(question)
72
+
73
+ # Clears a already done vote by an user.
74
+ # If the user didn't vote for the question then a NotVotedError is raised.
75
+ user.clear_vote(question)
76
+
77
+ # Does not raise a NotVotedError if the user didn't vote for the question
78
+ # (it just ignores the unvote and returns false).
79
+ user.clear_vote!(question)
80
+
81
+ # If you'd prefer, unvote is an alias for clear_vote.
82
+ user.unvote(question)
83
+ user.unvote!(question)
84
+
85
+ # The number of up votes for this question.
86
+ question.up_votes
87
+
88
+ # The number of down votes for this question.
89
+ question.down_votes
90
+
91
+ # The total number of votes for this question.
92
+ question.total_votes
93
+
94
+ # The number of up votes the user has cast.
95
+ user.up_votes
96
+
97
+ # The number of down votes the user has cat.
98
+ user.down_votes
99
+
100
+ # The total number of votes the user has cast.
101
+ user.total_votes
102
+
103
+ # up votes - down votes (may also be negative if there are more down votes than up votes)
104
+ question.tally
105
+
106
+ # The lower bound of the Wilson confidence interval. The default value for
107
+ # z is 1.4395314800662002, which estimates bounds with 85% confidence.
108
+ # The value can be modified in lib/voteable.rb.
109
+ # See http://en.wikipedia.org/wiki/Binomial_proportion_confidence_interval#Wilson_score_interval
110
+ # and: http://www.evanmiller.org/how-not-to-sort-by-average-rating.html
111
+ question.confidence
112
+
113
+ # The upper bound of the Wilson confidence interval.
114
+ question.confidence(:upper)
115
+
116
+ # Returns true if the question was voted by the user
117
+ user.voted?(question)
118
+
119
+ # Returns true if the question was up voted by the user, false otherwise
120
+ user.up_voted?(question)
121
+
122
+ # Returns true if the question was down voted by the user, false otherwise
123
+ user.down_voted?(question)
124
+
125
+ # Returns :up, :down, or nil depending on how the user voted
126
+ user.vote_value?(question)
127
+
128
+ # Access voted voteables through voter (slow)
129
+ voteable = user.voteables.first
130
+ voteable.up_voted?(user) # true if up voted by user, false otherwise
131
+ voteable.vote_value?(user) # returns :up, :down, or nil if user didn't vote on voteable
132
+
133
+ # Access votings through voteable (slow)
134
+ voter = question.voters.first
135
+ voter.up_voted?(question) # true if up voted question, false otherwise
136
+ voter.vote_value?(question) # returns :up, :down, or nil if voter didn't vote on question
137
+
138
+ TO DO:
139
+ ------
140
+
141
+ * Add support for getting raw voteable and voter arrays, which would save time
142
+ by not instantiating every object.
143
+
144
+ * (Related to the next point.) Automatic ranking of voteables. Consider using a sorted set in redis.
145
+
146
+ * Add some sort of namespacing/grouping support. That way, `voteables` could
147
+ be grouped and ranked within their group. An example use case would be users
148
+ voting on many options to multiple questions. The options would be grouped
149
+ by the question to which they belonged and could be easily ranked for a
150
+ question. Could use sorted set to store grouping and rankings within
151
+ groupings, but that may not be practical. Could also store the keys of all
152
+ of the voteables in a grouping in a set on Redis and rank them in Ruby,
153
+ which makes it possible to rank by Wilson confidence score.
154
+
155
+ Copyright © 2011 Chris Brauchli, released under the MIT license
@@ -18,10 +18,15 @@ module RedisVoteable
18
18
  :host => 'localhost',
19
19
  :port => '6379',
20
20
  :db => 0,
21
- :key_prefix => "vote:",
21
+ :key_prefix => 'vote:',
22
22
  }
23
+
24
+ def redis_voteable_settings=(new_settings)
25
+ @@redis_voteable_settings.update(new_settings)
26
+ end
27
+
23
28
  mattr_accessor :redis
24
- @@redis = Redis.new(@@redis_voteable_settings)
29
+ # @@redis =
25
30
 
26
31
  def prefixed(sid)
27
32
  "#{@@redis_voteable_settings[:key_prefix]}#{sid}"
@@ -47,6 +52,7 @@ module RedisVoteable
47
52
  # acts_as_voteable
48
53
  # end
49
54
  def acts_as_voteable
55
+ RedisVoteable.redis ||= Redis.new(RedisVoteable.redis_voteable_settings)
50
56
  include Voteable
51
57
  end
52
58
 
@@ -57,6 +63,7 @@ module RedisVoteable
57
63
  # acts_as_voter
58
64
  # end
59
65
  def acts_as_voter
66
+ RedisVoteable.redis ||= Redis.new(RedisVoteable.redis_voteable_settings)
60
67
  include Voter
61
68
  end
62
69
  end
@@ -1,3 +1,3 @@
1
1
  module RedisVoteable
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -47,6 +47,29 @@ module RedisVoteable
47
47
  nil
48
48
  end
49
49
 
50
+ # Returns true if the voter voted on the +voteable+.
51
+ def voted?(voter)
52
+ up_voted?(voter) || down_voted?(voter)
53
+ end
54
+
55
+ # Returns :up, :down, or nil.
56
+ def vote_value?(voter)
57
+ return :up if up_voted?(voter)
58
+ return :down if down_voted?(voter)
59
+ return nil
60
+ end
61
+
62
+ # Returns true if the voter up voted the +voteable+.
63
+ def up_voted?(voter)
64
+ redis.sismember prefixed("#{class_key(voter)}:#{UP_VOTES}"), "#{class_key(self)}"
65
+ end
66
+
67
+ # Returns true if the voter down voted the +voteable+.
68
+ def down_voted?(voter)
69
+ redis.sismember prefixed("#{class_key(voter)}:#{DOWN_VOTES}"), "#{class_key(self)}"
70
+ end
71
+
72
+
50
73
  # Returns an array of objects that are +voter+s that voted on this
51
74
  # +voteable+. This method can be very slow, as it constructs each
52
75
  # object. Also, it assumes that each object has a +find(id)+ method
@@ -59,7 +82,14 @@ module RedisVoteable
59
82
  voters = redis.smembers prefixed("#{class_key(self)}:#{UP_VOTERS}")
60
83
  voters.map do |voter|
61
84
  tmp = voter.split(':')
62
- tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
85
+ klass = tmp[0, tmp.length-1].join(':').constantize
86
+ if klass.respond_to?('find')
87
+ klass.find(tmp.last)
88
+ elsif klass.respond_to?('get')
89
+ klass.get(tmp.last)
90
+ else
91
+ nil
92
+ end
63
93
  end
64
94
  end
65
95
 
@@ -67,7 +97,14 @@ module RedisVoteable
67
97
  voters = redis.smembers prefixed("#{class_key(self)}:#{DOWN_VOTERS}")
68
98
  voters.map do |voter|
69
99
  tmp = voter.split(':')
70
- tmp[0, tmp.length-1].join(':').constantize.find(tmp.last)
100
+ klass = tmp[0, tmp.length-1].join(':').constantize
101
+ if klass.respond_to?('find')
102
+ klass.find(tmp.last)
103
+ elsif klass.respond_to?('get')
104
+ klass.get(tmp.last)
105
+ else
106
+ nil
107
+ end
71
108
  end
72
109
  end
73
110
 
@@ -100,7 +100,8 @@ module RedisVoteable
100
100
  raise Exceptions::NotVotedError unless r[0] == 1 || r[2] == 1
101
101
  true
102
102
  end
103
-
103
+ alias :unvote :clear_vote
104
+
104
105
  # Clears an already done vote on a +voteable+, but doesn't raise an error if
105
106
  # the voteable was not voted. It ignores the unvote then.
106
107
  def clear_vote!(voteable)
@@ -111,6 +112,7 @@ module RedisVoteable
111
112
  return false
112
113
  end
113
114
  end
115
+ alias :unvote! :clear_vote!
114
116
 
115
117
  # Return the total number of votes a voter has cast.
116
118
  def total_votes()
@@ -153,23 +155,37 @@ module RedisVoteable
153
155
  # +voteable+. This method can be very slow, as it constructs each
154
156
  # object. Also, it assumes that each object has a +find(id)+ method
155
157
  # defined (e.g., any ActiveRecord object).
156
- def votings
157
- up_votings | down_votings
158
+ def voteables
159
+ up_voteables | down_voteables
158
160
  end
159
161
 
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)
162
+ def up_voteables
163
+ voteables = redis.smembers prefixed("#{class_key(self)}:#{UP_VOTES}")
164
+ voteables.map do |voteable|
165
+ tmp = voteable.split(':')
166
+ klass = tmp[0, tmp.length-1].join(':').constantize
167
+ if klass.respond_to?('find')
168
+ klass.find(tmp.last)
169
+ elsif klass.respond_to?('get')
170
+ klass.get(tmp.last)
171
+ else
172
+ nil
173
+ end
165
174
  end
166
175
  end
167
176
 
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)
177
+ def down_voteables
178
+ voteables = redis.smembers prefixed("#{class_key(self)}:#{DOWN_VOTES}")
179
+ voteables.map do |voteable|
180
+ tmp = voteable.split(':')
181
+ klass = tmp[0, tmp.length-1].join(':').constantize
182
+ if klass.respond_to?('find')
183
+ klass.find(tmp.last)
184
+ elsif klass.respond_to?('get')
185
+ klass.get(tmp.last)
186
+ else
187
+ nil
188
+ end
173
189
  end
174
190
  end
175
191
 
@@ -40,7 +40,7 @@ describe "Redis Voteable" do
40
40
  @voter.up_votes == 0
41
41
  @voter.up_vote(@voteable)
42
42
  @voter.up_votes == 1
43
- @voter.votings[0].should == @voteable
43
+ @voter.voteables[0].should == @voteable
44
44
  end
45
45
 
46
46
  it "voteable should have down vote votings" do
@@ -55,7 +55,7 @@ describe "Redis Voteable" do
55
55
  @voter.down_votes.should == 0
56
56
  @voter.down_vote(@voteable)
57
57
  @voter.down_votes.should == 1
58
- @voter.votings[0].should == @voteable
58
+ @voter.voteables[0].should == @voteable
59
59
  end
60
60
 
61
61
  it "voteable should calculate correct percentages" do
@@ -149,6 +149,12 @@ describe "Redis Voteable" do
149
149
  @voter.up_voted?(@voteable).should be_true
150
150
  @voter.down_voted?(@voteable).should be_false
151
151
  end
152
+
153
+ it "should have up votings" do
154
+ @voter.up_vote(@voteable)
155
+ @voter.voteables[0].up_voted?(@voter).should be_true
156
+ @voter.voteables[0].down_voted?(@voter).should be_false
157
+ end
152
158
  end
153
159
 
154
160
  describe "down vote" do
@@ -216,6 +222,12 @@ describe "Redis Voteable" do
216
222
  @voter.up_voted?(@voteable).should be_false
217
223
  @voter.down_voted?(@voteable).should be_true
218
224
  end
225
+
226
+ it "should have down votings" do
227
+ @voter.down_vote(@voteable)
228
+ @voter.voteables[0].up_voted?(@voter).should be_false
229
+ @voter.voteables[0].down_voted?(@voter).should be_true
230
+ end
219
231
  end
220
232
 
221
233
  describe "clear_vote" do
@@ -227,6 +239,15 @@ describe "Redis Voteable" do
227
239
  @voteable.up_votes.should == 0
228
240
  @voter.up_votes.should == 0
229
241
  end
242
+
243
+ it "should have working aliases" do
244
+ @voter.up_vote(@voteable)
245
+ @voteable.up_votes.should == 1
246
+ @voter.up_votes.should == 1
247
+ @voter.unvote(@voteable)
248
+ @voteable.up_votes.should == 0
249
+ @voter.up_votes.should == 0
250
+ end
230
251
 
231
252
  it "should raise an error if voter didn't vote for the voteable" do
232
253
  lambda { @voter.clear_vote(@voteable) }.should raise_error(RedisVoteable::Exceptions::NotVotedError)
@@ -34,7 +34,7 @@ RSpec.configure do |config|
34
34
 
35
35
  config.after(:each) do
36
36
  DatabaseCleaner.clean
37
- redis = Redis.new
38
- redis.flushdb
37
+ # redis = Redis.new
38
+ # redis.flushdb
39
39
  end
40
40
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis_voteable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-09-30 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: &70115573519260 !ruby/object:Gem::Requirement
16
+ requirement: &70257025093760 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 2.2.0
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70115573519260
24
+ version_requirements: *70257025093760
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: activesupport
27
- requirement: &70115573518580 !ruby/object:Gem::Requirement
27
+ requirement: &70257025093220 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 3.0.0
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70115573518580
35
+ version_requirements: *70257025093220
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: activerecord
38
- requirement: &70115573517840 !ruby/object:Gem::Requirement
38
+ requirement: &70257025092720 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 3.0.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70115573517840
46
+ version_requirements: *70257025092720
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: sqlite3-ruby
49
- requirement: &70115573510500 !ruby/object:Gem::Requirement
49
+ requirement: &70257025092160 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.3.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70115573510500
57
+ version_requirements: *70257025092160
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: bundler
60
- requirement: &70115573509880 !ruby/object:Gem::Requirement
60
+ requirement: &70257025091620 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.0.0
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70115573509880
68
+ version_requirements: *70257025091620
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rspec
71
- requirement: &70115573508880 !ruby/object:Gem::Requirement
71
+ requirement: &70257025090940 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ~>
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: 2.0.0
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70115573508880
79
+ version_requirements: *70257025090940
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: database_cleaner
82
- requirement: &70115573508360 !ruby/object:Gem::Requirement
82
+ requirement: &70257025089860 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ~>
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: 0.6.7
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70115573508360
90
+ version_requirements: *70257025089860
91
91
  description: ! 'A Redis-backed voting extension for Rails applications. '
92
92
  email:
93
93
  - cbrauchli@gmail.com
@@ -98,6 +98,7 @@ files:
98
98
  - .gitignore
99
99
  - Gemfile
100
100
  - MIT-LICENSE
101
+ - README.md
101
102
  - Rakefile
102
103
  - lib/redis_voteable.rb
103
104
  - lib/redis_voteable/exceptions.rb
@@ -105,7 +106,6 @@ files:
105
106
  - lib/redis_voteable/voteable.rb
106
107
  - lib/redis_voteable/voter.rb
107
108
  - redis_voteable.gemspec
108
- - redis_voteable.sqlite3
109
109
  - spec/database.yml
110
110
  - spec/lib/redis_voteable_spec.rb
111
111
  - spec/models.rb
Binary file