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.
Files changed (52) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/main.yml +44 -0
  3. data/.gitignore +10 -6
  4. data/.rubocop.yml +121 -0
  5. data/.ruby-version +1 -0
  6. data/Appraisals +23 -0
  7. data/Gemfile +3 -14
  8. data/{README.markdown → README.md} +87 -52
  9. data/Rakefile +6 -4
  10. data/acts_as_votable.gemspec +12 -7
  11. data/gemfiles/.bundle/config +2 -0
  12. data/gemfiles/rails_4.gemfile +7 -0
  13. data/gemfiles/rails_5.gemfile +7 -0
  14. data/gemfiles/rails_5_1.gemfile +7 -0
  15. data/gemfiles/rails_5_2.gemfile +7 -0
  16. data/gemfiles/rails_6.gemfile +8 -0
  17. data/gemfiles/rails_6_1.gemfile +8 -0
  18. data/gemfiles/rails_6_rc1.gemfile +8 -0
  19. data/lib/acts_as_votable.rb +9 -9
  20. data/lib/acts_as_votable/cacheable.rb +174 -0
  21. data/lib/acts_as_votable/extenders/controller.rb +3 -4
  22. data/lib/acts_as_votable/extenders/votable.rb +17 -6
  23. data/lib/acts_as_votable/extenders/voter.rb +4 -6
  24. data/lib/acts_as_votable/helpers/words.rb +7 -10
  25. data/lib/acts_as_votable/version.rb +3 -1
  26. data/lib/acts_as_votable/votable.rb +74 -194
  27. data/lib/acts_as_votable/vote.rb +10 -12
  28. data/lib/acts_as_votable/voter.rb +55 -56
  29. data/lib/generators/acts_as_votable/migration/migration_generator.rb +25 -4
  30. data/lib/generators/acts_as_votable/migration/templates/active_record/{migration.rb → migration.erb} +1 -6
  31. data/spec/factories/votable.rb +6 -0
  32. data/spec/factories/votable_cache.rb +6 -0
  33. data/spec/factories/votable_cache_update_attributes.rb +6 -0
  34. data/spec/factories/votable_cache_update_columns.rb +6 -0
  35. data/spec/factories/votable_child_of_sti_not_votable.rb +6 -0
  36. data/spec/factories/votable_child_of_sti_votable.rb +6 -0
  37. data/spec/factories/votable_voter.rb +6 -0
  38. data/spec/factories/vote.rb +6 -0
  39. data/spec/factories/voter.rb +6 -0
  40. data/spec/generators/active_record_generator_spec.rb +13 -0
  41. data/spec/shared_example/votable_model.rb +542 -0
  42. data/spec/shared_example/voter_model.rb +280 -0
  43. data/spec/spec_helper.rb +28 -18
  44. data/spec/support/factory_bot.rb +9 -0
  45. data/spec/votable_spec.rb +10 -9
  46. data/spec/votable_voter_spec.rb +12 -12
  47. data/spec/voter_spec.rb +9 -10
  48. data/spec/words_spec.rb +9 -12
  49. metadata +116 -26
  50. data/.travis.yml +0 -25
  51. data/spec/shared_example/votable_model_spec.rb +0 -421
  52. data/spec/shared_example/voter_model_spec.rb +0 -279
data/Rakefile CHANGED
@@ -1,10 +1,12 @@
1
- require 'bundler'
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
2
4
  Bundler::GemHelper.install_tasks
3
5
 
4
- require 'rspec/core/rake_task'
6
+ require "rspec/core/rake_task"
5
7
 
6
8
  desc "Run specs"
7
9
  RSpec::Core::RakeTask.new(:spec)
8
10
 
9
- desc 'Default: run specs.'
10
- task :default => :spec
11
+ desc "Default: run specs."
12
+ task default: :spec
@@ -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 = %q{Rails gem to allowing records to be votable}
13
- s.description = %q{Rails gem to allowing records to be votable}
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", '1.3.7'
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
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_RETRY: "1"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "4.2.11"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.0.6"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.1.7"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,7 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "5.2.4"
6
+
7
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "6.0.3"
6
+ gem "sqlite3", "~> 1.4"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "6.1.0"
6
+ gem "sqlite3", "~> 1.4"
7
+
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rails", "6.0.0.rc1"
6
+ gem "sqlite3", "~> 1.4"
7
+
8
+ gemspec path: "../"
@@ -1,21 +1,21 @@
1
- require 'active_record'
2
- require 'active_support/inflector'
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 'acts_as_votable/extenders/votable'
10
- require 'acts_as_votable/extenders/voter'
11
- require 'acts_as_votable/vote'
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 'acts_as_votable/extenders/controller'
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 'acts_as_votable/votable'
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(*args)
11
- require 'acts_as_votable/voter'
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
- module ActsAsVotable::Helpers
1
+ # frozen_string_literal: true
2
2
 
3
- # this helper provides methods that help find what words are
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
- ['up', 'upvote', 'like', 'liked', 'positive', 'yes', 'good', 'true', 1, true]
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
- ['down', 'downvote', 'dislike', 'disliked', 'negative', 'no', 'bad', 'false', 0, false]
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 word
29
+ def self.meaning_of(word)
32
30
  !that_mean_false.include?(word)
33
31
  end
34
-
35
32
  end
36
33
  end