acts_as_votable 0.0.5 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
File without changes
data/Gemfile CHANGED
File without changes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acts_as_votable (0.0.4)
4
+ acts_as_votable (0.0.5)
5
5
  rails (>= 3.0.0)
6
6
 
7
7
  GEM
data/README.markdown ADDED
@@ -0,0 +1,200 @@
1
+ ### Major Updates
2
+
3
+ Version 0.1.0 introduces new and refactored function calls that improve the
4
+ natural language syntax of this gem. Certain calls that were compatible with
5
+ version 0.0.5 will now be broken. Remember to specify a the version in your
6
+ Gemfile to prevent functionality breakdowns between versions.
7
+
8
+ In version 0.1.0 functions like ``@post.votes`` return an array of all of the vote
9
+ records for @post. In order to count the number of votes simply use
10
+ ``@post.votes.size`` now.
11
+
12
+ - - -
13
+
14
+ # Acts As Votable (aka Acts As Likeable)
15
+
16
+ Acts As Votable is a Ruby Gem specifically written for Rails/ActiveRecord models.
17
+ The main goals of this gem are:
18
+
19
+ - Allow any model to be voted on, like/dislike, upvote/downvote, etc.
20
+ - Allow any model to vote. In other words, votes do not have to come from a user,
21
+ they can come from any model (such as a Group or Team).
22
+ - Provide an easy to write natural language syntax.
23
+
24
+ ## Installation
25
+
26
+ ### Rails 3
27
+
28
+ Just add the following to your Gemfile.
29
+
30
+ gem 'acts_as_votable'
31
+
32
+ And follow that up with a ``bundle install``.
33
+
34
+ ### Database Migrations
35
+
36
+ Acts As Votable uses a votes table to store all voting information. To
37
+ generate and run the migration just use.
38
+
39
+ rails generate acts_as_votable:migration
40
+ rake db:migrate
41
+
42
+ You will get a performance increase by adding in cached columns to your model's
43
+ tables. You will have to do this manually through your own migrations. See the
44
+ caching section of this document for more information.
45
+
46
+ ## Usage
47
+
48
+ ### Votable Models
49
+
50
+ class Post < ActiveRecord::Base
51
+ acts_as_votable
52
+ end
53
+
54
+ @post = Post.new(:name => 'my post!')
55
+ @post.save
56
+
57
+ @post.liked_by @user
58
+ @post.votes.size # => 1
59
+
60
+ ### Like/Dislike Yes/No Up/Down
61
+
62
+ Here are some voting examples. All of these calls are valid and acceptable. The
63
+ more natural calls are the first few examples.
64
+
65
+ @post.liked_by @user1
66
+ @post.downvote_from @user2
67
+ @post.vote :voter => @user3
68
+ @post.vote :voter => @user4, :vote => 'bad'
69
+ @post.vote :voter => @user5, :vote => 'like'
70
+
71
+
72
+ By default all votes are positive, so @user3 has cast a 'good' vote for @post.
73
+
74
+ @user1, @user3, and @user5 all voted in favor of @post.
75
+
76
+ @user2 and @user4 on the other had has voted against @post.
77
+
78
+
79
+ Just about any word works for casting a vote in favor or against post. Up/Down,
80
+ Like/Dislike, Positive/Negative... the list goes on-and-on. Boolean flags ``true`` and
81
+ ``false`` are also applicable.
82
+
83
+ Revisiting the previous example of code.
84
+
85
+ # positive votes
86
+ @post.liked_by @user1
87
+ @post.vote :voter => @user3
88
+ @post.vote :voter => @user5, :vote => 'like'
89
+
90
+ # negative votes
91
+ @post.downvote_from @user2
92
+ @post.vote :voter => @user2, :vote => 'bad'
93
+
94
+ # tally them up!
95
+ @post.votes.size # => 5
96
+ @post.likes.size # => 3
97
+ @post.upvotes.size # => 3
98
+ @post.dislikes.size # => 2
99
+ @post.downvotes.size # => 2
100
+
101
+ ### The Voter
102
+
103
+ You can have your voters ``acts_as_voter`` to provide some reserve functionality.
104
+
105
+ class User < ActiveRecord::Base
106
+ acts_as_voter
107
+ end
108
+
109
+ @user.likes @article
110
+
111
+ @article.votes.size # => 1
112
+ @article.likes.size # => 1
113
+ @article.downvotes.size # => 0
114
+
115
+ To check if a voter has voted on a model, you can use ``voted_for?``. You can
116
+ check how the voter voted by using ``voted_as_when_voted_for``.
117
+
118
+ @user.likes @comment1
119
+ @user.up_votes @comment2
120
+ # user has not voted on @comment3
121
+
122
+ @user.voted_for? @comment1 # => true
123
+ @user.voted_for? @comment2 # => true
124
+ @user.voted_for? @comment3 # => false
125
+
126
+ @user.voted_as_when_voted_for @comment1 # => true, he liked it
127
+ @user.voted_as_when_voted_for @comment2 # => false, he didnt like it
128
+ @user.voted_as_when_voted_for @comment3 # => nil, he has yet to vote
129
+
130
+ ### Registered Votes
131
+
132
+ Voters can only vote once per model. In this example the 2nd vote does not count
133
+ because @user has already voted for @shoe.
134
+
135
+ @user.likes @shoe
136
+ @user.upvotes @shoe
137
+
138
+ @shoe.votes # => 1
139
+ @shoe.likes # => 1
140
+
141
+ To check if a vote counted, or registered, use vote_registered? on your model
142
+ directly after voting. For example:
143
+
144
+ @hat.liked_by @user
145
+ @hat.vote_registered? # => true
146
+
147
+ @hat.liked_by => @user
148
+ @hat.vote_registered? # => false, because @user has already voted this way
149
+
150
+ @hat.disliked_by @user
151
+ @hat.vote_registered? # => true, because user changed their vote
152
+
153
+ @hat.votes.size # => 1
154
+ @hat.positives.size # => 0
155
+ @hat.negatives.size # => 1
156
+
157
+ ## Caching
158
+
159
+ To speed up perform you can add cache columns to your votable model's table. These
160
+ columns will automatically be updated after each vote. For example, if we wanted
161
+ to speed up @post we would use the following migration:
162
+
163
+ class AddCachedVotesToPosts < ActiveRecord::Migration
164
+ def self.up
165
+ add_column :posts, :cached_votes_total, :integer, :default => 0
166
+ add_column :posts, :cached_votes_up, :integer, :default => 0
167
+ add_column :posts, :cached_votes_down, :integer, :default => 0
168
+ add_index :posts, :cached_votes_total
169
+ add_index :posts, :cached_votes_up
170
+ add_index :posts, :cached_votes_down
171
+ end
172
+
173
+ def self.down
174
+ remove_column :posts, :cached_votes_total
175
+ remove_column :posts, :cached_votes_up
176
+ remove_column :posts, :cached_votes_down
177
+ end
178
+ end
179
+
180
+ ## Testing
181
+
182
+ All tests follow the RSpec format and are located in the spec directory
183
+
184
+ ## Thanks
185
+
186
+ A huge thank you to Michael Bleigh and his Acts-As-Taggable-On gem. I learned
187
+ how to write gems by following his source code.
188
+
189
+ ## TODO
190
+
191
+ - Smarter language syntax. Example: ``@user.likes`` will return all of the votables
192
+ that the user likes, while ``@user.likes @model`` will cast a vote for @model by
193
+ @user.
194
+
195
+ - Need to test a model that is votable as well as a voter
196
+
197
+ - The aliased functions are referred to by using the terms 'up/down' amd/or
198
+ 'true/false'. Need to come up with guidelines for naming these function.
199
+
200
+ - Create more aliases. Specifically for counting votes and finding votes.
data/Rakefile CHANGED
File without changes
File without changes
File without changes
@@ -3,7 +3,7 @@ module ActsAsVotable::Alias
3
3
  def self.words_to_alias object, words, call_function
4
4
  words.each do |word|
5
5
  if word.is_a?(String)
6
- function = word.pluralize.to_sym
6
+ function = word.to_sym
7
7
  if !object.respond_to?(function)
8
8
  object.send(:alias_method, function, call_function)
9
9
  end
File without changes
@@ -20,8 +20,16 @@ module ActsAsVotable::Init
20
20
  end
21
21
 
22
22
  # aliasing
23
- ActsAsVotable::Alias::words_to_alias self, ActsAsVotable::Vote.true_votes, :count_votes_true
24
- ActsAsVotable::Alias::words_to_alias self, ActsAsVotable::Vote.false_votes, :count_votes_false
23
+
24
+ # voting
25
+ ActsAsVotable::Alias::words_to_alias self, %w(up_by upvote_by like_by liked_by vote_by), :vote_up
26
+ ActsAsVotable::Alias::words_to_alias self, %w(up_from upvote_from like_from liked_from vote_from), :vote_up
27
+ ActsAsVotable::Alias::words_to_alias self, %w(down_by downvote_by dislike_by disliked_by), :vote_down
28
+ ActsAsVotable::Alias::words_to_alias self, %w(down_from downvote_from dislike_from disliked_from), :vote_down
29
+
30
+ # finding
31
+ ActsAsVotable::Alias::words_to_alias self, %w(true_votes ups upvotes likes positives), :up_votes
32
+ ActsAsVotable::Alias::words_to_alias self, %w(false_votes downs downvotes dislikes negatives), :down_votes
25
33
 
26
34
  end
27
35
 
@@ -18,6 +18,9 @@ module ActsAsVotable::Init
18
18
 
19
19
  include ActsAsVotable::Voter
20
20
 
21
+ ActsAsVotable::Alias::words_to_alias self, %w(likes upvotes up_votes), :vote_up_for
22
+ ActsAsVotable::Alias::words_to_alias self, %w(dislikes downvotes down_votes), :vote_down_for
23
+
21
24
  end
22
25
 
23
26
  end
@@ -1,3 +1,3 @@
1
1
  module ActsAsVotable
2
- VERSION = "0.0.5"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -25,6 +25,7 @@ module ActsAsVotable
25
25
  }
26
26
  end
27
27
 
28
+ # voting
28
29
  def vote args = {}
29
30
 
30
31
  options = ActsAsVotable::Vote.default_voting_args.merge(args)
@@ -67,6 +68,14 @@ module ActsAsVotable
67
68
 
68
69
  end
69
70
 
71
+ def vote_up voter
72
+ self.vote :voter => voter, :vote => true
73
+ end
74
+
75
+ def vote_down voter
76
+ self.vote :voter => voter, :vote => false
77
+ end
78
+
70
79
  # caching
71
80
  def update_cached_votes
72
81
 
@@ -77,11 +86,11 @@ module ActsAsVotable
77
86
  end
78
87
 
79
88
  if self.respond_to?(:cached_votes_up=)
80
- updates[:cached_votes_up] = count_votes_true(true)
89
+ updates[:cached_votes_up] = count_votes_up(true)
81
90
  end
82
91
 
83
92
  if self.respond_to?(:cached_votes_down=)
84
- updates[:cached_votes_down] = count_votes_false(true)
93
+ updates[:cached_votes_down] = count_votes_down(true)
85
94
  end
86
95
 
87
96
  self.update_attributes(updates) if updates.size > 0
@@ -93,27 +102,37 @@ module ActsAsVotable
93
102
  def find_votes extra_conditions = {}
94
103
  ActsAsVotable::Vote.find(:all, :conditions => default_conditions.merge(extra_conditions))
95
104
  end
105
+ alias :votes :find_votes
106
+
107
+ def up_votes
108
+ find_votes(:vote_flag => true)
109
+ end
110
+
111
+ def down_votes
112
+ find_votes(:vote_flag => false)
113
+ end
114
+
96
115
 
116
+ # counting
97
117
  def count_votes_total skip_cache = false
98
118
  if !skip_cache && self.respond_to?(:cached_votes_total)
99
119
  return self.send(:cached_votes_total)
100
120
  end
101
121
  find_votes.size
102
122
  end
103
- alias :votes :count_votes_total
104
123
 
105
- def count_votes_true skip_cache = false
124
+ def count_votes_up skip_cache = false
106
125
  if !skip_cache && self.respond_to?(:cached_votes_up)
107
126
  return self.send(:cached_votes_up)
108
127
  end
109
- find_votes(:vote_flag => true).size
128
+ up_votes.size
110
129
  end
111
130
 
112
- def count_votes_false skip_cache = false
131
+ def count_votes_down skip_cache = false
113
132
  if !skip_cache && self.respond_to?(:cached_votes_down)
114
133
  return self.send(:cached_votes_down)
115
134
  end
116
- find_votes(:vote_flag => false).size
135
+ down_votes.size
117
136
  end
118
137
 
119
138
  # voters
@@ -13,11 +13,11 @@ module ActsAsVotable
13
13
  validates_presence_of :voter_id
14
14
 
15
15
  def self.true_votes
16
- ['up', 'upvote', 'like', 'yes', 'good', 'true', 1, true]
16
+ ['up', 'upvote', 'like', 'liked', 'positive', 'yes', 'good', 'true', 1, true]
17
17
  end
18
18
 
19
19
  def self.false_votes
20
- ['down', 'downvote', 'dislike', 'no', 'bad', 'false', 0, false]
20
+ ['down', 'downvote', 'dislike', 'disliked', 'negative', 'no', 'bad', 'false', 0, false]
21
21
  end
22
22
 
23
23
  ##
@@ -18,6 +18,20 @@ module ActsAsVotable
18
18
  }
19
19
  end
20
20
 
21
+ # voting
22
+ def vote args
23
+ args[:votable].vote args.merge({:voter => self})
24
+ end
25
+
26
+ def vote_up_for model
27
+ vote :votable => model, :vote => true
28
+ end
29
+
30
+ def vote_down_for model
31
+ vote :votable => model, :vote => false
32
+ end
33
+
34
+ # results
21
35
  def voted_on? votable
22
36
  votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.name)
23
37
  votes.size > 0
@@ -31,16 +45,30 @@ module ActsAsVotable
31
45
  end
32
46
  alias :voted_as_when_voting_for :voted_as_when_voting_on
33
47
 
48
+
34
49
  def find_votes extra_conditions = {}
35
50
  ActsAsVotable::Vote.find(:all, :conditions => default_conditions.merge(extra_conditions))
36
51
  end
52
+ alias :votes :find_votes
37
53
 
38
- def total_votes_for_class klass
39
- find_votes({:votable_type => klass.name}).size
54
+ def find_up_votes
55
+ find_votes :vote_flag => true
40
56
  end
41
57
 
42
- def vote args
43
- args[:votable].vote args.merge({:voter => self})
58
+ def find_down_votes
59
+ find_votes :vote_flag => false
60
+ end
61
+
62
+ def find_votes_for_class klass, extra_conditions = {}
63
+ find_votes extra_conditions.merge({:votable_type => klass.name})
64
+ end
65
+
66
+ def find_up_votes_for_class klass
67
+ find_votes_for_class klass, :vote_flag => true
68
+ end
69
+
70
+ def find_down_votes_for_class klass
71
+ find_votes_for_class klass, :vote_flag => false
44
72
  end
45
73
 
46
74
  end
data/spec/alias_spec.rb CHANGED
@@ -5,27 +5,60 @@ describe ActsAsVotable::Alias do
5
5
 
6
6
  before(:each) do
7
7
  clean_database
8
- @votable = Votable.new(:name => 'votable with aliases')
9
- @votable.save
10
-
11
- @voter = Voter.new(:name => 'a voter')
12
- @voter.save
13
8
  end
14
9
 
15
- it "should alias a bunch of functions" do
16
- @votable.respond_to?(:upvotes).should be true
17
- @votable.respond_to?(:ups).should be true
18
- @votable.respond_to?(:dislikes).should be true
19
- end
10
+ describe "votable models" do
11
+
12
+ before(:each) do
13
+ clean_database
14
+ @votable = Votable.new(:name => 'votable with aliases')
15
+ @votable.save
16
+
17
+ @voter = Voter.new(:name => 'a voter')
18
+ @voter.save
19
+ end
20
+
21
+ it "should alias a bunch of functions" do
22
+
23
+ # voting
24
+ @votable.respond_to?(:disliked_by).should be true
25
+ @votable.respond_to?(:up_from).should be true
26
+
27
+ # results
28
+ @votable.respond_to?(:upvotes).should be true
29
+ @votable.respond_to?(:ups).should be true
30
+ @votable.respond_to?(:dislikes).should be true
20
31
 
21
- it "should only alias voting words that are strings" do
22
- @votable.respond_to?('1s'.to_sym).should be false
32
+ end
33
+
34
+ it "should add callable functions" do
35
+ @votable.vote :voter => @voter
36
+ @votable.likes.size.should == 1
37
+ end
23
38
  end
24
39
 
25
- it "should add callable functions" do
26
- @votable.vote :voter => @voter
27
- @votable.likes.should == 1
40
+ describe "voter models" do
41
+
42
+ before(:each) do
43
+ clean_database
44
+ @votable = Votable.new(:name => 'a votable')
45
+ @votable.save
46
+
47
+ @voter = Voter.new(:name => 'a voter with aliases')
48
+ @voter.save
49
+ end
50
+
51
+ it "should alias a bunch of functions" do
52
+ @voter.respond_to?(:upvotes).should be true
53
+ @voter.respond_to?(:dislikes).should be true
54
+ end
55
+
56
+ it "should add callable functions" do
57
+ @voter.likes @votable
58
+ @votable.likes.size.should == 1
59
+ end
28
60
  end
29
61
 
30
62
 
63
+
31
64
  end
data/spec/spec_helper.rb CHANGED
File without changes
data/spec/votable_spec.rb CHANGED
@@ -35,31 +35,41 @@ describe ActsAsVotable::Votable do
35
35
 
36
36
  it "should have one vote when saved" do
37
37
  @votable.vote :voter => @voter, :vote => 'yes'
38
- @votable.votes.should == 1
38
+ @votable.votes.size.should == 1
39
39
  end
40
40
 
41
41
  it "should have one vote when voted on twice by the same person" do
42
42
  @votable.vote :voter => @voter, :vote => 'yes'
43
43
  @votable.vote :voter => @voter, :vote => 'no'
44
- @votable.votes.should == 1
44
+ @votable.votes.size.should == 1
45
+ end
46
+
47
+ it "should be callable with vote_up" do
48
+ @votable.vote_up @voter
49
+ @votable.up_votes.first.voter.should == @voter
50
+ end
51
+
52
+ it "should be callable with vote_down" do
53
+ @votable.vote_down @voter
54
+ @votable.down_votes.first.voter.should == @voter
45
55
  end
46
56
 
47
57
  it "should have 2 votes when voted on once by two different people" do
48
58
  @votable.vote :voter => @voter
49
59
  @votable.vote :voter => @voter2
50
- @votable.votes.should == 2
60
+ @votable.votes.size.should == 2
51
61
  end
52
62
 
53
63
  it "should have one true vote" do
54
64
  @votable.vote :voter => @voter
55
65
  @votable.vote :voter => @voter2, :vote => 'dislike'
56
- @votable.count_votes_true.should == 1
66
+ @votable.up_votes.size.should == 1
57
67
  end
58
68
 
59
69
  it "should have 2 false votes" do
60
70
  @votable.vote :voter => @voter, :vote => 'no'
61
71
  @votable.vote :voter => @voter2, :vote => 'dislike'
62
- @votable.count_votes_false.should == 2
72
+ @votable.down_votes.size.should == 2
63
73
  end
64
74
 
65
75
  it "should have been voted on by voter2" do
@@ -102,6 +112,7 @@ describe ActsAsVotable::Votable do
102
112
  end
103
113
 
104
114
 
115
+
105
116
  describe "with cached votes" do
106
117
 
107
118
  before(:each) do
@@ -142,19 +153,19 @@ describe ActsAsVotable::Votable do
142
153
  it "should select from cached total votes if there a total column" do
143
154
  @votable_cache.vote :voter => @voter
144
155
  @votable_cache.cached_votes_total = 50
145
- @votable_cache.votes.should == 50
156
+ @votable_cache.count_votes_total.should == 50
146
157
  end
147
158
 
148
159
  it "should select from cached up votes if there is an up vote column" do
149
160
  @votable_cache.vote :voter => @voter
150
161
  @votable_cache.cached_votes_up = 50
151
- @votable_cache.count_votes_true.should == 50
162
+ @votable_cache.count_votes_up.should == 50
152
163
  end
153
164
 
154
165
  it "should select from cached down votes if there is a down vote column" do
155
166
  @votable_cache.vote :voter => @voter, :vote => 'false'
156
167
  @votable_cache.cached_votes_down = 50
157
- @votable_cache.count_votes_false.should == 50
168
+ @votable_cache.count_votes_down.should == 50
158
169
  end
159
170
 
160
171
  end
data/spec/vote_spec.rb CHANGED
File without changes
data/spec/voter_spec.rb CHANGED
@@ -55,17 +55,54 @@ describe ActsAsVotable::Voter do
55
55
  @voter.voted_as_when_voting_on(@votable).should be nil
56
56
  end
57
57
 
58
- it "should return the total number of votes cast against a given class" do
59
- @votable.vote :voter => @voter
60
- @votable2.vote :voter => @voter
61
- @voter.total_votes_for_class(Votable).should == 2
62
- end
63
-
64
58
  it "should provide reserve functionality, voter can vote on votable" do
65
59
  @voter.vote :votable => @votable, :vote => 'bad'
66
60
  @voter.voted_as_when_voting_on(@votable).should be false
67
61
  end
68
62
 
63
+ it "should allow the voter to vote up a model" do
64
+ @voter.vote_up_for @votable
65
+ @votable.up_votes.first.voter.should == @voter
66
+ end
67
+
68
+ it "should allow the voter to vote down a model" do
69
+ @voter.vote_down_for @votable
70
+ @votable.down_votes.first.voter.should == @voter
71
+ end
72
+
73
+ it "should get all of the voters votes" do
74
+ @voter.vote_up_for @votable
75
+ @voter.find_votes.size.should == 1
76
+ end
77
+
78
+ it "should get all of the voters up votes" do
79
+ @voter.vote_up_for @votable
80
+ @voter.find_up_votes.size.should == 1
81
+ end
82
+
83
+ it "should get all of the voters down votes" do
84
+ @voter.vote_down_for @votable
85
+ @voter.find_down_votes.size.should == 1
86
+ end
87
+
88
+ it "should get all of the votes votes for a class" do
89
+ @votable.vote :voter => @voter
90
+ @votable2.vote :voter => @voter, :vote => false
91
+ @voter.find_votes_for_class(Votable).size.should == 2
92
+ end
93
+
94
+ it "should get all of the voters up votes for a class" do
95
+ @votable.vote :voter => @voter
96
+ @votable2.vote :voter => @voter, :vote => false
97
+ @voter.find_up_votes_for_class(Votable).size.should == 1
98
+ end
99
+
100
+ it "should get all of the voters down votes for a class" do
101
+ @votable.vote :voter => @voter
102
+ @votable2.vote :voter => @voter, :vote => false
103
+ @voter.find_down_votes_for_class(Votable).size.should == 1
104
+ end
105
+
69
106
  it "should be thread safe" do
70
107
  @voter.vote :votable => @votable, :vote => false
71
108
  @voter2.vote :votable => @votable
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 0
7
+ - 1
7
8
  - 0
8
- - 5
9
- version: 0.0.5
9
+ version: 0.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Ryan
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-12-30 00:00:00 -05:00
17
+ date: 2011-02-09 00:00:00 -05:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -58,6 +58,7 @@ files:
58
58
  - .gitignore
59
59
  - Gemfile
60
60
  - Gemfile.lock
61
+ - README.markdown
61
62
  - Rakefile
62
63
  - acts_as_votable.gemspec
63
64
  - lib/acts_as_votable.rb