voting 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE +21 -0
- data/README.md +229 -0
- data/lib/generators/voting/install_generator.rb +19 -0
- data/lib/generators/voting/templates/db/migrate/create_voting_tables.rb +35 -0
- data/lib/voting.rb +10 -0
- data/lib/voting/models/voting/extension.rb +85 -0
- data/lib/voting/models/voting/vote.rb +45 -0
- data/lib/voting/models/voting/voting.rb +73 -0
- data/lib/voting/version.rb +5 -0
- data/spec/factories/article.rb +5 -0
- data/spec/factories/author.rb +5 -0
- data/spec/factories/category.rb +5 -0
- data/spec/factories/comment.rb +5 -0
- data/spec/factories/voting/vote.rb +8 -0
- data/spec/factories/voting/voting.rb +9 -0
- data/spec/models/extension/after_save_spec.rb +37 -0
- data/spec/models/extension/as_spec.rb +26 -0
- data/spec/models/extension/down_spec.rb +26 -0
- data/spec/models/extension/order_by_voting_spec.rb +94 -0
- data/spec/models/extension/up_spec.rb +26 -0
- data/spec/models/extension/vote_for_spec.rb +26 -0
- data/spec/models/extension/vote_spec.rb +26 -0
- data/spec/models/extension/voted_question_spec.rb +38 -0
- data/spec/models/extension/voted_records_spec.rb +12 -0
- data/spec/models/extension/voted_spec.rb +40 -0
- data/spec/models/extension/votes_records_spec.rb +12 -0
- data/spec/models/extension/votes_spec.rb +40 -0
- data/spec/models/extension/voting_records_spec.rb +12 -0
- data/spec/models/extension/voting_spec.rb +40 -0
- data/spec/models/extension/voting_warm_up_spec.rb +115 -0
- data/spec/models/vote/create_spec.rb +273 -0
- data/spec/models/vote/vote_for_spec.rb +40 -0
- data/spec/models/vote_spec.rb +27 -0
- data/spec/models/voting/update_voting_spec.rb +28 -0
- data/spec/models/voting/values_data_spec.rb +24 -0
- data/spec/models/voting_spec.rb +26 -0
- data/spec/rails_helper.rb +11 -0
- data/spec/support/common.rb +22 -0
- data/spec/support/database_cleaner.rb +19 -0
- data/spec/support/db/migrate/create_articles_table.rb +7 -0
- data/spec/support/db/migrate/create_authors_table.rb +7 -0
- data/spec/support/db/migrate/create_categories_table.rb +9 -0
- data/spec/support/db/migrate/create_comments_table.rb +7 -0
- data/spec/support/factory_bot.rb +9 -0
- data/spec/support/migrate.rb +11 -0
- data/spec/support/models/article.rb +7 -0
- data/spec/support/models/author.rb +5 -0
- data/spec/support/models/category.rb +5 -0
- data/spec/support/models/comment.rb +5 -0
- data/spec/support/shared_context/with_database_records.rb +22 -0
- data/spec/support/shoulda.rb +10 -0
- metadata +257 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Voting
|
4
|
+
class Voting < ActiveRecord::Base
|
5
|
+
self.table_name = 'voting_votings'
|
6
|
+
|
7
|
+
belongs_to :resource, polymorphic: true
|
8
|
+
belongs_to :scopeable, polymorphic: true
|
9
|
+
|
10
|
+
validates :estimate, :negative, :positive, :resource, presence: true
|
11
|
+
validates :estimate, numericality: true
|
12
|
+
validates :negative, :positive, numericality: { greater_than_or_equal_to: 0, only_integer: true }
|
13
|
+
|
14
|
+
validates :resource_id, uniqueness: {
|
15
|
+
case_sensitive: false,
|
16
|
+
scope: %i[resource_type scopeable_id scopeable_type]
|
17
|
+
}
|
18
|
+
|
19
|
+
class << self
|
20
|
+
def values_data(resource, scopeable)
|
21
|
+
sql = %(
|
22
|
+
SELECT
|
23
|
+
COALESCE(SUM(negative), 0) voting_negative,
|
24
|
+
COALESCE(SUM(positive), 0) voting_positive
|
25
|
+
FROM #{vote_table_name}
|
26
|
+
WHERE resource_type = ? and resource_id = ? and #{scope_query(scopeable)}
|
27
|
+
).squish
|
28
|
+
|
29
|
+
values = [sql, resource.class.name, resource.id]
|
30
|
+
values += [scopeable.class.name, scopeable.id] unless scopeable.nil?
|
31
|
+
|
32
|
+
execute_sql values
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_voting(resource, scopeable)
|
36
|
+
record = find_or_initialize_by(resource: resource, scopeable: scopeable)
|
37
|
+
values = values_data(resource, scopeable)
|
38
|
+
|
39
|
+
record.estimate = estimate(values)
|
40
|
+
record.negative = values.voting_negative
|
41
|
+
record.positive = values.voting_positive
|
42
|
+
|
43
|
+
record.save!
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def estimate(values)
|
49
|
+
sum = values.voting_negative + values.voting_positive
|
50
|
+
|
51
|
+
return 0 if sum.zero?
|
52
|
+
|
53
|
+
square = Math.sqrt((values.voting_negative * values.voting_positive) / sum + 0.9604)
|
54
|
+
|
55
|
+
((values.voting_positive + 1.9208) / sum - 1.96 * square / sum) / (1 + 3.8416 / sum)
|
56
|
+
end
|
57
|
+
|
58
|
+
def execute_sql(sql)
|
59
|
+
Vote.find_by_sql(sql).first
|
60
|
+
end
|
61
|
+
|
62
|
+
def vote_table_name
|
63
|
+
@vote_table_name ||= Vote.table_name
|
64
|
+
end
|
65
|
+
|
66
|
+
def scope_query(scopeable)
|
67
|
+
return 'scopeable_type is NULL and scopeable_id is NULL' if scopeable.nil?
|
68
|
+
|
69
|
+
'scopeable_type = ? and scopeable_id = ?'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, 'after_save' do
|
6
|
+
context 'when record is author' do
|
7
|
+
let!(:record) { build :author }
|
8
|
+
|
9
|
+
it 'does warm up the cache' do
|
10
|
+
expect(record).not_to receive(:voting_warm_up)
|
11
|
+
|
12
|
+
record.save
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when record is not author' do
|
17
|
+
context 'when record has scoping' do
|
18
|
+
let!(:record) { build :article }
|
19
|
+
|
20
|
+
it 'warms up the cache' do
|
21
|
+
expect(record).to receive(:voting_warm_up).with(scoping: :categories)
|
22
|
+
|
23
|
+
record.save
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when record has no scoping' do
|
28
|
+
let!(:record) { build :comment }
|
29
|
+
|
30
|
+
it 'warms up the cache' do
|
31
|
+
expect(record).to receive(:voting_warm_up).with(scoping: nil)
|
32
|
+
|
33
|
+
record.save
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, ':as' do
|
6
|
+
context 'when is nil' do
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
it 'creates a voting record with values as zero to warm up the cache' do
|
10
|
+
voting = Voting::Voting.find_by(resource: comment)
|
11
|
+
|
12
|
+
expect(voting.negative).to eq 0
|
13
|
+
expect(voting.positive).to eq 0
|
14
|
+
expect(voting.resource).to eq comment
|
15
|
+
expect(voting.scopeable).to eq nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when is :author' do
|
20
|
+
let!(:author) { create :author }
|
21
|
+
|
22
|
+
it 'does not creates a voting record' do
|
23
|
+
expect(Voting::Voting.exists?(resource: author)).to eq false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, '.down' do
|
6
|
+
let!(:author) { create :author }
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
context 'with no scopeable' do
|
10
|
+
it 'delegates to create method' do
|
11
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: nil, value: -1
|
12
|
+
|
13
|
+
author.down comment
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with scopeable' do
|
18
|
+
let!(:category) { build :category }
|
19
|
+
|
20
|
+
it 'delegates to create method' do
|
21
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: category, value: -1
|
22
|
+
|
23
|
+
author.down comment, scope: category
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
require 'support/shared_context/with_database_records'
|
5
|
+
|
6
|
+
RSpec.describe Voting::Extension, ':order_by_voting' do
|
7
|
+
include_context 'with_database_records'
|
8
|
+
|
9
|
+
context 'with default filters' do
|
10
|
+
it 'sorts by :estimate :desc' do
|
11
|
+
expect(Comment.order_by_voting).to eq [
|
12
|
+
comment_1,
|
13
|
+
comment_2,
|
14
|
+
comment_3
|
15
|
+
]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'when filtering by :estimate' do
|
20
|
+
context 'with asc' do
|
21
|
+
it 'works' do
|
22
|
+
expect(Comment.order_by_voting(:estimate, :asc)).to eq [
|
23
|
+
comment_3,
|
24
|
+
comment_2,
|
25
|
+
comment_1
|
26
|
+
]
|
27
|
+
end
|
28
|
+
|
29
|
+
context 'with scope' do
|
30
|
+
it 'works' do
|
31
|
+
expect(Comment.order_by_voting(:estimate, :asc, scope: category)).to eq [
|
32
|
+
comment_1
|
33
|
+
]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'with desc' do
|
39
|
+
it 'works' do
|
40
|
+
expect(Comment.order_by_voting(:estimate, :desc)).to eq [
|
41
|
+
comment_1,
|
42
|
+
comment_2,
|
43
|
+
comment_3
|
44
|
+
]
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'with scope' do
|
48
|
+
it 'works' do
|
49
|
+
expect(Comment.order_by_voting(:estimate, :desc, scope: category)).to eq [
|
50
|
+
comment_1
|
51
|
+
]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when filtering by :negative' do
|
58
|
+
context 'with asc' do
|
59
|
+
it 'works' do
|
60
|
+
expect(Comment.order_by_voting(:negative, :asc)).to eq [
|
61
|
+
comment_3,
|
62
|
+
comment_1,
|
63
|
+
comment_2
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with scope' do
|
68
|
+
it 'works' do
|
69
|
+
expect(Comment.order_by_voting(:negative, :asc, scope: category)).to eq [
|
70
|
+
comment_1
|
71
|
+
]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'with desc' do
|
77
|
+
it 'works' do
|
78
|
+
expect(Comment.order_by_voting(:negative, :desc)).to eq [
|
79
|
+
comment_2,
|
80
|
+
comment_1,
|
81
|
+
comment_3
|
82
|
+
]
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'with scope' do
|
86
|
+
it 'works' do
|
87
|
+
expect(Comment.order_by_voting(:negative, :desc, scope: category)).to eq [
|
88
|
+
comment_1
|
89
|
+
]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, '.up' do
|
6
|
+
let!(:author) { create :author }
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
context 'with no scopeable' do
|
10
|
+
it 'delegates to create method' do
|
11
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: nil, value: 1
|
12
|
+
|
13
|
+
author.up comment
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with scopeable' do
|
18
|
+
let!(:category) { build :category }
|
19
|
+
|
20
|
+
it 'delegates to create method' do
|
21
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: category, value: 1
|
22
|
+
|
23
|
+
author.up comment, scope: category
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, ':vote_for' do
|
6
|
+
let!(:author) { create :author }
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
context 'with no scopeable' do
|
10
|
+
it 'delegates to vote object' do
|
11
|
+
expect(Voting::Vote).to receive(:vote_for).with author: author, resource: comment, scopeable: nil
|
12
|
+
|
13
|
+
author.vote_for comment
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with scopeable' do
|
18
|
+
let!(:category) { build :category }
|
19
|
+
|
20
|
+
it 'delegates to vote object' do
|
21
|
+
expect(Voting::Vote).to receive(:vote_for).with author: author, resource: comment, scopeable: category
|
22
|
+
|
23
|
+
author.vote_for comment, scope: category
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, ':vote' do
|
6
|
+
let!(:author) { create :author }
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
context 'with no scopeable' do
|
10
|
+
it 'delegates to vote object' do
|
11
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: nil, value: 1
|
12
|
+
|
13
|
+
author.vote comment, 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'with scopeable' do
|
18
|
+
let!(:category) { build :category }
|
19
|
+
|
20
|
+
it 'delegates to vote object' do
|
21
|
+
expect(Voting::Vote).to receive(:create).with author: author, resource: comment, scopeable: category, value: 1
|
22
|
+
|
23
|
+
author.vote comment, 1, scope: category
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
|
5
|
+
RSpec.describe Voting::Extension, ':voted?' do
|
6
|
+
let!(:author) { create :author }
|
7
|
+
let!(:comment) { create :comment }
|
8
|
+
|
9
|
+
context 'with no scopeable' do
|
10
|
+
context 'when has no vote for the given resource' do
|
11
|
+
before { allow(author).to receive(:vote_for).with(comment, scope: nil) { nil } }
|
12
|
+
|
13
|
+
specify { expect(author.voted?(comment)).to eq false }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when has vote for the given resource' do
|
17
|
+
before { allow(author).to receive(:vote_for).with(comment, scope: nil) { double } }
|
18
|
+
|
19
|
+
specify { expect(author.voted?(comment)).to eq true }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'with scopeable' do
|
24
|
+
let!(:category) { build :category }
|
25
|
+
|
26
|
+
context 'when has no vote for the given resource' do
|
27
|
+
before { allow(author).to receive(:vote_for).with(comment, scope: category) { nil } }
|
28
|
+
|
29
|
+
specify { expect(author.voted?(comment, scope: category)).to eq false }
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'when has vote for the given resource' do
|
33
|
+
before { allow(author).to receive(:vote_for).with(comment, scope: category) { double } }
|
34
|
+
|
35
|
+
specify { expect(author.voted?(comment, scope: category)).to eq true }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rails_helper'
|
4
|
+
require 'support/shared_context/with_database_records'
|
5
|
+
|
6
|
+
RSpec.describe Voting::Extension, '.voted_records' do
|
7
|
+
include_context 'with_database_records'
|
8
|
+
|
9
|
+
it 'returns all votes that this author gave' do
|
10
|
+
expect(author_1.voted_records).to match_array [vote_1, vote_4, vote_7]
|
11
|
+
end
|
12
|
+
end
|