mongoid_atomic_votes 0.2.1 → 1.0.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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +1 -1
- data/Gemfile +0 -1
- data/lib/mongoid_atomic_votes/atomic_votes.rb +98 -38
- data/lib/mongoid_atomic_votes/version.rb +1 -1
- data/lib/mongoid_atomic_votes/vote.rb +3 -13
- data/spec/factories/factories.rb +8 -2
- data/spec/lib/atomic_votes_spec.rb +194 -0
- data/spec/lib/vote_spec.rb +42 -0
- data/spec/spec_helper.rb +2 -3
- metadata +7 -5
- data/spec/atomic_votes_spec.rb +0 -171
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 18279cb81ff240c02a926b21066c461c80dfa500
|
4
|
+
data.tar.gz: 2234359382750bfb52027fff45933ae589df3897
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c135a92ae45d11af9af5fcea96d6469c614b982ef7aa5573ef364c21ea8807ff1875556b91880231265e5eb7ebe4bdda5286a9affb4586b8adec514a93bda65
|
7
|
+
data.tar.gz: c8c59df6c14596b8a2efa96041cf68f5bed0071efdc44f051f29b7ed55a40959e99fe1617a4a8560bd88b9e287f1b88be8376dd4a6e58282436c88ba70b9010a
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
@@ -2,100 +2,160 @@ module Mongoid
|
|
2
2
|
module AtomicVotes
|
3
3
|
class << self
|
4
4
|
def included(base)
|
5
|
+
define_fields(base)
|
5
6
|
define_relations(base)
|
6
7
|
define_scopes(base)
|
8
|
+
|
7
9
|
base.extend ClassMethods
|
8
10
|
end
|
9
11
|
|
10
12
|
private
|
11
|
-
def
|
13
|
+
def define_fields(base)
|
12
14
|
base.field :vote_count, type: Integer, default: 0
|
13
15
|
base.field :vote_value, type: Float, default: nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def define_relations(base)
|
14
19
|
base.embeds_many :votes, class_name: 'Mongoid::AtomicVotes::Vote', as: :atomic_voteable
|
15
20
|
end
|
16
21
|
|
17
22
|
def define_scopes(base)
|
18
|
-
base.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
scopes(base).each { |name, block| base.scope name, block }
|
24
|
+
end
|
25
|
+
|
26
|
+
def scopes(base)
|
27
|
+
{
|
28
|
+
not_voted: -> { base.where(:vote_value.exists => false) },
|
29
|
+
voted: -> { base.where(:vote_value.exists => true) },
|
30
|
+
voted_by: ->(resource) {
|
31
|
+
base.where(
|
32
|
+
'votes.voted_by_id' => resource.id,
|
33
|
+
'votes.voter_type' => resource.class.name
|
34
|
+
)
|
35
|
+
},
|
36
|
+
vote_value_in: ->(range) {
|
37
|
+
base.where(
|
38
|
+
:vote_value.gte => range.begin,
|
39
|
+
:vote_value.lte => range.end
|
40
|
+
)
|
41
|
+
},
|
42
|
+
highest_voted: ->(limit=10) { base.order_by(:vote_value.desc).limit(limit) }
|
43
|
+
}
|
27
44
|
end
|
28
45
|
end
|
29
46
|
|
47
|
+
# Creates an embedded vote record and updates number of votes and vote value.
|
48
|
+
#
|
49
|
+
# @param [Int,Float] value vote value
|
50
|
+
# @param [Mongoid::Document] voted_by object from which the vote is done
|
51
|
+
# @return [Boolean] success flag
|
30
52
|
def vote(value, voted_by)
|
31
53
|
mark = Vote.new(value: value, voted_by_id: voted_by.id, voter_type: voted_by.class.name)
|
32
|
-
return false unless mark.valid?
|
33
54
|
add_vote_mark(mark)
|
34
55
|
end
|
35
56
|
|
57
|
+
# Removes previously added vote.
|
58
|
+
#
|
59
|
+
# @param [Mongoid::Document] voted_by object from which the vote was done
|
60
|
+
# @return [Boolean] success flag
|
36
61
|
def retract(voted_by)
|
37
62
|
mark = self.votes.find_by(voted_by_id: voted_by.id)
|
38
|
-
|
39
|
-
remove_vote_mark(mark)
|
63
|
+
mark && remove_vote_mark(mark)
|
40
64
|
end
|
41
65
|
|
66
|
+
# Indicates whether the document has votes or not.
|
67
|
+
#
|
68
|
+
# @return [Boolean]
|
42
69
|
def has_votes?
|
43
70
|
self.vote_count > 0
|
44
71
|
end
|
45
72
|
|
73
|
+
# Indicates whether the document has a vote from particular voter object
|
74
|
+
#
|
75
|
+
# @param [Mongoid::Document] voted_by object from which the vote was done
|
76
|
+
# @return [Boolean]
|
46
77
|
def voted_by?(voted_by)
|
47
78
|
!!self.votes.find_by(voted_by_id: voted_by.id)
|
48
|
-
rescue
|
79
|
+
rescue NoMethodError, Mongoid::Errors::DocumentNotFound
|
49
80
|
false
|
50
81
|
end
|
51
82
|
|
52
83
|
module ClassMethods
|
84
|
+
attr_reader :vote_range
|
85
|
+
|
86
|
+
# Specifies possible vote range which is used vote mark validation later.
|
87
|
+
#
|
88
|
+
# @param [Range] val new vote range, for example: 1..5
|
89
|
+
# @return [Range] vote range, previously passed to a method as a parameter
|
53
90
|
def set_vote_range(val)
|
54
|
-
|
55
|
-
|
91
|
+
fail ArgumentError.new('argument should be a Range') unless val.is_a?(Range)
|
92
|
+
@vote_range = val
|
93
|
+
end
|
94
|
+
|
95
|
+
# Sets vote range to nil
|
96
|
+
# @return [NilClass] nil
|
97
|
+
def reset_vote_range
|
98
|
+
@vote_range = nil
|
56
99
|
end
|
57
100
|
end
|
58
101
|
|
59
102
|
private
|
60
|
-
def update_votes(mark, retract=false)
|
61
|
-
opts = {
|
62
|
-
'$inc' => { vote_count: retract ? -1 : 1 },
|
63
|
-
'$set' => { vote_value: self.vote_value }
|
64
|
-
}
|
65
103
|
|
66
|
-
|
67
|
-
|
104
|
+
def update_vote_counters(mark, increment_count_by)
|
105
|
+
self.vote_value = calculate_vote_value(mark.value, increment_count_by)
|
106
|
+
self.vote_count += increment_count_by
|
107
|
+
end
|
108
|
+
|
109
|
+
def calculate_vote_value(value, count)
|
110
|
+
current_vote_count = self.vote_count
|
111
|
+
new_vote_count = current_vote_count + count
|
112
|
+
|
113
|
+
if new_vote_count > 0
|
114
|
+
(current_vote_count * self.vote_value.to_f + value * count) / new_vote_count
|
68
115
|
else
|
69
|
-
|
116
|
+
nil
|
70
117
|
end
|
118
|
+
end
|
71
119
|
|
72
|
-
|
120
|
+
def update_votes(query_opts)
|
121
|
+
self.collection.find(_id: self.id).update_one(query_opts).modified_count > 0
|
73
122
|
end
|
74
123
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
124
|
+
def vote_options(mark)
|
125
|
+
{
|
126
|
+
'$inc' => { vote_count: 1 },
|
127
|
+
'$set' => { vote_value: self.vote_value },
|
128
|
+
'$push' => { votes: mark.as_json }
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
def retract_options(mark)
|
133
|
+
{
|
134
|
+
'$inc' => { vote_count: -1 },
|
135
|
+
'$set' => { vote_value: self.vote_value },
|
136
|
+
'$pull' => { votes: { _id: mark.id } }
|
137
|
+
}
|
83
138
|
end
|
84
139
|
|
85
140
|
def add_vote_mark(mark)
|
141
|
+
mark_is_valid = false
|
142
|
+
|
86
143
|
_assigning do
|
87
144
|
self.votes << mark
|
88
|
-
|
145
|
+
mark_is_valid = mark.valid?
|
146
|
+
mark_is_valid && update_vote_counters(mark, 1)
|
89
147
|
end
|
90
|
-
|
148
|
+
|
149
|
+
mark_is_valid && update_votes(vote_options(mark))
|
91
150
|
end
|
92
151
|
|
93
152
|
def remove_vote_mark(mark)
|
94
153
|
_assigning do
|
95
|
-
self.votes.reject! { |
|
96
|
-
|
154
|
+
self.votes.reject! { |vote| vote.id == mark.id }
|
155
|
+
update_vote_counters(mark, -1)
|
97
156
|
end
|
98
|
-
|
157
|
+
|
158
|
+
update_votes(retract_options(mark))
|
99
159
|
end
|
100
160
|
end
|
101
161
|
end
|
@@ -9,19 +9,9 @@ class Mongoid::AtomicVotes::Vote
|
|
9
9
|
field :voter_type, type: String
|
10
10
|
|
11
11
|
validates_presence_of :value, :voted_by_id, :voter_type
|
12
|
-
validates_inclusion_of :value,
|
12
|
+
validates_inclusion_of :value,
|
13
|
+
in: ->(vote) { vote.atomic_voteable.class.vote_range },
|
14
|
+
if: ->(vote) { vote.atomic_voteable.class.vote_range }
|
13
15
|
|
14
16
|
index({ voted_by_id: 1, mark: 1 }, unique: true, background: true)
|
15
|
-
|
16
|
-
class << self
|
17
|
-
@@vote_range = nil
|
18
|
-
|
19
|
-
def set_vote_range(val)
|
20
|
-
@@vote_range ||= val
|
21
|
-
end
|
22
|
-
|
23
|
-
def vote_range
|
24
|
-
@@vote_range
|
25
|
-
end
|
26
|
-
end
|
27
17
|
end
|
data/spec/factories/factories.rb
CHANGED
@@ -1,9 +1,15 @@
|
|
1
1
|
FactoryGirl.define do
|
2
2
|
factory :user do
|
3
|
-
sequence(:login) {|n| "user_#{n}"}
|
3
|
+
sequence(:login) { |n| "user_#{n}" }
|
4
4
|
end
|
5
5
|
|
6
6
|
factory :post do
|
7
|
-
sequence(:title) {|n| "post_#{n}"}
|
7
|
+
sequence(:title) { |n| "post_#{n}" }
|
8
|
+
end
|
9
|
+
|
10
|
+
factory :vote, class: 'Mongoid::AtomicVotes::Vote' do
|
11
|
+
value { rand(0..10) }
|
12
|
+
sequence(:voted_by_id) { |n| n }
|
13
|
+
voter_type 'User'
|
8
14
|
end
|
9
15
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Post do
|
4
|
+
let!(:users) { FactoryGirl.create_list(:user, 2) }
|
5
|
+
let!(:post) { FactoryGirl.create(:post) }
|
6
|
+
|
7
|
+
it 'has Mongoid::AtomicVotes module' do
|
8
|
+
expect(subject.class.ancestors).to include(Mongoid::AtomicVotes)
|
9
|
+
end
|
10
|
+
|
11
|
+
it { expect(subject).to respond_to(:vote) }
|
12
|
+
it { expect(subject).to respond_to(:retract) }
|
13
|
+
it { expect(subject).to respond_to(:has_votes?) }
|
14
|
+
it { expect(subject).to respond_to(:vote_count) }
|
15
|
+
it { expect(subject).to respond_to(:vote_value) }
|
16
|
+
it { expect(subject).to respond_to(:votes) }
|
17
|
+
|
18
|
+
it { expect(subject.class).to respond_to(:set_vote_range) }
|
19
|
+
it { expect { subject.class.set_vote_range(1) }.to raise_error('argument should be a Range') }
|
20
|
+
|
21
|
+
describe '#votes' do
|
22
|
+
it { expect(post.votes).to be_an_instance_of(Array) }
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'when does not have votes' do
|
26
|
+
it 'is in not_voted scope' do
|
27
|
+
expect(subject.class.not_voted).to include(post)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'is not in voted scope' do
|
31
|
+
expect(subject.class.voted).not_to include(post)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'is not in voted_by scope' do
|
35
|
+
expect(users.all? { |user| subject.class.voted_by(user).include?(post) == false }).to eq(true)
|
36
|
+
end
|
37
|
+
|
38
|
+
it '#vote_count returns 0' do
|
39
|
+
expect(post.vote_count).to eq(0)
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#vote_value returns nil' do
|
43
|
+
expect(post.vote_value).to be_nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it '#votes returns empty array' do
|
47
|
+
expect(post.votes).to eq([])
|
48
|
+
end
|
49
|
+
|
50
|
+
it '#has_votes? returns false' do
|
51
|
+
expect(post.has_votes?).to eq(false)
|
52
|
+
end
|
53
|
+
|
54
|
+
it '#voted_by? returns false for any voter' do
|
55
|
+
expect(users.all? { |user| post.voted_by?(user) == false }).to eq(true)
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#vote' do
|
59
|
+
let(:votes) { [rand(1..10), rand(1..10)] }
|
60
|
+
let(:expected_vote_value) { votes.sum.to_f / users.size }
|
61
|
+
|
62
|
+
it 'returns true on successfull vote' do
|
63
|
+
expect(users.all? { |user| post.vote(rand(1..10), user) == true }).to eq(true)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'properly calculates vote value' do
|
67
|
+
users.each_with_index { |user, index| post.vote(votes[index], user) }
|
68
|
+
expect(post.vote_value).to eq(expected_vote_value)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'properly adds vote' do
|
72
|
+
expect { post.vote(rand(1..10), users.sample) }.to change { post.vote_count }.by(1)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'persists changes to database' do
|
76
|
+
users.each_with_index { |user, index| post.vote(votes[index], user) }
|
77
|
+
|
78
|
+
db_post = Post.find(post.id)
|
79
|
+
votes_info = [
|
80
|
+
db_post.vote_value,
|
81
|
+
db_post.vote_count,
|
82
|
+
db_post.votes.size
|
83
|
+
]
|
84
|
+
expect(votes_info).to eq([expected_vote_value, users.size, users.size])
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
describe 'when has votes' do
|
90
|
+
before(:each) do
|
91
|
+
users.each { |user| post.vote(rand(1..10), user) }
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'is not in the not_voted scope' do
|
95
|
+
expect(subject.class.not_voted).not_to include(post)
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'is in voted scope' do
|
99
|
+
expect(subject.class.voted).to include(post)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'is in voted_by scope for each voted user' do
|
103
|
+
expect(users.all? { |user| subject.class.voted_by(user).include?(post) == true }).to eq(true)
|
104
|
+
end
|
105
|
+
|
106
|
+
it '#vote_count returns count of votes' do
|
107
|
+
expect(post.vote_count).to eq(post.votes.size)
|
108
|
+
end
|
109
|
+
|
110
|
+
it '#vote_value returns actual vote value' do
|
111
|
+
expected_vote_value = post.votes.map(&:value).sum.to_f / post.votes.size
|
112
|
+
expect(post.vote_value).to eq(expected_vote_value)
|
113
|
+
end
|
114
|
+
|
115
|
+
describe '#votes' do
|
116
|
+
let(:votes) { post.votes }
|
117
|
+
|
118
|
+
it { expect(votes.class).to eq(Array) }
|
119
|
+
it { expect(votes).not_to be_empty }
|
120
|
+
it { expect(votes.size).to eq(users.size) }
|
121
|
+
it { expect(votes.map(&:voted_by_id)).to match_array(users.map(&:id)) }
|
122
|
+
end
|
123
|
+
|
124
|
+
it '#has_votes? returns true' do
|
125
|
+
expect(post.has_votes?).to eq(true)
|
126
|
+
end
|
127
|
+
|
128
|
+
it '#voted_by? returns true for all voted resource' do
|
129
|
+
expect(users.all? { |user| post.voted_by?(user) == true }).to eq(true)
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#retract' do
|
133
|
+
it 'returns true on successfull vote retract' do
|
134
|
+
expect(users.all? { |user| post.retract(user) == true }).to eq(true)
|
135
|
+
end
|
136
|
+
|
137
|
+
it 'properly calculates vote value' do
|
138
|
+
user = users.sample
|
139
|
+
retracted_vote_value = post.votes.find_by(voted_by_id: user.id).value
|
140
|
+
expected_vote_value = (post.votes.map(&:value).sum.to_f - retracted_vote_value) / (users.size - 1)
|
141
|
+
|
142
|
+
post.retract(user)
|
143
|
+
expect(post.vote_value).to eq(expected_vote_value)
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'properly decreases vote count' do
|
147
|
+
expect { post.retract(users.sample) }.to change { post.vote_count }.by(-1)
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'persists changes to database' do
|
151
|
+
user = users.sample
|
152
|
+
retracted_vote_value = post.votes.find_by(voted_by_id: user.id).value
|
153
|
+
expected_vote_value = (post.votes.map(&:value).sum.to_f - retracted_vote_value) / (users.size - 1)
|
154
|
+
|
155
|
+
post.retract(user)
|
156
|
+
|
157
|
+
db_post = Post.find(post.id)
|
158
|
+
votes_info = [
|
159
|
+
db_post.vote_value,
|
160
|
+
db_post.vote_count,
|
161
|
+
db_post.votes.size
|
162
|
+
]
|
163
|
+
expect(votes_info).to eq([expected_vote_value, users.size - 1, users.size - 1])
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'when model has vote_range' do
|
169
|
+
before { Post.set_vote_range(1..5) }
|
170
|
+
|
171
|
+
it 'marks vote as invalid if value is out of range' do
|
172
|
+
post.vote(rand(6..10), users.sample)
|
173
|
+
expect(post.votes.last).not_to be_valid
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'marks post as invalid' do
|
177
|
+
post.vote(rand(6..10), users.sample)
|
178
|
+
expect(post).not_to be_valid
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'has properly filled error messages for invalid vote' do
|
182
|
+
post.vote(rand(6..10), users.sample)
|
183
|
+
expect(post.votes.last.errors.messages[:value].first).to match(/included in the list/)
|
184
|
+
end
|
185
|
+
|
186
|
+
describe '#reset_vote_range' do
|
187
|
+
let(:klass) { post.class }
|
188
|
+
|
189
|
+
it 'sets vote_range to nil' do
|
190
|
+
expect { klass.reset_vote_range }.to change { klass.vote_range }.to(nil)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Mongoid::AtomicVotes::Vote do
|
4
|
+
let(:vote) { FactoryGirl.build(:vote, atomic_voteable: post) }
|
5
|
+
let(:post) { FactoryGirl.create(:post) }
|
6
|
+
|
7
|
+
it { expect(vote).to be_valid }
|
8
|
+
|
9
|
+
it 'is not valid without vote value' do
|
10
|
+
vote.value = nil
|
11
|
+
expect(vote).not_to be_valid
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'is not valid without voted_by_id' do
|
15
|
+
vote.voted_by_id = nil
|
16
|
+
expect(vote).not_to be_valid
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'is not valid without voter type' do
|
20
|
+
vote.voter_type = nil
|
21
|
+
expect(vote).not_to be_valid
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'with vote range' do
|
25
|
+
before do
|
26
|
+
vote.atomic_voteable = post
|
27
|
+
post.class.set_vote_range(2..5)
|
28
|
+
end
|
29
|
+
|
30
|
+
after { post.class.reset_vote_range }
|
31
|
+
|
32
|
+
it 'is valid if value is in range specified in vote_range' do
|
33
|
+
vote.value = rand(post.class.vote_range)
|
34
|
+
expect(vote).to be_valid
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is not valid if value is out of range specified in vote_range' do
|
38
|
+
vote.value = [1, 6].sample
|
39
|
+
expect(vote).not_to be_valid
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -3,6 +3,8 @@ MODELS = File.join(File.dirname(__FILE__), 'models')
|
|
3
3
|
require 'rubygems'
|
4
4
|
require 'database_cleaner'
|
5
5
|
require 'factory_girl'
|
6
|
+
require 'mongoid'
|
7
|
+
require 'mongoid_atomic_votes'
|
6
8
|
require 'simplecov'
|
7
9
|
|
8
10
|
SimpleCov.start do
|
@@ -10,9 +12,6 @@ SimpleCov.start do
|
|
10
12
|
add_filter '/.bundle/'
|
11
13
|
end
|
12
14
|
|
13
|
-
require 'mongoid'
|
14
|
-
require 'mongoid_atomic_votes'
|
15
|
-
|
16
15
|
Dir["#{MODELS}/*.rb"].each { |f| require f }
|
17
16
|
|
18
17
|
Mongoid.configure do |config|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mongoid_atomic_votes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Hck
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2016-04-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: mongoid
|
@@ -42,8 +42,9 @@ files:
|
|
42
42
|
- lib/mongoid_atomic_votes/version.rb
|
43
43
|
- lib/mongoid_atomic_votes/vote.rb
|
44
44
|
- mongoid_atomic_votes.gemspec
|
45
|
-
- spec/atomic_votes_spec.rb
|
46
45
|
- spec/factories/factories.rb
|
46
|
+
- spec/lib/atomic_votes_spec.rb
|
47
|
+
- spec/lib/vote_spec.rb
|
47
48
|
- spec/models/post.rb
|
48
49
|
- spec/models/user.rb
|
49
50
|
- spec/spec_helper.rb
|
@@ -66,13 +67,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
67
|
version: '0'
|
67
68
|
requirements: []
|
68
69
|
rubyforge_project:
|
69
|
-
rubygems_version: 2.
|
70
|
+
rubygems_version: 2.5.1
|
70
71
|
signing_key:
|
71
72
|
specification_version: 4
|
72
73
|
summary: Atomic votes implementation for mongoid
|
73
74
|
test_files:
|
74
|
-
- spec/atomic_votes_spec.rb
|
75
75
|
- spec/factories/factories.rb
|
76
|
+
- spec/lib/atomic_votes_spec.rb
|
77
|
+
- spec/lib/vote_spec.rb
|
76
78
|
- spec/models/post.rb
|
77
79
|
- spec/models/user.rb
|
78
80
|
- spec/spec_helper.rb
|
data/spec/atomic_votes_spec.rb
DELETED
@@ -1,171 +0,0 @@
|
|
1
|
-
require 'pry'
|
2
|
-
require 'spec_helper'
|
3
|
-
|
4
|
-
describe Post do
|
5
|
-
before(:each) do
|
6
|
-
@users = FactoryGirl.create_list(:user, 2)
|
7
|
-
@post = FactoryGirl.create(:post)
|
8
|
-
end
|
9
|
-
|
10
|
-
it 'has Mongoid::AtomicVotes module' do
|
11
|
-
expect(subject.class.ancestors).to include(Mongoid::AtomicVotes)
|
12
|
-
end
|
13
|
-
|
14
|
-
it { expect(subject).to respond_to(:vote) }
|
15
|
-
it { expect(subject).to respond_to(:retract) }
|
16
|
-
it { expect(subject).to respond_to(:has_votes?) }
|
17
|
-
it { expect(subject).to respond_to(:vote_count) }
|
18
|
-
it { expect(subject).to respond_to(:vote_value) }
|
19
|
-
it { expect(subject).to respond_to(:votes) }
|
20
|
-
|
21
|
-
it { expect(subject.class).to respond_to(:set_vote_range) }
|
22
|
-
it { expect { subject.class.set_vote_range(1) }.to raise_error('argument should be a Range') }
|
23
|
-
|
24
|
-
describe '#votes' do
|
25
|
-
it 'is array of votes' do
|
26
|
-
expect(@post.votes).to be_an_instance_of(Array)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
describe 'when does not have votes' do
|
31
|
-
it 'is in not_voted scope' do
|
32
|
-
expect(subject.class.not_voted).to include(@post)
|
33
|
-
end
|
34
|
-
|
35
|
-
it 'is not in voted scope' do
|
36
|
-
expect(subject.class.voted).not_to include(@post)
|
37
|
-
end
|
38
|
-
|
39
|
-
it 'is not in voted_by scope' do
|
40
|
-
@users.each do |u|
|
41
|
-
expect(subject.class.voted_by(u)).not_to include(@post)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
it '#vote_count returns 0' do
|
46
|
-
expect(@post.vote_count).to eq(0)
|
47
|
-
end
|
48
|
-
|
49
|
-
it '#vote_value returns nil' do
|
50
|
-
expect(@post.vote_value).to be_nil
|
51
|
-
end
|
52
|
-
|
53
|
-
it '#votes returns empty array' do
|
54
|
-
expect(@post.votes).to eq([])
|
55
|
-
end
|
56
|
-
|
57
|
-
it '#has_votes? returns false' do
|
58
|
-
expect(@post.vote_count).to eq(0)
|
59
|
-
expect(@post.has_votes?).to be_falsey
|
60
|
-
end
|
61
|
-
|
62
|
-
it '#voted_by? returns false with any resource' do
|
63
|
-
@users.each do |u|
|
64
|
-
expect(@post.voted_by?(u)).to be_falsey
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
it '#vote returns true on successful vote' do
|
69
|
-
@users.each do |u|
|
70
|
-
expect(@post.vote(rand(1..10), u)).to be_truthy
|
71
|
-
end
|
72
|
-
|
73
|
-
vote_value = @post.votes.map(&:value).sum.to_f/@post.votes.size
|
74
|
-
|
75
|
-
expect(@post.vote_value).to eq(vote_value)
|
76
|
-
expect(@post.vote_count).to eq(@users.size)
|
77
|
-
expect(@post.votes.size).to eq(@users.size)
|
78
|
-
|
79
|
-
Post.find(@post.id).tap do |post|
|
80
|
-
expect(post.vote_value).to eq(vote_value)
|
81
|
-
expect(post.vote_count).to eq(@users.size)
|
82
|
-
expect(post.votes.size).to eq(@users.size)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
describe 'when has votes' do
|
88
|
-
before(:each) do
|
89
|
-
@users.each{|u| @post.vote(rand(1..10), u)}
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'is not in the not_voted scope' do
|
93
|
-
expect(subject.class.not_voted).not_to include(@post)
|
94
|
-
end
|
95
|
-
|
96
|
-
it 'is in voted scope' do
|
97
|
-
expect(subject.class.voted).to include(@post)
|
98
|
-
end
|
99
|
-
|
100
|
-
it 'is in voted_by scope for each voted user' do
|
101
|
-
@users.each do |u|
|
102
|
-
expect(subject.class.voted_by(u)).to include(@post)
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
it '#vote_count returns count of votes' do
|
107
|
-
expect(@post.vote_count).to eq(@post.votes.size)
|
108
|
-
end
|
109
|
-
|
110
|
-
it '#vote_value returns actual vote value' do
|
111
|
-
vote_value = @post.votes.map(&:value).sum.to_f/@post.votes.size
|
112
|
-
expect(@post.vote_value).to eq(vote_value)
|
113
|
-
end
|
114
|
-
|
115
|
-
it '#votes returns array of vote marks' do
|
116
|
-
@post.votes.tap do |votes|
|
117
|
-
expect(votes.class).to eq(Array)
|
118
|
-
expect(votes).not_to be_empty
|
119
|
-
expect(votes.size).to eq(@users.size)
|
120
|
-
expect(votes.map(&:voted_by_id).sort).to eq(@users.map(&:id).sort)
|
121
|
-
end
|
122
|
-
end
|
123
|
-
|
124
|
-
it '#has_votes? returns true' do
|
125
|
-
expect(@post.vote_count).to eq(@post.votes.size)
|
126
|
-
expect(@post.has_votes?).to be_truthy
|
127
|
-
end
|
128
|
-
|
129
|
-
it '#voted_by? returns true for all voted resource' do
|
130
|
-
@users.each do |u|
|
131
|
-
expect(@post.voted_by?(u)).to be_truthy
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
it '#retract returns true on successful vote retract' do
|
136
|
-
cnt = @users.size
|
137
|
-
|
138
|
-
@users.each do |u|
|
139
|
-
expect(@post.retract(u)).to be_truthy
|
140
|
-
cnt -= 1
|
141
|
-
|
142
|
-
vote_value = @post.votes.size == 0 ? nil : @post.votes.map(&:value).sum.to_f/@post.votes.size
|
143
|
-
|
144
|
-
expect(@post.vote_value).to eq(vote_value)
|
145
|
-
expect(@post.vote_count).to eq(@post.votes.size)
|
146
|
-
expect(@post.vote_count).to eq(cnt)
|
147
|
-
|
148
|
-
expect(Post.find(@post.id).vote_value).to eq(vote_value)
|
149
|
-
expect(Post.find(@post.id).vote_count).to eq(cnt)
|
150
|
-
Post.find(@post.id).votes.size == cnt
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
describe 'when model has vote_range' do
|
156
|
-
before(:each) do
|
157
|
-
Post.send(:set_vote_range, 1..5)
|
158
|
-
@post = FactoryGirl.create(:post)
|
159
|
-
end
|
160
|
-
|
161
|
-
it 'Vote has vote_range set up' do
|
162
|
-
expect(Mongoid::AtomicVotes::Vote.vote_range).to eq((1..5))
|
163
|
-
end
|
164
|
-
|
165
|
-
it 'Vote is not valid if its value is not in specified vote_range' do
|
166
|
-
@vote = Mongoid::AtomicVotes::Vote.new(value: rand(6..10), voted_by_id: @users.first.id, voter_type: @users.first.class.name)
|
167
|
-
expect(@vote).not_to be_valid
|
168
|
-
expect(@vote.errors.messages[:value].first).to match(/included in the list/)
|
169
|
-
end
|
170
|
-
end
|
171
|
-
end
|