acts_as_votable 0.10.0 → 0.13.0

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.
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