jy-acts_as_votable 0.3.1
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 +5 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +103 -0
- data/README.markdown +227 -0
- data/Rakefile +2 -0
- data/acts_as_votable.gemspec +29 -0
- data/lib/acts_as_votable.rb +16 -0
- data/lib/acts_as_votable/extenders/votable.rb +25 -0
- data/lib/acts_as_votable/extenders/voter.rb +24 -0
- data/lib/acts_as_votable/helpers/words.rb +36 -0
- data/lib/acts_as_votable/version.rb +3 -0
- data/lib/acts_as_votable/votable.rb +195 -0
- data/lib/acts_as_votable/vote.rb +27 -0
- data/lib/acts_as_votable/voter.rb +100 -0
- data/lib/generators/acts_as_votable/migration/migration_generator.rb +31 -0
- data/lib/generators/acts_as_votable/migration/templates/active_record/migration.rb +20 -0
- data/spec/spec_helper.rb +103 -0
- data/spec/votable_spec.rb +250 -0
- data/spec/voter_spec.rb +157 -0
- data/spec/words_spec.rb +30 -0
- metadata +130 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module ActsAsVotable::Helpers
|
|
2
|
+
|
|
3
|
+
# this helper provides methods that help find what words are
|
|
4
|
+
# up votes and what words are down votes
|
|
5
|
+
#
|
|
6
|
+
# It can be called
|
|
7
|
+
#
|
|
8
|
+
# votable_object.votable_words.that_mean_true
|
|
9
|
+
#
|
|
10
|
+
module Words
|
|
11
|
+
|
|
12
|
+
def votable_words
|
|
13
|
+
VotableWords
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class VotableWords
|
|
19
|
+
|
|
20
|
+
def self.that_mean_true
|
|
21
|
+
['up', 'upvote', 'like', 'liked', 'positive', 'yes', 'good', 'true', 1, true]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.that_mean_false
|
|
25
|
+
['down', 'downvote', 'dislike', 'disliked', 'negative', 'no', 'bad', 'false', 0, false]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# check is word is a true or bad vote
|
|
29
|
+
# if the word is unknown, then it counts it as a true/good
|
|
30
|
+
# vote. this exists to allow all voting to be good by default
|
|
31
|
+
def self.meaning_of word
|
|
32
|
+
!that_mean_false.include?(word)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
require 'acts_as_votable/helpers/words'
|
|
2
|
+
|
|
3
|
+
module ActsAsVotable
|
|
4
|
+
module Votable
|
|
5
|
+
|
|
6
|
+
include Helpers::Words
|
|
7
|
+
|
|
8
|
+
def self.included base
|
|
9
|
+
|
|
10
|
+
# allow the user to define these himself
|
|
11
|
+
aliases = {
|
|
12
|
+
|
|
13
|
+
:vote_up => [
|
|
14
|
+
:up_by, :upvote_by, :like_by, :liked_by, :vote_by,
|
|
15
|
+
:up_from, :upvote_from, :upvote_by, :like_from, :liked_from, :vote_from
|
|
16
|
+
],
|
|
17
|
+
|
|
18
|
+
:vote_down => [
|
|
19
|
+
:down_by, :downvote_by, :dislike_by, :disliked_by,
|
|
20
|
+
:down_from, :downvote_from, :downvote_by, :dislike_by, :disliked_by
|
|
21
|
+
],
|
|
22
|
+
|
|
23
|
+
:up_votes => [
|
|
24
|
+
:true_votes, :ups, :upvotes, :likes, :positives, :for_votes,
|
|
25
|
+
],
|
|
26
|
+
|
|
27
|
+
:down_votes => [
|
|
28
|
+
:false_votes, :downs, :downvotes, :dislikes, :negatives
|
|
29
|
+
],
|
|
30
|
+
:unvote => [
|
|
31
|
+
:unliked_by, :undisliked_by
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
base.class_eval do
|
|
36
|
+
|
|
37
|
+
belongs_to :votable, :polymorphic => true
|
|
38
|
+
has_many :votes, :class_name => "ActsAsVotable::Vote", :as => :votable do
|
|
39
|
+
def voters
|
|
40
|
+
includes(:voter).map(&:voter)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
aliases.each do |method, links|
|
|
45
|
+
links.each do |new_method|
|
|
46
|
+
alias_method(new_method, method)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
attr_accessor :vote_registered
|
|
54
|
+
|
|
55
|
+
def vote_registered?
|
|
56
|
+
return self.vote_registered
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def default_conditions
|
|
60
|
+
{
|
|
61
|
+
:votable_id => self.id,
|
|
62
|
+
:votable_type => self.class.base_class.name.to_s
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# voting
|
|
67
|
+
def vote args = {}
|
|
68
|
+
|
|
69
|
+
options = {
|
|
70
|
+
:vote => true,
|
|
71
|
+
}.merge(args)
|
|
72
|
+
|
|
73
|
+
self.vote_registered = false
|
|
74
|
+
|
|
75
|
+
if options[:voter].nil?
|
|
76
|
+
return false
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# find the vote
|
|
80
|
+
_votes_ = find_votes({
|
|
81
|
+
:voter_id => options[:voter].id,
|
|
82
|
+
:voter_type => options[:voter].class.name
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
if _votes_.count == 0
|
|
86
|
+
# this voter has never voted
|
|
87
|
+
vote = ActsAsVotable::Vote.new(
|
|
88
|
+
:votable => self,
|
|
89
|
+
:voter => options[:voter]
|
|
90
|
+
)
|
|
91
|
+
else
|
|
92
|
+
# this voter is potentially changing his vote
|
|
93
|
+
vote = _votes_.first
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
last_update = vote.updated_at
|
|
97
|
+
|
|
98
|
+
vote.vote_flag = votable_words.meaning_of(options[:vote])
|
|
99
|
+
|
|
100
|
+
if vote.save
|
|
101
|
+
self.vote_registered = true if last_update != vote.updated_at
|
|
102
|
+
update_cached_votes
|
|
103
|
+
return true
|
|
104
|
+
else
|
|
105
|
+
self.vote_registered = false
|
|
106
|
+
return false
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def unvote args = {}
|
|
112
|
+
return false if args[:voter].nil?
|
|
113
|
+
_votes_ = find_votes(:voter_id => args[:voter].id, :voter_type => args[:voter].class.name)
|
|
114
|
+
|
|
115
|
+
return true if _votes_.size == 0
|
|
116
|
+
_votes_.each(&:destroy)
|
|
117
|
+
update_cached_votes
|
|
118
|
+
self.vote_registered = false if votes.count == 0
|
|
119
|
+
return true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def vote_up voter
|
|
123
|
+
self.vote :voter => voter, :vote => true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def vote_down voter
|
|
127
|
+
self.vote :voter => voter, :vote => false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# caching
|
|
131
|
+
def update_cached_votes
|
|
132
|
+
|
|
133
|
+
updates = {}
|
|
134
|
+
|
|
135
|
+
if self.respond_to?(:cached_votes_total=)
|
|
136
|
+
updates[:cached_votes_total] = count_votes_total(true)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
if self.respond_to?(:cached_votes_up=)
|
|
140
|
+
updates[:cached_votes_up] = count_votes_up(true)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
if self.respond_to?(:cached_votes_down=)
|
|
144
|
+
updates[:cached_votes_down] = count_votes_down(true)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
self.update_attributes(updates, :without_protection => true) if updates.size > 0
|
|
148
|
+
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# results
|
|
153
|
+
def find_votes extra_conditions = {}
|
|
154
|
+
votes.where(extra_conditions)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def up_votes
|
|
158
|
+
find_votes(:vote_flag => true)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def down_votes
|
|
162
|
+
find_votes(:vote_flag => false)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# counting
|
|
167
|
+
def count_votes_total skip_cache = false
|
|
168
|
+
if !skip_cache && self.respond_to?(:cached_votes_total)
|
|
169
|
+
return self.send(:cached_votes_total)
|
|
170
|
+
end
|
|
171
|
+
find_votes.count
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def count_votes_up skip_cache = false
|
|
175
|
+
if !skip_cache && self.respond_to?(:cached_votes_up)
|
|
176
|
+
return self.send(:cached_votes_up)
|
|
177
|
+
end
|
|
178
|
+
up_votes.count
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def count_votes_down skip_cache = false
|
|
182
|
+
if !skip_cache && self.respond_to?(:cached_votes_down)
|
|
183
|
+
return self.send(:cached_votes_down)
|
|
184
|
+
end
|
|
185
|
+
down_votes.count
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# voters
|
|
189
|
+
def voted_on_by? voter
|
|
190
|
+
votes = find_votes :voter_id => voter.id, :voter_type => voter.class.name
|
|
191
|
+
votes.count > 0
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
end
|
|
195
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'acts_as_votable/helpers/words'
|
|
2
|
+
|
|
3
|
+
module ActsAsVotable
|
|
4
|
+
class Vote < ::ActiveRecord::Base
|
|
5
|
+
|
|
6
|
+
include Helpers::Words
|
|
7
|
+
|
|
8
|
+
attr_accessible :votable_id, :votable_type,
|
|
9
|
+
:voter_id, :voter_type,
|
|
10
|
+
:votable, :voter,
|
|
11
|
+
:vote_flag
|
|
12
|
+
|
|
13
|
+
belongs_to :votable, :polymorphic => true
|
|
14
|
+
belongs_to :voter, :polymorphic => true
|
|
15
|
+
|
|
16
|
+
scope :up, where(:vote_flag => true)
|
|
17
|
+
scope :down, where(:vote_flag => false)
|
|
18
|
+
scope :for_type, lambda{ |klass| where(:votable_type => klass) }
|
|
19
|
+
scope :by_type, lambda{ |klass| where(:voter_type => klass) }
|
|
20
|
+
|
|
21
|
+
validates_presence_of :votable_id
|
|
22
|
+
validates_presence_of :voter_id
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module ActsAsVotable
|
|
2
|
+
module Voter
|
|
3
|
+
|
|
4
|
+
def self.included(base)
|
|
5
|
+
|
|
6
|
+
# allow user to define these
|
|
7
|
+
aliases = {
|
|
8
|
+
:vote_up_for => [:likes, :upvotes, :up_votes],
|
|
9
|
+
:vote_down_for => [:dislikes, :downvotes, :down_votes],
|
|
10
|
+
:unvote_for => [:unlike, :undislike]
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
base.class_eval do
|
|
14
|
+
|
|
15
|
+
belongs_to :voter, :polymorphic => true
|
|
16
|
+
has_many :votes, :class_name => "ActsAsVotable::Vote", :as => :voter do
|
|
17
|
+
def votables
|
|
18
|
+
includes(:votable).map(&:votable)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
aliases.each do |method, links|
|
|
23
|
+
links.each do |new_method|
|
|
24
|
+
alias_method(new_method, method)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# voting
|
|
33
|
+
def vote args
|
|
34
|
+
args[:votable].vote args.merge({:voter => self})
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def vote_up_for model=nil
|
|
38
|
+
vote :votable => model, :vote => true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def vote_down_for model
|
|
42
|
+
vote :votable => model, :vote => false
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def unvote_for model
|
|
46
|
+
model.unvote :voter => self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# results
|
|
50
|
+
def voted_on? votable
|
|
51
|
+
votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name)
|
|
52
|
+
votes.size > 0
|
|
53
|
+
end
|
|
54
|
+
alias :voted_for? :voted_on?
|
|
55
|
+
|
|
56
|
+
def voted_up_on? votable
|
|
57
|
+
votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name, :vote_flag => true)
|
|
58
|
+
votes.size > 0
|
|
59
|
+
end
|
|
60
|
+
alias :voted_up_for? :voted_up_on?
|
|
61
|
+
|
|
62
|
+
def voted_down_on? votable
|
|
63
|
+
votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name, :vote_flag => false)
|
|
64
|
+
votes.size > 0
|
|
65
|
+
end
|
|
66
|
+
alias :voted_down_for? :voted_down_on?
|
|
67
|
+
|
|
68
|
+
def voted_as_when_voting_on votable
|
|
69
|
+
votes = find_votes(:votable_id => votable.id, :votable_type => votable.class.base_class.name)
|
|
70
|
+
return nil if votes.size == 0
|
|
71
|
+
return votes.first.vote_flag
|
|
72
|
+
end
|
|
73
|
+
alias :voted_as_when_voted_for :voted_as_when_voting_on
|
|
74
|
+
|
|
75
|
+
def find_votes extra_conditions = {}
|
|
76
|
+
votes.where(extra_conditions)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def find_up_votes
|
|
80
|
+
find_votes :vote_flag => true
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def find_down_votes
|
|
84
|
+
find_votes :vote_flag => false
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def find_votes_for_class klass, extra_conditions = {}
|
|
88
|
+
find_votes extra_conditions.merge({:votable_type => klass.name})
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def find_up_votes_for_class klass
|
|
92
|
+
find_votes_for_class klass, :vote_flag => true
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def find_down_votes_for_class klass
|
|
96
|
+
find_votes_for_class klass, :vote_flag => false
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require 'rails/generators/migration'
|
|
2
|
+
|
|
3
|
+
module ActsAsVotable
|
|
4
|
+
class MigrationGenerator < Rails::Generators::Base
|
|
5
|
+
include Rails::Generators::Migration
|
|
6
|
+
|
|
7
|
+
desc "Generates migration for votable (votes table)"
|
|
8
|
+
|
|
9
|
+
def self.orm
|
|
10
|
+
Rails::Generators.options[:rails][:orm]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.source_root
|
|
14
|
+
File.join(File.dirname(__FILE__), 'templates', (orm.to_s unless orm.class.eql?(String)) )
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.orm_has_migration?
|
|
18
|
+
[:active_record].include? orm
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.next_migration_number(path)
|
|
22
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def create_migration_file
|
|
26
|
+
if self.class.orm_has_migration?
|
|
27
|
+
migration_template 'migration.rb', 'db/migrate/acts_as_votable_migration'
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
class ActsAsVotableMigration < ActiveRecord::Migration
|
|
2
|
+
def self.up
|
|
3
|
+
create_table :votes do |t|
|
|
4
|
+
|
|
5
|
+
t.references :votable, :polymorphic => true
|
|
6
|
+
t.references :voter, :polymorphic => true
|
|
7
|
+
|
|
8
|
+
t.boolean :vote_flag
|
|
9
|
+
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
add_index :votes, [:votable_id, :votable_type]
|
|
14
|
+
add_index :votes, [:voter_id, :voter_type]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.down
|
|
18
|
+
drop_table :votes
|
|
19
|
+
end
|
|
20
|
+
end
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
$LOAD_PATH << File.join(File.dirname(__FILE__), '..', 'lib')
|
|
2
|
+
require 'acts_as_votable'
|
|
3
|
+
|
|
4
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
|
|
5
|
+
|
|
6
|
+
ActiveRecord::Schema.define(:version => 1) do
|
|
7
|
+
create_table :votes do |t|
|
|
8
|
+
t.references :votable, :polymorphic => true
|
|
9
|
+
t.references :voter, :polymorphic => true
|
|
10
|
+
|
|
11
|
+
t.boolean :vote_flag
|
|
12
|
+
|
|
13
|
+
t.timestamps
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
add_index :votes, [:votable_id, :votable_type]
|
|
17
|
+
add_index :votes, [:voter_id, :voter_type]
|
|
18
|
+
|
|
19
|
+
create_table :voters do |t|
|
|
20
|
+
t.string :name
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
create_table :not_voters do |t|
|
|
24
|
+
t.string :name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
create_table :votables do |t|
|
|
28
|
+
t.string :name
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
create_table :sti_votables do |t|
|
|
32
|
+
t.string :name
|
|
33
|
+
t.string :type
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
create_table :sti_not_votables do |t|
|
|
37
|
+
t.string :name
|
|
38
|
+
t.string :type
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
create_table :not_votables do |t|
|
|
42
|
+
t.string :name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
create_table :votable_caches do |t|
|
|
46
|
+
t.string :name
|
|
47
|
+
t.integer :cached_votes_total
|
|
48
|
+
t.integer :cached_votes_up
|
|
49
|
+
t.integer :cached_votes_down
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Voter < ActiveRecord::Base
|
|
56
|
+
acts_as_voter
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class NotVoter < ActiveRecord::Base
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
class Votable < ActiveRecord::Base
|
|
64
|
+
acts_as_votable
|
|
65
|
+
validates_presence_of :name
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class StiVotable < ActiveRecord::Base
|
|
69
|
+
acts_as_votable
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class ChildOfStiVotable < StiVotable
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
class StiNotVotable < ActiveRecord::Base
|
|
76
|
+
validates_presence_of :name
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class VotableChildOfStiNotVotable < StiNotVotable
|
|
80
|
+
acts_as_votable
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class NotVotable < ActiveRecord::Base
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class VotableCache < ActiveRecord::Base
|
|
87
|
+
acts_as_votable
|
|
88
|
+
validates_presence_of :name
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
class ABoringClass
|
|
92
|
+
def self.hw
|
|
93
|
+
'hello world'
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def clean_database
|
|
99
|
+
models = [ActsAsVotable::Vote, Voter, NotVoter, Votable, NotVotable, VotableCache]
|
|
100
|
+
models.each do |model|
|
|
101
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
|
|
102
|
+
end
|
|
103
|
+
end
|