acts_as_votable 0.10.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/workflows/main.yml +44 -0
- data/.gitignore +10 -6
- data/.rubocop.yml +121 -0
- data/.ruby-version +1 -0
- data/Appraisals +23 -0
- data/Gemfile +3 -14
- data/{README.markdown → README.md} +87 -52
- data/Rakefile +6 -4
- data/acts_as_votable.gemspec +12 -7
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/rails_4.gemfile +7 -0
- data/gemfiles/rails_5.gemfile +7 -0
- data/gemfiles/rails_5_1.gemfile +7 -0
- data/gemfiles/rails_5_2.gemfile +7 -0
- data/gemfiles/rails_6.gemfile +8 -0
- data/gemfiles/rails_6_1.gemfile +8 -0
- data/gemfiles/rails_6_rc1.gemfile +8 -0
- data/lib/acts_as_votable.rb +9 -9
- data/lib/acts_as_votable/cacheable.rb +174 -0
- data/lib/acts_as_votable/extenders/controller.rb +3 -4
- data/lib/acts_as_votable/extenders/votable.rb +17 -6
- data/lib/acts_as_votable/extenders/voter.rb +4 -6
- data/lib/acts_as_votable/helpers/words.rb +7 -10
- data/lib/acts_as_votable/version.rb +3 -1
- data/lib/acts_as_votable/votable.rb +74 -194
- data/lib/acts_as_votable/vote.rb +10 -12
- data/lib/acts_as_votable/voter.rb +55 -56
- data/lib/generators/acts_as_votable/migration/migration_generator.rb +25 -4
- data/lib/generators/acts_as_votable/migration/templates/active_record/{migration.rb → migration.erb} +1 -6
- data/spec/factories/votable.rb +6 -0
- data/spec/factories/votable_cache.rb +6 -0
- data/spec/factories/votable_cache_update_attributes.rb +6 -0
- data/spec/factories/votable_cache_update_columns.rb +6 -0
- data/spec/factories/votable_child_of_sti_not_votable.rb +6 -0
- data/spec/factories/votable_child_of_sti_votable.rb +6 -0
- data/spec/factories/votable_voter.rb +6 -0
- data/spec/factories/vote.rb +6 -0
- data/spec/factories/voter.rb +6 -0
- data/spec/generators/active_record_generator_spec.rb +13 -0
- data/spec/shared_example/votable_model.rb +542 -0
- data/spec/shared_example/voter_model.rb +280 -0
- data/spec/spec_helper.rb +28 -18
- data/spec/support/factory_bot.rb +9 -0
- data/spec/votable_spec.rb +10 -9
- data/spec/votable_voter_spec.rb +12 -12
- data/spec/voter_spec.rb +9 -10
- data/spec/words_spec.rb +9 -12
- metadata +116 -26
- data/.travis.yml +0 -25
- data/spec/shared_example/votable_model_spec.rb +0 -421
- data/spec/shared_example/voter_model_spec.rb +0 -279
data/Rakefile
CHANGED
@@ -1,10 +1,12 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler"
|
2
4
|
Bundler::GemHelper.install_tasks
|
3
5
|
|
4
|
-
require
|
6
|
+
require "rspec/core/rake_task"
|
5
7
|
|
6
8
|
desc "Run specs"
|
7
9
|
RSpec::Core::RakeTask.new(:spec)
|
8
10
|
|
9
|
-
desc
|
10
|
-
task :
|
11
|
+
desc "Default: run specs."
|
12
|
+
task default: :spec
|
data/acts_as_votable.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
2
4
|
$:.push File.expand_path("../lib", __FILE__)
|
3
5
|
require "acts_as_votable/version"
|
4
6
|
|
@@ -9,16 +11,19 @@ Gem::Specification.new do |s|
|
|
9
11
|
s.authors = ["Ryan"]
|
10
12
|
s.email = ["ryanto"]
|
11
13
|
s.homepage = "http://rubygems.org/gems/acts_as_votable"
|
12
|
-
s.summary =
|
13
|
-
s.description =
|
14
|
-
|
15
|
-
s.rubyforge_project = "acts_as_votable"
|
14
|
+
s.summary = "Rails gem to allowing records to be votable"
|
15
|
+
s.description = "Rails gem to allowing records to be votable"
|
16
|
+
s.license = "MIT"
|
16
17
|
|
17
18
|
s.files = `git ls-files`.split("\n")
|
18
19
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
21
|
s.require_paths = ["lib"]
|
21
22
|
|
22
|
-
s.add_development_dependency "rspec"
|
23
|
-
s.add_development_dependency "sqlite3",
|
23
|
+
s.add_development_dependency "rspec", "~> 3.6"
|
24
|
+
s.add_development_dependency "sqlite3", "~> 1.3.6"
|
25
|
+
s.add_development_dependency "rubocop", "~> 0.49.1"
|
26
|
+
s.add_development_dependency "simplecov", "~> 0.15.0"
|
27
|
+
s.add_development_dependency "appraisal", "~> 2.2"
|
28
|
+
s.add_development_dependency "factory_bot", "~> 4.8"
|
24
29
|
end
|
data/lib/acts_as_votable.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_record"
|
4
|
+
require "active_support/inflector"
|
3
5
|
|
4
6
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
7
|
|
6
8
|
module ActsAsVotable
|
7
|
-
|
8
9
|
if defined?(ActiveRecord::Base)
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
10
|
+
require "acts_as_votable/extenders/votable"
|
11
|
+
require "acts_as_votable/extenders/voter"
|
12
|
+
require "acts_as_votable/vote"
|
12
13
|
ActiveRecord::Base.extend ActsAsVotable::Extenders::Votable
|
13
14
|
ActiveRecord::Base.extend ActsAsVotable::Extenders::Voter
|
14
15
|
end
|
15
|
-
|
16
16
|
end
|
17
17
|
|
18
|
-
require
|
18
|
+
require "acts_as_votable/extenders/controller"
|
19
19
|
ActiveSupport.on_load(:action_controller) do
|
20
20
|
include ActsAsVotable::Extenders::Controller
|
21
|
-
end
|
21
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActsAsVotable
|
4
|
+
module Cacheable
|
5
|
+
def scope_cache_field(field, vote_scope)
|
6
|
+
return field if vote_scope.nil?
|
7
|
+
|
8
|
+
case field
|
9
|
+
when :cached_votes_total=
|
10
|
+
"cached_scoped_#{vote_scope}_votes_total="
|
11
|
+
when :cached_votes_total
|
12
|
+
"cached_scoped_#{vote_scope}_votes_total"
|
13
|
+
when :cached_votes_up=
|
14
|
+
"cached_scoped_#{vote_scope}_votes_up="
|
15
|
+
when :cached_votes_up
|
16
|
+
"cached_scoped_#{vote_scope}_votes_up"
|
17
|
+
when :cached_votes_down=
|
18
|
+
"cached_scoped_#{vote_scope}_votes_down="
|
19
|
+
when :cached_votes_down
|
20
|
+
"cached_scoped_#{vote_scope}_votes_down"
|
21
|
+
when :cached_votes_score=
|
22
|
+
"cached_scoped_#{vote_scope}_votes_score="
|
23
|
+
when :cached_votes_score
|
24
|
+
"cached_scoped_#{vote_scope}_votes_score"
|
25
|
+
when :cached_weighted_total
|
26
|
+
"cached_weighted_#{vote_scope}_total"
|
27
|
+
when :cached_weighted_total=
|
28
|
+
"cached_weighted_#{vote_scope}_total="
|
29
|
+
when :cached_weighted_score
|
30
|
+
"cached_weighted_#{vote_scope}_score"
|
31
|
+
when :cached_weighted_score=
|
32
|
+
"cached_weighted_#{vote_scope}_score="
|
33
|
+
when :cached_weighted_average
|
34
|
+
"cached_weighted_#{vote_scope}_average"
|
35
|
+
when :cached_weighted_average=
|
36
|
+
"cached_weighted_#{vote_scope}_average="
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def update_cached_votes(vote_scope = nil)
|
41
|
+
updates = {}
|
42
|
+
|
43
|
+
if self.respond_to?(:cached_votes_total=)
|
44
|
+
updates[:cached_votes_total] = count_votes_total(true)
|
45
|
+
end
|
46
|
+
|
47
|
+
if self.respond_to?(:cached_votes_up=)
|
48
|
+
updates[:cached_votes_up] = count_votes_up(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
if self.respond_to?(:cached_votes_down=)
|
52
|
+
updates[:cached_votes_down] = count_votes_down(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
if self.respond_to?(:cached_votes_score=)
|
56
|
+
updates[:cached_votes_score] = (
|
57
|
+
(updates[:cached_votes_up] || count_votes_up(true)) -
|
58
|
+
(updates[:cached_votes_down] || count_votes_down(true))
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
if self.respond_to?(:cached_weighted_total=)
|
63
|
+
updates[:cached_weighted_total] = weighted_total(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
if self.respond_to?(:cached_weighted_score=)
|
67
|
+
updates[:cached_weighted_score] = weighted_score(true)
|
68
|
+
end
|
69
|
+
|
70
|
+
if self.respond_to?(:cached_weighted_average=)
|
71
|
+
updates[:cached_weighted_average] = weighted_average(true)
|
72
|
+
end
|
73
|
+
|
74
|
+
if vote_scope
|
75
|
+
if self.respond_to?(scope_cache_field :cached_votes_total=, vote_scope)
|
76
|
+
updates[scope_cache_field :cached_votes_total, vote_scope] = count_votes_total(true, vote_scope)
|
77
|
+
end
|
78
|
+
|
79
|
+
if self.respond_to?(scope_cache_field :cached_votes_up=, vote_scope)
|
80
|
+
updates[scope_cache_field :cached_votes_up, vote_scope] = count_votes_up(true, vote_scope)
|
81
|
+
end
|
82
|
+
|
83
|
+
if self.respond_to?(scope_cache_field :cached_votes_down=, vote_scope)
|
84
|
+
updates[scope_cache_field :cached_votes_down, vote_scope] = count_votes_down(true, vote_scope)
|
85
|
+
end
|
86
|
+
|
87
|
+
if self.respond_to?(scope_cache_field :cached_weighted_total=, vote_scope)
|
88
|
+
updates[scope_cache_field :cached_weighted_total, vote_scope] = weighted_total(true, vote_scope)
|
89
|
+
end
|
90
|
+
|
91
|
+
if self.respond_to?(scope_cache_field :cached_weighted_score=, vote_scope)
|
92
|
+
updates[scope_cache_field :cached_weighted_score, vote_scope] = weighted_score(true, vote_scope)
|
93
|
+
end
|
94
|
+
|
95
|
+
if self.respond_to?(scope_cache_field :cached_votes_score=, vote_scope)
|
96
|
+
updates[scope_cache_field :cached_votes_score, vote_scope] = (
|
97
|
+
(updates[scope_cache_field :cached_votes_up, vote_scope] || count_votes_up(true, vote_scope)) -
|
98
|
+
(updates[scope_cache_field :cached_votes_down, vote_scope] || count_votes_down(true, vote_scope))
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
if self.respond_to?(scope_cache_field :cached_weighted_average=, vote_scope)
|
103
|
+
updates[scope_cache_field :cached_weighted_average, vote_scope] = weighted_average(true, vote_scope)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
self.send(acts_as_votable_options[:cacheable_strategy], updates) if updates.size > 0
|
108
|
+
end
|
109
|
+
|
110
|
+
# counting
|
111
|
+
def count_votes_total(skip_cache = false, vote_scope = nil)
|
112
|
+
from_cache(skip_cache, :cached_votes_total, vote_scope) do
|
113
|
+
find_votes_for(scope_or_empty_hash(vote_scope)).count
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def count_votes_up(skip_cache = false, vote_scope = nil)
|
118
|
+
from_cache(skip_cache, :cached_votes_up, vote_scope) do
|
119
|
+
get_up_votes(vote_scope: vote_scope).count
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def count_votes_down(skip_cache = false, vote_scope = nil)
|
124
|
+
from_cache(skip_cache, :cached_votes_down, vote_scope) do
|
125
|
+
get_down_votes(vote_scope: vote_scope).count
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def count_votes_score(skip_cache = false, vote_scope = nil)
|
130
|
+
from_cache(skip_cache, :cached_votes_score, vote_scope) do
|
131
|
+
ups = count_votes_up(true, vote_scope)
|
132
|
+
downs = count_votes_down(true, vote_scope)
|
133
|
+
ups - downs
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def weighted_total(skip_cache = false, vote_scope = nil)
|
138
|
+
from_cache(skip_cache, :cached_weighted_total, vote_scope) do
|
139
|
+
ups = get_up_votes(vote_scope: vote_scope).sum(:vote_weight)
|
140
|
+
downs = get_down_votes(vote_scope: vote_scope).sum(:vote_weight)
|
141
|
+
ups + downs
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def weighted_score(skip_cache = false, vote_scope = nil)
|
146
|
+
from_cache(skip_cache, :cached_weighted_score, vote_scope) do
|
147
|
+
ups = get_up_votes(vote_scope: vote_scope).sum(:vote_weight)
|
148
|
+
downs = get_down_votes(vote_scope: vote_scope).sum(:vote_weight)
|
149
|
+
ups - downs
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def weighted_average(skip_cache = false, vote_scope = nil)
|
154
|
+
from_cache(skip_cache, :cached_weighted_average, vote_scope) do
|
155
|
+
count = count_votes_total(skip_cache, vote_scope).to_i
|
156
|
+
if count > 0
|
157
|
+
weighted_score(skip_cache, vote_scope).to_f / count
|
158
|
+
else
|
159
|
+
0.0
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
private
|
165
|
+
|
166
|
+
def from_cache(skip_cache, cached_method, vote_scope)
|
167
|
+
if !skip_cache && respond_to?(scope_cache_field(cached_method, vote_scope))
|
168
|
+
send(scope_cache_field(cached_method, vote_scope))
|
169
|
+
else
|
170
|
+
yield
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -1,19 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsVotable
|
2
4
|
module Extenders
|
3
|
-
|
4
5
|
module Controller
|
5
|
-
|
6
6
|
def voter_params(params_object = params[:vote])
|
7
7
|
params_object.permit(:votable_id, :votable_type,
|
8
8
|
:voter_id, :voter_type,
|
9
9
|
:votable, :voter,
|
10
10
|
:vote_flag, :vote_scope)
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
def votable_params(params_object = params[:vote])
|
14
14
|
params_object.permit(:vote_registered)
|
15
15
|
end
|
16
|
-
|
17
16
|
end
|
18
17
|
end
|
19
18
|
end
|
@@ -1,25 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsVotable
|
2
4
|
module Extenders
|
3
|
-
|
4
5
|
module Votable
|
6
|
+
ALLOWED_CACHEABLE_STRATEGIES = %i[update update_columns]
|
5
7
|
|
6
8
|
def votable?
|
7
9
|
false
|
8
10
|
end
|
9
11
|
|
10
|
-
def acts_as_votable
|
11
|
-
require
|
12
|
+
def acts_as_votable(args = {})
|
13
|
+
require "acts_as_votable/votable"
|
12
14
|
include ActsAsVotable::Votable
|
13
15
|
|
16
|
+
if args.key?(:cacheable_strategy) && !ALLOWED_CACHEABLE_STRATEGIES.include?(args[:cacheable_strategy])
|
17
|
+
raise ArgumentError, args[:cacheable_strategy]
|
18
|
+
end
|
19
|
+
|
20
|
+
define_method :acts_as_votable_options do
|
21
|
+
self.class.instance_variable_get("@acts_as_votable_options")
|
22
|
+
end
|
23
|
+
|
14
24
|
class_eval do
|
25
|
+
@acts_as_votable_options = {
|
26
|
+
cacheable_strategy: :update
|
27
|
+
}.merge(args)
|
28
|
+
|
15
29
|
def self.votable?
|
16
30
|
true
|
17
31
|
end
|
18
32
|
end
|
19
|
-
|
20
33
|
end
|
21
|
-
|
22
34
|
end
|
23
|
-
|
24
35
|
end
|
25
36
|
end
|
@@ -1,14 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActsAsVotable
|
2
4
|
module Extenders
|
3
|
-
|
4
5
|
module Voter
|
5
|
-
|
6
6
|
def voter?
|
7
7
|
false
|
8
8
|
end
|
9
9
|
|
10
|
-
def acts_as_voter(*
|
11
|
-
require
|
10
|
+
def acts_as_voter(*_args)
|
11
|
+
require "acts_as_votable/voter"
|
12
12
|
include ActsAsVotable::Voter
|
13
13
|
|
14
14
|
class_eval do
|
@@ -16,9 +16,7 @@ module ActsAsVotable
|
|
16
16
|
true
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
20
19
|
end
|
21
|
-
|
22
20
|
end
|
23
21
|
end
|
24
22
|
end
|
@@ -1,36 +1,33 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
module ActsAsVotable::Helpers
|
4
|
+
# this helper provides methods that help find what words are
|
4
5
|
# up votes and what words are down votes
|
5
6
|
#
|
6
|
-
# It can be called
|
7
|
+
# It can be called
|
7
8
|
#
|
8
9
|
# votable_object.votable_words.that_mean_true
|
9
10
|
#
|
10
11
|
module Words
|
11
|
-
|
12
12
|
def votable_words
|
13
13
|
VotableWords
|
14
14
|
end
|
15
|
-
|
16
15
|
end
|
17
16
|
|
18
17
|
class VotableWords
|
19
|
-
|
20
18
|
def self.that_mean_true
|
21
|
-
[
|
19
|
+
["up", "upvote", "like", "liked", "positive", "yes", "good", "agree", "true", 1, true]
|
22
20
|
end
|
23
21
|
|
24
22
|
def self.that_mean_false
|
25
|
-
[
|
23
|
+
["down", "downvote", "dislike", "disliked", "negative", "no", "bad", "disagree", "false", 0, false]
|
26
24
|
end
|
27
25
|
|
28
26
|
# check is word is a true or bad vote
|
29
27
|
# if the word is unknown, then it counts it as a true/good
|
30
28
|
# vote. this exists to allow all voting to be good by default
|
31
|
-
def self.meaning_of
|
29
|
+
def self.meaning_of(word)
|
32
30
|
!that_mean_false.include?(word)
|
33
31
|
end
|
34
|
-
|
35
32
|
end
|
36
33
|
end
|